即拿即用:STM32后备寄存器(BKP)的另类用法(无需开启RTC)

【关键词】 STM32、后备寄存器、BKP、复位、状态保持、无需RTC、看门狗

打破一个误解

很多开发者以为,要使用STM32的后备寄存器,必须先开启RTC(实时时钟)。

其实不然。

F1系列拥有20个16位后备寄存器,F4系列则有32个32位后备寄存器。它们虽然属于RTC后备域,但完全可以独立于RTC单独使用。

今天分享一个我在实际项目中使用的技巧:用后备寄存器记录设备状态,让意外重启变成“无感恢复”。

一个真实的应用案例

我设计的一款PID电加热控制板,主要有三种工作状态:

  • 正在自整定

  • 正在加热

  • 待机

控制板通过固态继电器驱动负载。但麻烦的是,加热或整定过程中,固态继电器的过零干扰偶尔会导致死机,触发看门狗复位。复位之后所有状态丢失,整个工艺流程就断了。

解决办法很简单:在每一个状态切换的节点,把当前状态写入后备寄存器。系统重启时,程序入口处读取后备寄存器,恢复到重启前的状态继续跑。外部完全察觉不到控制器曾发生过复位,生产节奏丝毫不受影响。

实现逻辑

  • 开始PID自整定时,将“自整定”标记写入后备寄存器,整定完成时清除。

  • 开始加热时,将“加热”标记写入后备寄存器,停止加热时清除。

就这么简单。即使看门狗超时引发了复位,程序启动后先读一下后备寄存器,是什么标记就干什么事,逻辑清晰又可靠。

代码实战

后备寄存器的操作并不复杂,也无需开启RTC。以下是可直接复制使用的读写函数:

// 写后备寄存器
static void BKP_Write(volatile uint32_t *reg, uint32_t val)
{
    __HAL_RCC_PWR_CLK_ENABLE();     // 使能电源接口时钟
    HAL_PWR_EnableBkUpAccess();     // 允许访问后备域
    *reg = val;
    HAL_PWR_DisableBkUpAccess();    // 禁止访问后备域
}

// 读后备寄存器
static uint32_t BKP_Read(volatile uint32_t *reg)
{
    __HAL_RCC_PWR_CLK_ENABLE();
    HAL_PWR_EnableBkUpAccess();
    uint32_t dat = *reg;
    HAL_PWR_DisableBkUpAccess();
    return dat;
}

验证一下写入是否可靠。向4个寄存器写入测试值,然后软件复位:

void SaveStateToBKP(void)
{
    BKP_Write(&RTC->BKP1R, 0x11223344);
    BKP_Write(&RTC->BKP2R, 0x33445566);
    BKP_Write(&RTC->BKP3R, 0x778899AA);
    BKP_Write(&RTC->BKP4R, 0xAABBCCDD);
    HAL_NVIC_SystemReset();   // 软件复位,验证写入的数据不丢失
}

复位后读出来验证:

uint32_t DataFromBKP1;
uint32_t DataFromBKP2;
uint32_t DataFromBKP3;
uint32_t DataFromBKP4;

void ReadDataFromBKP(void)
{
    DataFromBKP1 = BKP_Read(&RTC->BKP1R);
    DataFromBKP2 = BKP_Read(&RTC->BKP2R);
    DataFromBKP3 = BKP_Read(&RTC->BKP3R);
    DataFromBKP4 = BKP_Read(&RTC->BKP4R);
}

一个关键技巧:魔数校验

写入状态标记时, 务必额外写入一个校验用的“魔数”

上电时后备寄存器里的内容可能是随机值。必须用一个魔数来确认:这确实是我上次亲手写进去的数据。

这个魔数可以是约定好的特殊值,比如 0xAABBCCDD,也可以是前面数据的CRC校验值。

重要原则:魔数必须在所有有效数据写完之后再写。

如果先写魔数,万一后续数据写到一半时发生复位,就会出现“魔数对但数据不全”的情况,系统可能做出错误判断。

代码示例如下:

// 写入状态和魔数(魔数最后写)
void SaveStateWithMagic(void)
{
    BKP_Write(&RTC->BKP1R, STATE_TUNING);     // 先写状态标记
    BKP_Write(&RTC->BKP2R, 0xAABBCCDD);       // 最后写魔数
}

// 读取时先校验魔数
uint8_t ReadStateWithMagic(void)
{
    uint32_t magic = BKP_Read(&RTC->BKP2R);
    if (magic == 0xAABBCCDD)
    {
        return (uint8_t)BKP_Read(&RTC->BKP1R); // 魔数正确,数据可信
    }
    return STATE_UNKNOWN; // 魔数错误,数据不可信
}

额外提醒

后备寄存器的数据由 VBAT引脚 供电维持。

如果VBAT端没有接纽扣电池或其他备用电源,主电源一断,寄存器数据就丢了。

因此,对于需要经历断电的工况,建议给VBAT接上电池或超级电容,才能真正发挥后备寄存器的最大价值。如果只有上电复位没有断电的场景,则无需额外处理。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐