资讯动态

STM32F030低功耗实战:RTC定时唤醒与Stop模式

发布时间:2026/10/21 8:05:48 来源:尧图企业网站定制
简介这份资源面向使用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 闹钟而不是 SysTick2.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×25532385接近但不等于 32768会有微小误差。更精确的做法是异步 127、同步 256乘积 32512还是差一点。要完全精确异步取 127、同步取 258 不行同步分频器最大 255。实际工程里常用异步 127、同步 255误差约 1.2%5 分钟差 3.6 秒对温湿度采集够用如果要求更严就得用异步 128、同步 256但异步分频器范围是 1~127128 超了。所以精确到 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表示用闹钟 AF030 有两个闹钟通道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 还是 HSEStop 模式唤醒后系统时钟默认切回 HSI内部 8MHz RC原来配的 HSE PLL 全部失效。如果应用依赖精确的主频比如串口波特率必须在唤醒后重新配置时钟树。这个项目串口只是发数据对波特率误差容忍度高直接用 HSI 跑 8MHz 也能通信省掉重新配 PLL 的时间。但如果用 HSE 做系统时钟唤醒后第一件事就是重新RCC_HSEConfig和RCC_PLLConfig否则串口会出乱码。void wakeup_reinit(void) { /* 唤醒后时钟是 HSI重新配回 HSEPLL */ RCC_HSEConfig(RCC_HSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) RESET); RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_6); /* 8M×648M */ 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 状态和时钟树在纸上画一遍再动手写代码因为这两个地方出的问题最隐蔽查起来最费时间。希望这篇笔记能帮到正在跟待机电流较劲的你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑