STM32F1检测启动类型及BKP寄存器的使用
在 STM32F103 上使用 HAL 库进行上电启动检测时,建议将检测代码放在 main() 函数的刚开始处,必须位于所有外设初始化之前(或紧跟在 HAL_Init() 之后)。
这是因为 STM32F103 的 PINRSTF(引脚复位标志)在很多情况下会与 PORRSTF(上电复位标志)同时置位。因此,最稳妥的逻辑是排除法:优先排除软件复位和看门狗复位,如果仅有上电或引脚标志,则判定为冷启动(上电启动)。
一、 核心代码实现
直接将以下代码复制到你的 main.c 中:
/* 私有变量定义 */
uint8_t is_power_on = 0; // 0: 异常/软件/看门狗复位, 1: 纯上电/硬复位启动
int main(void)
{
/* 1. 初始化 HAL 库 */
HAL_Init();
/* 2. 检测复位源 (必须在配置时钟和清除标志前执行) */
if (__HAL_RCC_GET_FLAG(RCC_FLAG_SFTRST) != RESET)
{
// 软件复位 (例如调用了 NVIC_SystemReset)
is_power_on = 0;
}
else if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) != RESET || __HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST) != RESET)
{
// 看门狗复位 (独立看门狗或窗口看门狗超时)
is_power_on = 0;
}
else if (__HAL_RCC_GET_FLAG(RCC_FLAG_PORRST) != RESET)
{
// 纯正的上电复位 / 掉电复位
is_power_on = 1;
}
else if (__HAL_RCC_GET_FLAG(RCC_FLAG_PINRST) != RESET)
{
// 外部 NRST 引脚复位 (按了板子上的 RESET 键)
// 注意:上电时此标志也可能同时置 1,若排除了前面几种,这里通常也视为冷启动
is_power_on = 1;
}
else
{
is_power_on = 0;
}
/* 3. 必须立即清除复位标志,否则下次复位时旧标志还在,导致判断出错 */
__HAL_RCC_CLEAR_RESET_FLAGS();
/* 4. 后续系统时钟和外设初始化 */
SystemClock_Config();
MX_GPIO_Init();
// ... 其他外设初始化
/* 5. 根据判断结果执行不同的业务逻辑 */
if (is_power_on == 1)
{
// 执行上电特有的操作,比如历史数据清零、屏幕显示欢迎 LOGO 等
}
else
{
// 执行复位后的恢复操作,比如从备份寄存器读取之前的运行状态
}
while (1)
{
// 主循环
}
}
二、 开发注意事项
- 时钟配置前检测:有些工程师喜欢把检测放在 SystemClock_Config() 之后,虽然有时可行,但最安全的做法是尽早检测并清除,防止有些外设初始化时内部触发了微小的复位信号扰乱寄存器。
- 硬件调试器干扰:使用 ST-Link 或 J-Link 下载程序或在线仿真(Debug)时,调试器会向 NRST 引脚发送复位信号,这会强行触发 RCC_FLAG_PINRST。因此,测试上电逻辑时,请断开仿真器,彻底断电后重新上电观察。
如果在冷启动时,你需要保存某些数据不被清空(即便上电复位也能留存),我们可以利用 STM32F103 的 BKP(备份寄存器) 来实现。
在 STM32F103 中,BKP(备份寄存器) 是一块非常特殊的区域。即使单片机发生上电复位、看门狗复位或软件复位,只要后备电源(Vbat 引脚接了纽扣电池)不断电,BKP 里的数据就不会丢失。
结合上一步的“上电检测”逻辑,我们可以用 BKP 寄存器来实现:上电后判断数据是否有效,若有效则恢复运行;若是真正意义上的第一次掉电(Vbat 也断了),则重新初始化数据。
一、 核心实现步骤
要在 HAL 库中使用 BKP 寄存器,需要开启 PWR(电源控制) 和 BKP(备份时钟),并解除保护。
- 编写 BKP 读写函数
首先在 main.c 中添加以下两个简易的读写辅助函数(STM32F103 有 42 个 16 位的备份寄存器,这里我们使用第 1 个 BKP_DR1):
/**
* @brief 向BKP寄存器写入一个16位数据
* @param BKP_DR: 目标寄存器 (如 BKP_DR1, BKP_DR2 等)
* @param Data: 要写入的16位数据
*/
void BKP_Write(uint32_t BKP_DR, uint16_t Data)
{
__HAL_RCC_PWR_CLK_ENABLE(); // 开启PWR时钟
__HAL_RCC_BKP_CLK_ENABLE(); // 开启BKP时钟
HAL_PWR_EnableBkUpAccess(); // 取消备份区域写保护
HAL_RTCEx_BKUPWrite(&hrtc, BKP_DR, Data); // 写入数据 (需要引入RTC句柄)
HAL_PWR_DisableBkUpAccess(); // 恢复备份区域写保护
}
/**
* @brief 从BKP寄存器读取一个16位数据
*/
uint16_t BKP_Read(uint32_t BKP_DR)
{
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();
return (uint16_t)HAL_RTCEx_BKUPRead(&hrtc, BKP_DR);
}
注:hrtc 是 RTC(实时时钟)的句柄。如果你在 CubeMX 中没有开启 RTC,HAL_RTCEx_BKUPWrite 传入 NULL 或者声明一个临时的 RTC_HandleTypeDef hrtc; 即可,因为 BKP 的读写实际上只依赖 PWR 和 BKP 时钟。
二、 整合:上电检测 + 完整业务逻辑
我们在 main() 函数中将复位源检测与BKP标志位检查结合起来。这里我们定义一个标志暗号 0x5A5A:
/* 私有变量 */
uint8_t is_power_on = 0; // 0: 异常复位, 1: 正常冷启动
uint16_t saved_run_count = 0;// 模拟需要掉电保存的数据
int main(void)
{
HAL_Init();
/* 1. 检测复位源 */
if (__HAL_RCC_GET_FLAG(RCC_FLAG_SFTRST) != RESET ||
__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) != RESET ||
__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST) != RESET)
{
is_power_on = 0; // 软件或看门狗导致的异常复位
}
else
{
is_power_on = 1; // 纯上电或按了Reset键的冷启动
}
__HAL_RCC_CLEAR_RESET_FLAGS(); // 清除标志
/* 2. 时钟和外设初始化 */
SystemClock_Config();
MX_GPIO_Init();
// 如果用到了RTC,MX_RTC_Init() 也会在这里执行
/* 3. 核心 BKP 数据恢复与判断逻辑 */
// 读取 BKP_DR1 中的“数据有效标志暗号”
if (BKP_Read(RTC_BKP_DR1) == 0x5A5A)
{
/* 说明:Vbat电池有电,之前系统运行过并成功写入了数据 */
// 从 BKP_DR2 读取你需要保护的业务数据
saved_run_count = BKP_Read(RTC_BKP_DR2);
if (is_power_on == 1)
{
// 情况 A:正常的断电后重新上电,且电池有电,数据完好
// 可以在这里恢复设备状态
}
else
{
// 情况 B:看门狗复位或软件复位,数据完好
// 可以在这里做故障恢复逻辑
}
}
else
{
/* 说明:这是芯片这辈子“第一次”上电,或者 Vbat 纽扣电池没电/被拔掉了 */
// 1. 初始化你的业务数据
saved_run_count = 0;
// 2. 写入暗号和初始数据,防止下次上电再丢失
BKP_Write(RTC_BKP_DR1, 0x5A5A); // 写入有效标志
BKP_Write(RTC_BKP_DR2, saved_run_count); // 写入初始数据
}
/* --- 模拟后续的业务运行 --- */
while (1)
{
// 模拟每隔一段时间,业务数据发生改变,更新到 BKP 中
HAL_Delay(5000);
saved_run_count++;
// 数据变了,随时同步到 BKP 中,确保哪怕下一秒突然断电数据也在
BKP_Write(RTC_BKP_DR2, saved_run_count);
}
}
三、 避坑指南
- Vbat 引脚不可悬空:如果你想测试“完全断电后数据不丢失”,STM32F103 的 VBAT 引脚(Pin 1)必须接一颗 3V 纽扣电池。如果悬空或接地,一旦主电源 VCC 断开,BKP 里的数据瞬间清空。
- 容量限制:STM32F103 的 BKP 空间很小。
• 小容量/中容量(如 C8T6, RCT6):有 10 个 16位寄存器(BKP_DR1 到 BKP_DR10)。
• 大容量(如 ZET6, VET6):有 42 个 16位寄存器(BKP_DR1 到 BKP_DR42)。