简介:这份资源面向使用STM32F030系列进行低功耗开发的嵌入式工程师与学习者,聚焦RTC定时5分钟后从待机模式唤醒这一典型场景,帮助解决电池供电设备中长时间休眠与精准定时唤醒的功耗控制问题。压缩包共548个文件,约3.83MB,以C源码与头文件为核心,包含RTC、TIM、USART、RCC等外设驱动,并配有Keil工程文件、编译产物、SVD与XSD描述文件,以及若干网页与图片素材,便于直接打开工程验证与移植。目前已有4184人学习下载,说明该方案在低功耗应用中有一定参考价值。资源完整呈现了RTC时钟源初始化、闹钟配置、唤醒事件设置、待机模式进入及PA12、PB3引脚状态控制等关键环节,读者可据此掌握STM32F030C8T的RTC唤醒流程,并在此基础上优化唤醒延时与整体功耗,适用于物联网节点、传感器终端等便携设备开发。
1. 从一块纽扣电池撑过整个季度说起:STM32F030 的 RTC 唤醒到底怎么落地
手头有个环境监测的小板子,主控是 STM32F030F4P6,两节五号电池供电,要求每 5 分钟采一次温湿度、通过串口把数据丢出去,其余时间必须睡死。第一次打样回来,实测待机电流 1.8mA,电池撑不过三天,项目直接卡在功耗这一关。后来把主频降下来、外设全关、把唤醒源从 SysTick 换成 RTC 闹钟,整机待机电流压到 6μA 上下,才算把这块板子救回来。这篇笔记就把这套「STM32F030 + RTC 定时 5 分钟唤醒 + 低功耗」的完整实现拆开讲清楚,从时钟树配置、RTC 闹钟寄存器、Stop 模式进出,到唤醒后怎么恢复现场、怎么测电流,全部落到能直接抄的代码。适合正在做电池供电节点、低功耗采集终端,或者被待机电流折磨过的嵌入式开发者。
2. 低功耗方案选型:为什么是 RTC 闹钟而不是 SysTick
2.1 STM32F030 的几种低功耗模式对比
STM32F030 属于 Cortex-M0 内核,低功耗模式主要有 Sleep、Stop、Standby 三档。Sleep 模式只是把内核时钟停掉,外设还在跑,电流通常在 mA 级别,对电池供电场景基本没意义。Standby 模式最省,能到 1μA 以下,但 SRAM 和寄存器内容全部丢失,唤醒相当于复位,每次醒来都要重新初始化,对于需要保持采集状态、累计计数的场景不太友好。Stop 模式是折中方案:内核时钟停掉、大部分外设时钟关闭,但 SRAM 和寄存器内容保留,唤醒后从停机点继续执行,典型电流在几 μA 到十几 μA 之间,正好卡在这个项目的需求上。
选 Stop 模式之后,唤醒源就成了关键。可用的唤醒源有 EXTI 外部中断、RTC 闹钟、独立看门狗等。EXTI 需要外部信号触发,不适合定时;IWDG 只能复位不能唤醒;RTC 闹钟是唯一能提供精确长周期定时、又能在 Stop 模式下工作的片上外设。5 分钟这个周期,用 RTC 的闹钟功能最合适,精度靠 LSE 晶振保证,误差一天也就几秒。
2.2 时钟树配置:LSE 给 RTC 供时钟
RTC 要跑得准,时钟源必须选 LSE(外部 32.768kHz 晶振),不能用 LSI。LSI 是内部 RC,温漂大,一天能差出几分钟,5 分钟周期累积下来误差很可观。配置顺序是先开 LSE,等它稳定,再把 RTC 时钟源切到 LSE,最后配置 RTC 分频。下面这段是标准库风格的初始化代码:
/* 使能 LSE 并等待稳定 */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); PWR_BackupAccessCmd(ENABLE); /* 解锁备份域,否则 RTC 寄存器写不进去 */ RCC_LSEConfig(RCC_LSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); /* 等 LSE 起振,实际项目要加超时 */ /* 把 RTC 时钟源切到 LSE */ RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); RCC_RTCCLKCmd(ENABLE); RTC_WaitForSynchro(); /* 等 RTC 寄存器与 APB 同步 */这里有个容易翻车的点:PWR_BackupAccessCmd(ENABLE)必须在写 RTC 之前调用,否则所有 RTC 寄存器写入都会被硬件忽略,现象就是闹钟死活不触发。另外while等 LSE 稳定一定要加超时计数,晶振没焊好或者负载电容不对时,程序会死在这里,现场排查很痛苦。
2.3 RTC 闹钟周期计算:5 分钟怎么算出来
RTC 的分频结构是两级:异步分频器RTC_AsynchPrediv和同步分频器RTC_SynchPrediv。LSE 是 32768Hz,要得到 1Hz 的计数时钟,两级分频乘积得等于 32768。常见配置是异步 127、同步 255,乘积 127×255=32385,接近但不等于 32768,会有微小误差。更精确的做法是异步 127、同步 256,乘积 32512,还是差一点。要完全精确,异步取 127、同步取 258 不行,同步分频器最大 255。实际工程里常用异步 127、同步 255,误差约 1.2%,5 分钟差 3.6 秒,对温湿度采集够用;如果要求更严,就得用异步 128、同步 256,但异步分频器范围是 1~127,128 超了。所以精确到 1Hz 在 F030 上做不到,只能接受这个误差,或者用 RTC 的亚秒寄存器做补偿。
闹钟值直接按秒算:5 分钟 = 300 秒。把当前 RTC 计数值读出来,加上 300,写进闹钟寄存器。注意跨天、跨月的情况,简单做法是只比较秒和分,不比较小时和日期,这样每 5 分钟触发一次,不管当前是几点。
/* 设置 RTC 分频,得到约 1Hz 计数 */ RTC_InitTypeDef RTC_InitStruct; RTC_InitStruct.RTC_AsynchPrediv = 127; RTC_InitStruct.RTC_SynchPrediv = 255; RTC_Init(&RTC_InitStruct); /* 读当前时间,加 300 秒作为闹钟 */ uint32_t now = RTC_GetCounter(); uint32_t alarm = now + 300; RTC_AlarmTypeDef alarmStruct; alarmStruct.RTC_AlarmTime.RTC_Seconds = alarm % 60; alarmStruct.RTC_AlarmTime.RTC_Minutes = (alarm / 60) % 60; alarmStruct.RTC_AlarmTime.RTC_Hours = (alarm / 3600) % 24; alarmStruct.RTC_AlarmMask = RTC_AlarmMask_DateWeekDay; /* 不比较日期 */ alarmStruct.RTC_AlarmDateWeekDaySel = RTC_AlarmDateWeekDaySel_Date; alarmStruct.RTC_AlarmDateWeekDay = 1; RTC_SetAlarm(RTC_Alarm_A, &alarmStruct); RTC_ITConfig(RTC_IT_ALRA, ENABLE); /* 开闹钟中断 */RTC_AlarmMask_DateWeekDay这个掩码很关键,它让闹钟只比较时分秒,忽略日期,否则每天只会触发一次。参数RTC_Alarm_A表示用闹钟 A,F030 有两个闹钟通道,A 和 B 可以独立用。
3. Stop 模式进出与唤醒现场恢复:一个采集周期的完整链路
3.1 进入 Stop 模式前的收尾动作
进 Stop 之前要把所有能关的外设关掉,否则漏电流会吃掉低功耗收益。GPIO 要配置成模拟输入或者按需上拉,悬空的输入引脚会因为电平抖动产生额外功耗。串口、ADC、定时器这些外设时钟全部关掉。如果板子上有 LED,记得灭掉,一个 2mA 的 LED 能让整机功耗翻几百倍。
void enter_stop_mode(void) { /* 关闭外设时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, DISABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, DISABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, DISABLE); /* GPIO 全部设为模拟输入,避免悬空漏电 */ GPIO_InitTypeDef gpio; gpio.GPIO_Mode = GPIO_Mode_AN; gpio.GPIO_Pin = GPIO_Pin_All; GPIO_Init(GPIOA, &gpio); GPIO_Init(GPIOB, &gpio); /* 清唤醒标志,准备进 Stop */ PWR_ClearFlag(PWR_FLAG_WU); PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); }PWR_Regulator_LowPower让内部稳压器进低功耗态,比正常模式省几 μA。PWR_STOPEntry_WFI是等中断唤醒,配合 RTC 闹钟中断正好。注意进 Stop 前一定要清PWR_FLAG_WU,否则可能一进去就被旧标志唤醒,现象是电流没降下去或者程序跑飞。
3.2 唤醒后的时钟恢复:HSI 还是 HSE
Stop 模式唤醒后,系统时钟默认切回 HSI(内部 8MHz RC),原来配的 HSE PLL 全部失效。如果应用依赖精确的主频(比如串口波特率),必须在唤醒后重新配置时钟树。这个项目串口只是发数据,对波特率误差容忍度高,直接用 HSI 跑 8MHz 也能通信,省掉重新配 PLL 的时间。但如果用 HSE 做系统时钟,唤醒后第一件事就是重新RCC_HSEConfig和RCC_PLLConfig,否则串口会出乱码。
void wakeup_reinit(void) { /* 唤醒后时钟是 HSI,重新配回 HSE+PLL */ RCC_HSEConfig(RCC_HSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET); RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_6); /* 8M×6=48M */ RCC_PLLCmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while (RCC_GetSYSCLKSource() != 0x08); }这段代码里RCC_PLLMul_6是假设 HSE 是 8MHz,实际板子如果焊的是 12MHz 晶振,倍频系数要改成 4,否则主频超了会不稳定。唤醒后重新配时钟的顺序不能乱:先开 HSE、等稳定,再配 PLL、等锁定,最后切系统时钟源。
3.3 中断服务函数里该做什么、不该做什么
RTC 闹钟中断服务函数里只做最轻量的动作:清中断标志、置一个全局标志位,然后立刻退出。所有耗时操作——读传感器、算数据、发串口——都放到主循环里根据标志位执行。中断里如果调用printf或者延时函数,会拖长中断时间,影响下一次唤醒的准时性,严重时还会因为中断嵌套导致栈溢出。
volatile uint8_t g_alarm_flag = 0; void RTC_IRQHandler(void) { if (RTC_GetITStatus(RTC_IT_ALRA) != RESET) { RTC_ClearITPendingBit(RTC_IT_ALRA); EXTI_ClearITPendingBit(EXTI_Line17); /* RTC 闹钟走 EXTI17 */ g_alarm_flag = 1; } }EXTI_Line17是 RTC 闹钟专用的外部中断线,很多人在 NVIC 里配了 RTC_IRQn 却忘了清 EXTI 挂起位,结果中断反复触发,现象是程序一直在中断里出不来。这个坑我踩过,查了半天才发现是 EXTI 标志没清。
3.4 主循环的状态机写法
主循环用状态机组织,每次唤醒只跑一个采集周期,跑完立刻回去睡。这样代码结构清晰,也方便后续加功能。
int main(void) { system_init(); rtc_alarm_init(); while (1) { if (g_alarm_flag) { g_alarm_flag = 0; wakeup_reinit(); /* 恢复时钟 */ sensor_power_on(); /* 给传感器上电 */ delay_ms(10); /* 等传感器稳定 */ read_sensor(); /* 读数据 */ send_uart(); /* 发出去 */ sensor_power_off(); /* 传感器断电 */ set_next_alarm(300); /* 设下一次闹钟 */ enter_stop_mode(); /* 回去睡 */ } } }sensor_power_on和sensor_power_off是通过一个 GPIO 控制传感器的供电,不用的时候彻底断电,比让传感器进休眠模式更省。set_next_alarm每次重新算闹钟值,避免固定周期累积误差。
4. 避坑与排查:待机电流下不去、闹钟不触发怎么办
4.1 待机电流比手册高一个数量级
现象:进 Stop 模式后实测电流 200μA,手册标称几 μA。原因通常是 GPIO 没处理干净,某个引脚还配着推挽输出且输出高电平,驱动着外部电路;或者调试器还连着,SWD 引脚在调试模式下有额外功耗。解决:把所有 GPIO 设成模拟输入,拔掉调试器再测,如果必须在线调试,把 SWD 引脚配成普通 GPIO 并关闭调试时钟。
4.2 RTC 闹钟到点不触发
现象:程序进了 Stop 模式,但永远不醒。原因有几个:一是PWR_BackupAccessCmd没开,闹钟寄存器根本没写进去;二是 NVIC 里没使能RTC_IRQn;三是 EXTI17 的中断没配。解决:按顺序检查备份域解锁、RTC 中断使能、NVIC 使能、EXTI17 配置这四步,缺一不可。用调试器读 RTC_ALR 寄存器,看写入的值对不对,能快速定位是配置问题还是硬件问题。
4.3 唤醒后串口输出乱码
现象:唤醒后串口打印全是乱码。原因是 Stop 唤醒后系统时钟切回 HSI,而串口初始化时是按 HSE 主频算的波特率,时钟变了波特率就偏了。解决:要么唤醒后重新配 HSE 和 PLL,要么串口初始化时用 HSI 频率算波特率,保证唤醒前后一致。我一般选后者,省掉重新锁 PLL 的几百微秒。
4.4 LSE 起振失败导致程序卡死
现象:板子上电后程序不跑,仿真发现卡在等 LSE 稳定的 while 循环里。原因是晶振负载电容不匹配,或者晶振离芯片太远、走线太长。解决:负载电容按晶振规格书选,通常 12.5pF 配 6pF 左右的外部电容;PCB 上晶振尽量靠近芯片,走线包地。软件上加超时,超时后切到 LSI 降级运行,至少保证功能可用。
4.5 闹钟周期越跑越偏
现象:连续运行几小时后,唤醒时刻越来越晚。原因是 RTC 分频不是精确 1Hz,误差累积。解决:每次唤醒后重新读 RTC 计数值再算下一次闹钟,而不是固定加 300,这样误差不会累积。如果对精度要求高,可以用 RTC 的亚秒寄存器做微调,或者定期用外部时间源校准。
5. 二次开发改造:把 5 分钟周期改成可配置、加数据缓存
5.1 把唤醒周期做成可配置参数
原始代码里 300 秒是写死的,实际项目经常需要根据场景调整,比如夜间拉长到 30 分钟省电。改造思路是把周期存到 Flash 或者备份寄存器里,上电读出来,唤醒时按这个值算闹钟。备份寄存器在 Stop 模式下不掉电,适合存这种小配置。
/* 周期存到备份寄存器 BKP_DR1,单位秒 */ #define PERIOD_REG BKP_DR1 uint32_t get_period(void) { uint32_t p = BKP_ReadBackupRegister(PERIOD_REG); return (p == 0 || p > 3600) ? 300 : p; /* 默认 300 秒,上限 1 小时 */ } void set_period(uint32_t sec) { BKP_WriteBackupRegister(PERIOD_REG, sec); }BKP_ReadBackupRegister读出来是 16 位值,最大 65535,够存秒数。加个范围校验,防止 Flash 里的脏数据导致闹钟设成离谱的值。改周期的时候通过串口命令写进去,下次唤醒就生效,不用重新烧程序。
5.2 加一个环形缓冲区缓存采集数据
如果通信链路不稳定,唤醒后发数据可能失败,数据就丢了。加一个环形缓冲区,发送失败的数据先存着,下次唤醒一起发。缓冲区放 SRAM 里,Stop 模式不丢数据。
#define BUF_SIZE 32 typedef struct { uint16_t temp; uint16_t humi; uint32_t timestamp; } sample_t; static sample_t ring_buf[BUF_SIZE]; static uint8_t head = 0, tail = 0; void buffer_push(sample_t *s) { uint8_t next = (head + 1) % BUF_SIZE; if (next == tail) { /* 满了,丢最老的 */ tail = (tail + 1) % BUF_SIZE; } ring_buf[head] = *s; head = next; } uint8_t buffer_pop(sample_t *s) { if (head == tail) return 0; /* 空 */ *s = ring_buf[tail]; tail = (tail + 1) % BUF_SIZE; return 1; }head和tail用uint8_t,配合取模运算实现环形。缓冲区大小 32 条,按 5 分钟一条算,能缓存接近 3 小时的数据,足够应对短时通信中断。发送时循环buffer_pop直到空,发一条删一条,保证不丢也不重复。
5.3 验证改造效果的方法
改完之后怎么确认没引入新问题?我的习惯是分三步验证。第一步,用电流表串在电池和板子之间,看进 Stop 后的电流有没有变化,改造不应该让静态电流升高。第二步,用示波器抓传感器供电引脚,看每次唤醒的时序对不对,周期是不是 300 秒。第三步,连续跑 24 小时,把串口数据存下来,看时间戳间隔是否均匀、有没有丢数据。这三步走完,基本能确认改造是稳的。
从那以后我每次做低功耗项目,都会先把 GPIO 状态和时钟树在纸上画一遍,再动手写代码,因为这两个地方出的问题最隐蔽,查起来最费时间。希望这篇笔记能帮到正在跟待机电流较劲的你。
本文还有配套的精品资源,点击获取