C++实战:断电记忆功能实现

*************

C++

topic: battery backup memory

*************

Today we get something really fun. Let's write a fuction to implement the power-off memory function. In a real project, it is crucial to consider the total power consumption of statistical equipment through its entire life cicle. And we will try to implement the battery-off memory function of the enegry.

Frame is the basic of the whole program. Design the frame is the first step. And write some functions to implement the power-off memory function.And here is the frame.

  1. FlashDataStorage - 数据存储层
  2. EnergyData - 数据结构定义
  3. PowerManager - 电源管理器
  4. EnergyMonitor - 能耗监控器
  5. MainsEnergyManager - 主控制器

Memory is all to the computer. Each variable must be assigned a fixed amount of memory in flash, and its location must be specified. Image that there are same houses in a line. Each house has a door number. You are the commander, you say 'You, my solder, you will have the house No.1 and No.2.' This is what struct data do. And the first thing is to tell the flash what you should remember.

The equipment has run for 1 hour and the data the flash should remember is as folllow:

House number: 0x08080000 - 0x0808004F

0x08080000: E4 E3 E2 E1    // magic = 0xE1E2E3E4 (小端序)
0x08080004: 01 00          // version = 0x0001
0x08080006: 2B 1A          // checksum = 0x1A2B
0x08080008: F4 7E DF 02 DF 02 00 00  // totalEnergyWh = 12345678900 Wh
0x08080010: 00 A0 C6 63 9A 01 00 00  // timestamp = 1721721600000 (2024年7月23日)
0x08080018: 9C 00 00 00    // powerOnCount = 156
0x0808001C: 9B 00 00 00    // powerOffCount = 155  
0x08080020: 00 00 AB 42    // instantPowerW = 85.5 (IEEE 754)
0x08080024: 66 66 9C 42    // avgPowerW = 78.2 (IEEE 754)
0x08080028: 10 0E 00 00    // uptimeSeconds = 3600 (1小时)
0x0808002C: 00 00 00 00... // reserved[32] = 全零

In the code, it is written.

/**
 * @brief 能耗数据结构
 * 定义存储在Flash中的能耗信息格式
 */
struct EnergyData {
    static constexpr uint32_t MAGIC_NUMBER = 0xE1E2E3E4;    ///< 魔术数字,用于验证数据完整性
    static constexpr uint16_t VERSION = 0x0001;             ///< 数据格式版本号
    
    uint32_t magic;                    ///< 魔术数字
    uint16_t version;                  ///< 版本号
    uint16_t checksum;                 ///< 校验和
    
    uint64_t totalEnergyWh;            ///< 总能耗 (瓦时)
    uint64_t lastUpdateTimestamp;      ///< 最后更新时间戳
    uint32_t powerOnCount;             ///< 开机次数
    uint32_t powerOffCount;            ///< 关机次数
    
    float instantPowerW;               ///< 瞬时功率 (瓦)
    float avgPowerW;                   ///< 平均功率 (瓦)
    uint32_t uptimeSeconds;            ///< 本次运行时长 (秒)
    
    uint8_t reserved[32];              ///< 预留字节,用于未来扩展
    
    /**
     * @brief 计算校验和
     * @return 计算得到的校验和
     */
    uint16_t calculateChecksum() const;
    
    /**
     * @brief 验证数据完整性
     * @return true 如果数据有效
     */
    bool isValid() const;
    
    /**
     * @brief 重置为默认值
     */
    void reset();
};

This proportion is really interesting and I will learn more. Data should be always reliable and stable. In the code, smart guys use magic number, version number and sum number to identify the energy consumption.

static constexpr uint32_t MAGIC_NUMBER = 0xE1E2E3E4;  // 预定义的特殊值

// 保存时写入魔术数字
data.magic = EnergyData::MAGIC_NUMBER;

// 读取时验证
if (data.magic != EnergyData::MAGIC_NUMBER) 
{
    // 数据无效或损坏
    return false;
}

And the usage is as follow.

void saveEnergyData() 
{
    EnergyData data;
    
    // 1. 设置魔术数字
    data.magic = EnergyData::MAGIC_NUMBER;
    
    // 2. 设置版本号
    data.version = EnergyData::VERSION;
    
    // 3. 填充实际数据
    data.totalEnergyWh = getCurrentTotalEnergy();
    data.powerOnCount = getPowerOnCount();
    // ... 其他字段
    
    // 4. 计算并设置校验和(必须最后计算)
    data.checksum = data.calculateChecksum();
    
    // 5. 保存到Flash
    flashStorage.saveData(data);
}
bool loadEnergyData(EnergyData& outData) 
{
    EnergyData data;
    
    // 1. 从Flash读取原始数据
    if (!flashStorage.readData(data)) {
        return false;
    }
    
    // 2. 验证魔术数字
    if (data.magic != EnergyData::MAGIC_NUMBER) {
        printf("错误:无效的魔术数字 0x%08X\n", data.magic);
        return false;
    }
    
    // 3. 检查版本兼容性
    if (data.version > EnergyData::VERSION) {
        printf("警告:数据版本过新 %d\n", data.version);
        return false;
    }
    
    // 4. 验证校验和
    uint16_t calculatedChecksum = data.calculateChecksum();
    if (data.checksum != calculatedChecksum) {
        printf("错误:校验和不匹配,期望:%04X 实际:%04X\n", 
               calculatedChecksum, data.checksum);
        return false;
    }
    
    // 5. 数据有效,返回结果
    outData = data;
    return true;
}
// 场景1:Flash损坏导致的数据错误
Flash原始数据: E4 E3 E2 E1 01 00 2B 1A F4 7E DF 02...
Flash损坏数据: E4 E3 E2 E1 01 00 2B 1A F4 7E 00 02...  // 一个字节损坏
检测结果: 校验和不匹配 ❌

// 场景2:软件版本不兼容
旧版本数据: magic=0xE1E2E3E4, version=0x0001
新版本软件: 支持version=0x0002
处理结果: 执行数据迁移 ✅

// 场景3:Flash未初始化
全FF数据: FF FF FF FF FF FF FF FF...
检测结果: 魔术数字验证失败 ❌

Then tell the program how to write the data into the flash.

/**
 * @brief Flash数据存储管理器
 * 核心类:负责能耗数据的持久化存储和恢复
 */
class FlashDataStorage {
public:
    static constexpr size_t FLASH_SECTOR_SIZE = 4096;       ///< Flash扇区大小
    static constexpr size_t DATA_SIZE = sizeof(EnergyData); ///< 数据大小
    static constexpr size_t BACKUP_COPIES = 3;              ///< 备份副本数量
    
    /**
     * @brief 构造函数
     * @param baseAddress Flash存储基地址
     */
    explicit FlashDataStorage(uint32_t baseAddress);
    
    /**
     * @brief 析构函数
     */
    ~FlashDataStorage() = default;
    
    /**
     * @brief 初始化Flash存储
     * @return true 如果初始化成功
     */
    bool initialize();
    
    /**
     * @brief 保存能耗数据到Flash
     * @param data 要保存的能耗数据
     * @return true 如果保存成功
     */
    bool saveData(const EnergyData& data);
    
    /**
     * @brief 从Flash读取能耗数据
     * @param data 输出参数,读取到的能耗数据
     * @return true 如果读取成功
     */
    bool loadData(EnergyData& data);
    
    /**
     * @brief 擦除Flash数据
     * @return true 如果擦除成功
     */
    bool eraseData();
    
    /**
     * @brief 获取存储状态
     * @return 存储器状态信息
     */
    struct StorageStatus {
        bool isInitialized;     ///< 是否已初始化
        uint32_t writeCount;    ///< 写入次数
        uint32_t errorCount;    ///< 错误次数
        bool isHealthy;         ///< 存储器健康状态
    };
    
    StorageStatus getStatus() const { return status_; }

private:
    uint32_t baseAddress_;              ///< Flash基地址
    mutable StorageStatus status_;      ///< 存储状态
    
    /**
     * @brief 写入Flash扇区
     * @param address 目标地址
     * @param data 数据指针
     * @param size 数据大小
     * @return true 如果写入成功
     */
    bool writeFlashSector(uint32_t address, const void* data, size_t size);
    
    /**
     * @brief 读取Flash扇区
     * @param address 源地址
     * @param data 数据缓冲区
     * @param size 数据大小
     * @return true 如果读取成功
     */
    bool readFlashSector(uint32_t address, void* data, size_t size);
    
    /**
     * @brief 擦除Flash扇区
     * @param address 扇区地址
     * @return true 如果擦除成功
     */
    bool eraseFlashSector(uint32_t address);
    
    /**
     * @brief 查找最新有效数据
     * @param data 输出参数
     * @return true 如果找到有效数据
     */
    bool findLatestValidData(EnergyData& data);
    
    /**
     * @brief 获取下一个写入地址
     * @return 下一个可用的写入地址
     */
    uint32_t getNextWriteAddress();
};

How to wrirte the data into flash is really a happy thing.  Read the specification and write the code.

#include "stm32f4xx_hal.h"  // 根据具体MCU选择

bool FlashDataStorage::writeDataToFlash(uint32_t address, const void* data, size_t size) {
    const uint8_t* byteData = static_cast<const uint8_t*>(data);
    uint32_t currentAddr = address;
    
    // HAL库写入方式
    for (size_t i = 0; i < size; i += 4) {  // 按4字节对齐写入
        uint32_t word = 0;
        
        // 组装4字节数据(处理不足4字节的情况)
        for (int j = 0; j < 4 && (i + j) < size; j++) {
            word |= (static_cast<uint32_t>(byteData[i + j]) << (j * 8));
        }
        
        // 使用HAL库写入Flash
        HAL_StatusTypeDef status = HAL_FLASH_Program(
            FLASH_TYPEPROGRAM_WORD,  // 写入类型:字
            currentAddr,             // 目标地址
            word                     // 数据
        );
        
        if (status != HAL_OK) {
            return false;
        }
        
        currentAddr += 4;
    }
    
    return true;
}

bool FlashDataStorage::unlockFlash() {
    return HAL_FLASH_Unlock() == HAL_OK;
}

bool FlashDataStorage::lockFlash() {
    return HAL_FLASH_Lock() == HAL_OK;
}

Then I should monitor the power situation. If the power on, do something.

/**
 * @brief 电源管理器
 * 处理电源状态变化和掉电检测
 */
class PowerManager {
public:
    enum class PowerState {
        NORMAL,         ///< 正常供电
        LOW_VOLTAGE,    ///< 低电压警告
        POWER_FAIL,     ///< 掉电检测
        SHUTDOWN        ///< 关机状态
    };
    
    using PowerCallback = std::function<void(PowerState)>;
    
    /**
     * @brief 构造函数
     */
    PowerManager();
    
    /**
     * @brief 初始化电源管理
     * @return true 如果初始化成功
     */
    bool initialize();
    
    /**
     * @brief 注册电源状态回调
     * @param callback 状态变化回调函数
     */
    void registerCallback(PowerCallback callback);
    
    /**
     * @brief 获取当前电源状态
     * @return 当前电源状态
     */
    PowerState getCurrentState() const { return currentState_; }
    
    /**
     * @brief 检查电源状态(周期性调用)
     */
    void checkPowerStatus();
    
    /**
     * @brief 准备关机
     * 执行关机前的数据保存操作
     */
    void prepareShutdown();

private:
    PowerState currentState_;           ///< 当前电源状态
    PowerCallback callback_;            ///< 状态变化回调
    uint32_t lastVoltageCheckTime_;     ///< 上次电压检测时间
    
    /**
     * @brief 读取电源电压
     * @return 电压值 (mV)
     */
    uint32_t readVoltage();
};

Then calculate the power comsumption.

/**
 * @brief 能耗监控器
 * 实时监控和计算能耗数据
 */
class EnergyMonitor {
public:
    /**
     * @brief 构造函数
     */
    EnergyMonitor();
    
    /**
     * @brief 初始化监控器
     * @return true 如果初始化成功
     */
    bool initialize();
    
    /**
     * @brief 开始监控
     */
    void startMonitoring();
    
    /**
     * @brief 停止监控
     */
    void stopMonitoring();
    
    /**
     * @brief 获取当前能耗数据
     * @return 当前能耗数据
     */
    const EnergyData& getCurrentData() const { return currentData_; }
    
    /**
     * @brief 更新能耗数据(周期性调用)
     */
    void updateEnergyData();
    
    /**
     * @brief 重置能耗统计
     */
    void resetEnergyData();

private:
    EnergyData currentData_;            ///< 当前能耗数据
    bool isMonitoring_;                 ///< 是否正在监控
    uint32_t startTime_;                ///< 开始监控时间
    uint32_t lastUpdateTime_;           ///< 上次更新时间
    
    /**
     * @brief 读取当前功率
     * @return 功率值 (瓦)
     */
    float readCurrentPower();
    
    /**
     * @brief 获取系统时间戳
     * @return 当前时间戳
     */
    uint64_t getTimestamp();
};

And the main program.

/**
 * @brief 主能耗管理器
 * 协调各个组件,提供统一的对外接口
 */
class MainsEnergyManager {
public:
    /**
     * @brief 构造函数
     * @param flashBaseAddr Flash存储基地址
     */
    explicit MainsEnergyManager(uint32_t flashBaseAddr = 0x08080000);
    
    /**
     * @brief 析构函数
     */
    ~MainsEnergyManager();
    
    /**
     * @brief 初始化能耗管理系统
     * @return true 如果初始化成功
     */
    bool initialize();
    
    /**
     * @brief 启动能耗监控
     * @return true 如果启动成功
     */
    bool start();
    
    /**
     * @brief 停止能耗监控
     */
    void stop();
    
    /**
     * @brief 获取当前能耗信息
     * @return 当前能耗数据
     */
    const EnergyData& getEnergyInfo() const;
    
    /**
     * @brief 手动保存数据
     * @return true 如果保存成功
     */
    bool saveDataNow();
    
    /**
     * @brief 重置能耗统计
     * @return true 如果重置成功
     */
    bool resetEnergyStats();
    
    /**
     * @brief 获取系统状态
     */
    struct SystemStatus {
        bool isRunning;                 ///< 系统是否运行中
        PowerManager::PowerState powerState;  ///< 电源状态
        FlashDataStorage::StorageStatus storageStatus;  ///< 存储状态
        uint32_t uptimeSeconds;         ///< 运行时长
    };
    
    SystemStatus getSystemStatus() const;

private:
    std::unique_ptr<FlashDataStorage> storage_;     ///< Flash存储管理器
    std::unique_ptr<PowerManager> powerManager_;    ///< 电源管理器
    std::unique_ptr<EnergyMonitor> energyMonitor_;  ///< 能耗监控器
    
    bool isInitialized_;                ///< 是否已初始化
    bool isRunning_;                    ///< 是否正在运行
    uint32_t autoSaveInterval_;         ///< 自动保存间隔(秒)
    uint32_t lastAutoSaveTime_;         ///< 上次自动保存时间
    
    /**
     * @brief 电源状态变化处理
     * @param state 新的电源状态
     */
    void onPowerStateChanged(PowerManager::PowerState state);
    
    /**
     * @brief 执行定期任务
     */
    void performPeriodicTasks();
    
    /**
     * @brief 紧急数据保存
     * 在掉电时快速保存关键数据
     */
    void emergencyDataSave();
};

This is the frame. The specific function can easily be found.

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值