[电池]设置-电池-上次充满电时间计算

1. 现象

为什么第一次开机或者格式化后电池显示上次充满电是xxx天或者xxx分钟前,不管之前电池电量数值

实际操作:

  1. 充满电且拔除充电线,则显示上次充满电为0分钟前
  2. 新机器第一次开机,不论当前电量大小,拔除充电线,则显示上次充满电为0分钟前
  3. 修改系统时间未来时间,则显示上次充满电是xxx天或者xxx分钟前
  4. 修改系统时间过去时间,则显示为0分钟前

2. 原因

因为上次充满电接口getStartClockTime中 mStartClockTime = currentTime - (mClocks.elapsedRealtime()/当前自开机后,经过的时间,包括深度休眠的时间/-(mRealtimeStart/1000))决定,其中elapsedRealtime和mRealtimeStart的数值刷新逻辑BatteryStatsImpl->setBatteryStateLocked->setOnBatteryLocked->resetAllStatsLocked->initTimes改变

故这里显示上次充满电是xxx天或者xxx分钟前,是因为没有实际充电的时刻时,取的上次充满电时间为系统当前时间.故会有明明没充满电,显示上次充满电是xxx天或者xxx分钟前的逻辑显示。除非充满电且拔除充电线则为正常显示了。

Google的这个逻辑确实存在不精确

3. 源码

3.1 设置->电池
package com.android.settings.fuelgauge;

public class PowerUsageSummary extends PowerUsageBase implements OnLongClickListener,
        BatteryTipPreferenceController.BatteryTipListener {
        
    @VisibleForTesting
    void updateLastFullChargePreference() {
        if (mBatteryInfo != null && mBatteryInfo.averageTimeToDischarge
                != Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN) {
            // 电池在充满电后的预估使用时间
        } else {
            final long lastFullChargeTime = mBatteryUtils.calculateLastFullChargeTime(mStatsHelper,
                    System.currentTimeMillis());
            mLastFullChargePref.setTitle(R.string.battery_last_full_charge); // 上次充满电
            mLastFullChargePref.setSubtitle(
                    StringUtil.formatRelativeTime(getContext(), lastFullChargeTime,
                            false /* withSeconds */));
        }
    }

3.2 BatteryUtils.calculateLastFullChargeTime
package com.android.settings.fuelgauge;

/**
 * Utils for battery operation
 */
public class BatteryUtils {

    /**
     * Calculate the time since last full charge, including the device off time
     * 计算上次充电电时间,包含设备关机时间
     *
     * @param batteryStatsHelper utility class that contains the data 电池信息类
     * @param currentTimeMs      current wall time 当前时间
     * @return time in millis 返回时长
     */
    public long calculateLastFullChargeTime(BatteryStatsHelper batteryStatsHelper,
            long currentTimeMs) {
        return currentTimeMs - batteryStatsHelper.getStats().getStartClockTime();

    }
3.3 BatteryStatsHelper.getStats().getStartClockTime()
  • frameworks/base/core/java/com/android/internal/os/BatteryStatsHelper.java
package com.android.internal.os;

public class BatteryStatsHelper {

    private BatteryStats mStats;

    public BatteryStats getStats() {
        if (mStats == null) {
            load();
        }
        return mStats;
    }
    
    private static BatteryStatsImpl getStats(IBatteryStats service) {
        try {
            ParcelFileDescriptor pfd = service.getStatisticsStream();
            if (pfd != null) {
                try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
                    byte[] data = readFully(fis, MemoryFile.getSize(pfd.getFileDescriptor()));
                    Parcel parcel = Parcel.obtain();
                    parcel.unmarshall(data, 0, data.length);
                    parcel.setDataPosition(0);
                    BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR
                            .createFromParcel(parcel);
                    return stats;
                } catch (IOException e) {
                    Log.w(TAG, "Unable to read statistics stream", e);
                }
            }
        } catch (RemoteException e) {
            Log.w(TAG, "RemoteException:", e);
        }
        return new BatteryStatsImpl();
    }
3.3.1 BatteryStats.getStartClockTime()
  • frameworks/base/core/java/android/os/BatteryStats.java
package android.os;

public abstract class BatteryStats implements Parcelable {

    /**
     * Return the wall clock time when battery stats data collection started.
     */
    public abstract long getStartClockTime()
3.3.2 BatteryStatsImpl.getStartClockTime()
  • frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
package com.android.internal.os;

public class BatteryStatsImpl extends BatteryStats {

    // 获取上次充满电时间
    @Override public long getStartClockTime() {
    final long currentTime = System.currentTimeMillis();
    if (ensureStartClockTime(currentTime)) {
            recordCurrentTimeChangeLocked(currentTime, mClocks.elapsedRealtime(),
                    mClocks.uptimeMillis());
        }
        return mStartClockTime;
    }
3.4 BatteryStatsImpl.getStartClockTime
3.4.1 BatteryStatsImpl.getStartClockTime.ensureStartClockTime
package com.android.internal.os;

public class BatteryStatsImpl extends BatteryStats {

    long mStartClockTime;

    void initTimes(long uptime, long realtime) {
        ...
        mStartClockTime = System.currentTimeMillis();
        ...
    }

    // 从电池详情数据库中 batterystats.bin 更新StartClockTime
    public void readSummaryFromParcel(Parcel in) throws ParcelFormatException {
        ...
        // 从 batterystats.bin 获取上次充满电时间
        mStartClockTime = in.readLong();
        mRealtimeStart = in.readLong();
        ...
    }


    public void writeSummaryToParcel(Parcel out, boolean inclHistory) {
        ...
        // Pull the clock time.  This may update the time and make a new history entry
        // if we had originally pulled a time before the RTC was set.
        long startClockTime = getStartClockTime();
        out.writeLong(startClockTime);
        out.writeLong(mRealtimeStart);
        ...
    }

    // 是否需要重新校准上次充满电时间
    boolean ensureStartClockTime(final long currentTime) {
        final long ABOUT_ONE_YEAR = 365*24*60*60*1000L; // 1 年时间 31536000000
        // 是否满足以下条件
        // 1. 当前时间毫秒值 大于 1年,例如这里是100%满足,2018-12-22 10:41:46,即当前时间毫秒值 1545446651000
        // 2. 上次充满电时间StartClockTime 小于 当前时间毫秒值与1年的差, 即这里即记录最长上次充满电1年内,即我手动调整时间,最长只能记录上次充满电365天内

        // 或者满足下面条件
        // 1. 上次充满电时间StartClockTime 大于 当前时间毫秒值,这个条件可以将系统时间调整为过去的时间
        if ((currentTime > ABOUT_ONE_YEAR && mStartClockTime < (currentTime-ABOUT_ONE_YEAR))
                || (mStartClockTime > currentTime)) {
            // If the start clock time has changed by more than a year, then presumably
            // the previous time was completely bogus.  So we are going to figure out a
            // new time based on how much time has elapsed since we started counting.
            mStartClockTime = currentTime - (mClocks.elapsedRealtime()/*获取从设备boot后经历的时间值*/-(mRealtimeStart/1000));
            return true;
        }
        return false;
    }
    
    
3.4.2 查看 mStartClockTime 的赋值情况
  1. BatteryStatsImpl 初始化时间
    private BatteryStatsImpl(Clocks clocks, File systemDir, Handler handler,
            PlatformIdleStateCallback cb,
            UserInfoProvider userInfoProvider) {
        ...
        long uptime = mClocks.uptimeMillis() * 1000;
        long realtime = mClocks.elapsedRealtime() * 1000;
        initTimes(uptime, realtime)
  1. resetAllStatsLocked 初始化时间
    private void resetAllStatsLocked() {
        final long uptimeMillis = mClocks.uptimeMillis();
        final long elapsedRealtimeMillis = mClocks.elapsedRealtime();
        initTimes(uptimeMillis * 1000, elapsedRealtimeMillis * 1000);
        
  1. initTimes 函数
    public interface Clocks {
        public long elapsedRealtime();
        public long uptimeMillis();
    }

    public static class SystemClocks implements Clocks {
        public long elapsedRealtime() {
            return SystemClock.elapsedRealtime(); // 自开机后,经过的时间,包括深度休眠的时间
        }

        public long uptimeMillis() {
            return SystemClock.uptimeMillis(); // 自开机后,经过的时间,不包括深度休眠的时间
        }
    }
    
    void initTimes(long uptime, long realtime) {
        mStartClockTime = System.currentTimeMillis(); // 系统当前时间,即日期时间,可以被系统设置修改,如果设置系统时间,时间值会发生跳变。
        mOnBatteryTimeBase.init(uptime, realtime);
        mOnBatteryScreenOffTimeBase.init(uptime, realtime);
        mRealtimeStart = realtime;
    }
3.5 BatteryStatsImpl.setBatteryStateLocked.setOnBatteryLocked.resetAllStatsLocked
    @GuardedBy("this")
    public void setBatteryStateLocked(final int status, final int health, final int plugType,
            final int level, /* not final */ int temp, final int volt, final int chargeUAh,
            final int chargeFullUAh) {
            if (onBattery != mOnBattery) {
                ...
                setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level, chargeUAh);


    @GuardedBy("this")
    protected void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime,
            final boolean onBattery, final int oldStatus, final int level, final int chargeUAh) {
            
        if (onBattery) {
            // We will reset our status if we are unplugging after the
            // battery was last full, or the level is at 100, or
            // we have gone through a significant charge (from a very low
            // level to a now very high level).
            boolean reset = false;
            if (!mNoAutoReset && (oldStatus == BatteryManager.BATTERY_STATUS_FULL
                    || level >= 90
                    || (mDischargeCurrentLevel < 20 && level >= 80)
                    || (getHighDischargeAmountSinceCharge() >= 200
                            && mHistoryBuffer.dataSize() >= MAX_HISTORY_BUFFER))) {
                ...
                resetAllStatsLocked();
<think>好的,我现在需要帮用户找一个用C语言编写的程序,实现充时LED1闪烁(周期2秒)、充满后LED1常亮,以及放时LED2常亮的功能。首先,我得理清楚用户的具体需求,然后考虑如何用C语言来实现这些状态控制。 首先,用户提到了三个状态:充中、充满和放中。每个状态对应不同的LED行为。充时LED1要每2秒闪烁一次,也就是亮1秒灭1秒。充满后LED1常亮,放时LED2常亮。这里可能需要用到定时器来控制LED的闪烁周期,同时需要检测电池的状态,比如充状态、充满状态和放状态。 接下来,我需要考虑硬件相关的部分。通常,控制LED需要配置GPIO引脚为输出模式,然后通过高低平来控制LED的亮灭。用户没有指定具体的硬件平台,但根据提供的引用,可能涉及到Beetle ESP32-C3开发板或者其他微控制器。因此,代码需要具备可移植性,或者至少需要标注出硬件相关的部分可能需要调整。 然后,关于电池状态的检测。用户没有详细说明如何判断当前是充充满还是放状态。通常,这可能需要通过ADC读取电池压,或者有专门的源管理芯片提供状态信号。这里可能需要假设有一些函数或硬件接口来获取这些状态,比如`get_charge_status()`返回不同的枚举值。 接下来是代码结构。主程序应该是一个循环,不断检查当前状态,并根据状态控制LED。对于LED1的闪烁,需要使用定时器中断或者软件延时。由于用户可能是在嵌入式系统中实现,使用定时器中断更为可靠,不会阻塞其他任务。但若资源有限,也可以在主循环中使用延时函数,不过这样会影响其他操作的实时性。 考虑到代码示例需要简洁,可能先采用软件延时的方式,但需要指出在实际应用中可能更推荐使用定时器。例如,在充状态下,LED1需要每1秒切换一次状态,以达到2秒的周期(亮1秒,灭1秒)。这可以通过记录上一次切换的时间,并在每次循环中检查是否达到了时间间隔来实现。 然后,针对各个状态的处理: 1.状态(CHARGING):LED1闪烁,周期2秒。这里需要控制LED1每隔1秒切换状态,比如使用一个标志变量来记录当前是亮还是灭,每次时间到达1秒时切换,并重置计时器。同时,LED2应该保持熄灭。 2. 充满状态(FULL):LED1常亮,所以直接设置为高平(或低平,取决于硬件连接方式),LED2熄灭。 3.状态(DISCHARGING):LED2常亮,LED1熄灭。 接下来,需要编写代码框架。首先定义LED的引脚,比如LED1接GPIO2,LED2接GPIO3。然后定义状态枚举,以及模拟获取状态的函数。在主循环中,根据状态执行不同的控制逻辑。 对于充状态下的闪烁,可以使用一个静态变量来记录上一次切换的时间,每次循环检查当前时间是否超过了1秒,如果超过,则切换LED1的状态,并更新记录的时间。 需要注意的是,延时函数(如sleep(1))会阻塞整个循环,这可能影响其他任务的执行。因此,更好的方法是使用非阻塞的方式,比如记录时间戳,利用系统定时器来检查时间间隔。例如,使用millis()函数类似的方法,但具体实现依赖于硬件平台。 此外,用户提供的引用中提到了PWM调速和开发板的问题,可能涉及到定时器的使用,但在这个问题中,主要关注LED的控制,所以暂时不需要涉及PWM,除非LED的亮度需要调节,但根据需求,只需要开关控制。 最后,代码需要根据不同的硬件调整引脚定义和可能的中断配置。因此,在示例代码中应添加注释说明可能需要修改的部分,并提醒用户根据实际硬件调整。 总结步骤: 1. 定义LED引脚和状态枚举。 2. 初始化GPIO为输出模式。 3. 主循环中获取当前状态。 4. 根据状态控制LED,充状态下处理闪烁逻辑。 5. 使用非阻塞延时方法控制闪烁周期。 现在,将这些思路转化为具体的C代码,确保逻辑正确,并添加必要的注释说明。</think>根据需求,以下是一个基于嵌入式系统的C语言代码框架示例。假设使用GPIO控制LED,需要根据实际硬件调整引脚定义: ```c #include <stdbool.h> #include "hal_gpio.h" // 需替换为实际硬件库 #include "hal_timer.h" // 需包含硬件计时库 // 假设硬件引脚定义 #define LED1_PIN GPIO2 #define LED2_PIN GPIO3 // 电池状态枚举 typedef enum { CHARGING, // 充中 FULL, // 已充满 DISCHARGING // 放中 } ChargeState; // 模拟状态获取函数(需根据硬件实现) ChargeState get_charge_status() { // 通过ADC检测压或读取源管理芯片状态 return CHARGING; // 示例返回值 } int main() { bool led1_state = false; uint32_t last_toggle = 0; // 记录上次切换时间 gpio_init(LED1_PIN, OUTPUT); gpio_init(LED2_PIN, OUTPUT); while(1) { ChargeState status = get_charge_status(); switch(status) { case CHARGING: // LED1周期2秒闪烁(亮1秒灭1秒) if (get_current_time() - last_toggle >= 1000) { // 时间单位ms led1_state = !led1_state; gpio_write(LED1_PIN, led1_state); last_toggle = get_current_time(); } gpio_write(LED2_PIN, LOW); // 关闭LED2 break; case FULL: gpio_write(LED1_PIN, HIGH); // LED1常亮 gpio_write(LED2_PIN, LOW); break; case DISCHARGING: gpio_write(LED1_PIN, LOW); gpio_write(LED2_PIN, HIGH); // LED2常亮 break; } } return 0; } ``` 关键实现要点: 1. 使用`get_current_time()`获取系统时间(需硬件支持) 2.状态通过时间计算实现非阻塞闪烁 3. GPIO操作需根据实际硬件库调整
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

法迪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值