news 2026/9/8 12:50:49

STM32F103 Stop模式低功耗实战:从原理到例程的完整避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F103 Stop模式低功耗实战:从原理到例程的完整避坑指南

简介:STM32F103停止模式低功耗测试例程,基于金龙107开发板,面向嵌入式开发者及低功耗产品设计者。例程演示通过电源控制寄存器配置进入停止模式,并用外部中断或定时器事件唤醒,涵盖时钟配置、外设时钟关闭、IO口低功耗处理及唤醒后系统恢复等完整流程,保留静态存储器和寄存器内容以便快速恢复。压缩包共143个文件,大小2.44MB,以C语言源码和头文件为主,搭配工程文件、可执行文件及映射文件等调试信息,可直接编译烧录并按需修改。已有1254人学习该资源。代码内包含循环进出停止模式的测试逻辑,便于测量电流消耗和验证唤醒可靠性,同时提供调试打印接口,可跟踪模式切换过程。整体模块划分清晰,初始化、低功耗处理、唤醒服务相互独立,易于移植到电池供电等功耗敏感场景,帮助开发者快速掌握低功耗设计要点,也可作为学习STM32电源管理的实用参考。 最近在做一款电池供电的低功耗产品,stm32f103的stop模式测试例程来回折腾了不少时间。stop模式算是实际项目里用得最多的一种低功耗模式——CPU停转、全部时钟关断,但SRAM和寄存器内容还在,功耗能从正常运行时的几十毫安直接掉到微安级别,对依赖电池供电的设备来说这是最关键的设计手段之一。

如果你正在做低功耗仪表、便携设备或者需要“休眠-唤醒”工作流的项目,这篇例程实测总结会帮你少走不少弯路。我会从stop模式的基本原理讲起,给出完整的测试代码和实测数据,再把我踩过的坑全部摊开来说,包括唤醒后时钟恢复、EXTI电平选择、调试器对电流测量的干扰这些平时文档里不会写的细节。

1. stop模式在低功耗设计里的定位

1.1 为什么是stop模式,而不是睡眠或待机

先用一张表把STM32F103的三种低功耗模式看清楚,选型的时候就不会纠结。

模式时钟状态SRAM/寄存器典型功耗唤醒源适用场景
睡眠CPU停止,外设时钟照常保留毫安级任意中断短时等待、需要外设继续工作
停止所有时钟停止,内部1.8V区域供电保留微安级EXTI、RTC、USB等需要快速唤醒、保持现场数据
待机全部断电全部丢失亚微安级WKUP引脚、RTC超低功耗、允许重新上电初始化

睡眠模式对省电来说基本是“洒洒水”,因为外设还在跑,电流根本压不下去。待机模式虽然最省电,但SRAM内容全没,唤醒后等于冷启动,如果设备需要保留运行状态(比如计数值、通信配置、传感器校准参数),待机就直接不适用了。

stop模式正好卡在中间:电流能到微安级,RAM和寄存器数据还在,唤醒时间也快。设备从stop模式恢复后,能继续执行进入stop之前的代码,不需要重新走一遍初始化逻辑,这在很多需要“接着干”的场景里太方便了。这也是我这次测试例程选择stop模式的核心原因。

1.2 stop模式内部到底发生了什么

官方参考手册里对stop模式的描述很简单:所有时钟都被停止,包括HSE、HSI和PLL,但是SRAM、寄存器内容和1.8V区域的模拟部分保持供电。这里有个常被忽略的点——PWR_EnterSTOPMode函数参数里可以选择电压调节器的工作模式。

  • PWR_Regulator_ON:电压调节器保持正常模式,唤醒速度快,但静态电流稍微高一点。
  • PWR_Regulator_LowPower:电压调节器进入低功耗模式,电流更低,但唤醒后需要一段稳定时间,时钟恢复稍慢。

我的测试例程里用的是LowPower模式,因为目标是压功耗。如果设备对唤醒后的响应速度非常敏感,比如要在微秒级内处理紧急事件,我建议用ON模式实测对比一下,差距通常在几十微秒之内,但电流差异可能有好几微安。

另外一个关键点是,stop模式唤醒后系统时钟不会自动恢复到进入前的状态。因为HSE和PLL都停了,唤醒后如果不重新配置时钟,MCU会一直跑在HSI 8MHz下,波特率、定时器周期全都错乱,这也是后面要着重处理的问题。

2. 测试前的软硬件准备

2.1 开发板与最小系统设计要点

我用的是一块很常见的STM32F103C8T6最小系统板,蓝色那种。板上的晶振是8MHz,复位电路、boot跳线都有了,做低功耗测试不需要额外改造。如果你是自己画的板子,有几点必须注意。

首先是VDD和VDDA的滤波电容,一定要靠近芯片引脚放,否则进入stop模式后电源纹波可能造成异常唤醒或者电流偏高。其次是NRST引脚不要悬空,最近测试发现NRST受干扰时会导致MCU随机复位,低功耗模式下这个问题会被放大,因为系统对复位更敏感。最后是BOOT0引脚,必须用10kΩ左右电阻下拉到GND,不能直接悬空。

测量工具方面,我建议准备一个能测微安级的万用表,我在用的是一块四位半的台式万用表,串联到电源回路里测电流。如果没有高精度表,也可以在回路里串一个1Ω的精密采样电阻,用示波器或万用表mV档测压降,换算成电流,这个方法在观察睡眠-唤醒的瞬态波形时尤其好用。

2.2 开发环境与标准外设库配置

这版例程基于标准外设库V3.5(STM32F10x_StdPeriph_Lib),IDE用的是Keil MDK 5。虽然现在ST主推HAL库和STM32CubeMX,但F103的标准库资料最多、参考代码最好找,而且低功耗寄存器操作逻辑一目了然,做这种底层功耗测试反而更直观。

新建工程的时候记得在预定义符号里加上STM32F10X_MD(中等容量)和USE_STDPERIPH_DRIVER,不然编译会报错。文件结构上我习惯把低功耗相关的代码单独放到一个lowpower.c里,主程序留一个入口就行,这样后续移植其他项目也比较干净。

工程里需要引入几个核心外设库文件:misc.c(NVIC配置)、stm32f10x_rcc.c(时钟管理)、stm32f10x_gpio.c(引脚控制)、stm32f10x_usart.c(串口调试)、stm32f10x_exti.c(外部中断唤醒)、stm32f10x_pwr.c(低功耗模式函数)。STOP模式本身只需要PWR和RCC的支持,但测试例程里为了实现完整的“按键进入-中断唤醒-串口打印状态”链路,这几个外设库都跑不了。

2.3 测试例程的功能设计

这个例程想验证三件事:能不能正常进入stop模式、功耗是否达到预期、外部中断能不能可靠唤醒。所以整个流程设计成:

  1. 上电后LED点亮,串口打印“System Started”。
  2. 按一次按键,LED熄灭,系统进入stop模式。
  3. 再按同一个按键,EXTI触发唤醒,LED重新点亮,串口打印“Wakeup from STOP Mode”。
  4. 循环执行,每次进入前重新初始化时钟和外设,验证唤醒后系统工作的稳定性。

硬件连接上,按键接PA0(对应EXTI0),LED接PB12,串口1的TX/RX接到USB转串口工具。PA0默认要接上拉电阻到3.3V,按键另一端接地,这样按下时引脚拉低,产生下降沿触发的EXTI中断。

3. 测试例程的完整实现

3.1 时钟与GPIO初始化

进入stop模式之前的所有外设初始化,和普通程序是一样的。但有一个细节需要注意:在进入stop前,要把不用的外设时钟全部关闭,否则这些外设会继续消耗电流,导致实测功耗偏高。

void RCC_Configuration(void) { ErrorStatus HSEStartUpStatus; RCC_DeInit(); RCC_HSEConfig(RCC_HSE_ON); HSEStartUpStatus = RCC_WaitForHSEStartUp(); if (HSEStartUpStatus == SUCCESS) { RCC_HCLKConfig(RCC_SYSCLK_Div1); RCC_PCLK1Config(RCC_HCLK_Div2); RCC_PCLK2Config(RCC_HCLK_Div1); FLASH_SetLatency(FLASH_Latency_2); FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); RCC_PLLCmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) {} RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while (RCC_GetSYSCLKSource() != 0x08) {} } RCC_APB2PeriphClockCmd(LED_GPIO_CLK | KEY_GPIO_CLK | RCC_APB2Periph_AFIO | RCC_APB2Periph_USART1, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); }

GPIO初始化时要重点处理按键引脚。PA0作为EXTI输入,需要配置成上拉输入,同时打开AFIO时钟并复用EXTI映射。LED引脚直接配置成推挽输出就行。

void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = LED_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(LED_GPIO_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = KEY_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(KEY_GPIO_PORT, &GPIO_InitStructure); }

注意:配置EXIT之前,必须调用GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0),不然外部中断不会触发。这个函数依赖AFIO时钟,所以RCC配置时别漏了RCC_APB2Periph_AFIO

3.2 EXTI唤醒源配置

外部中断是这个例程的唤醒触发器。EXTI0对应的中断通道是EXTI0_IRQn,中断服务函数是EXTI0_IRQHandler。配置时要把优先级分组、占先优先级、子优先级都设置好,同时保证中断是在进入stop模式之前就配置完成的。

void EXTI_Configuration(void) { EXTI_InitTypeDef EXTI_InitStructure; GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); EXTI_InitStructure.EXTI_Line = EXTI_Line0; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); EXTI_ClearITPendingBit(EXTI_Line0); } void NVIC_Configuration(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) != RESET) { EXTI_ClearITPendingBit(EXTI_Line0); } }

关于下降沿还是上升沿触发,这是一个小考点。如果按键默认接上拉到高、按下接地,进stop前按键是松开状态,按下时产生下降沿,所以用Falling。如果换成了低电平触发模式,会在按下期间一直触发中断,可能导致系统反复唤醒,所以实际项目里我喜欢用边沿触发,一个沿只唤醒一次,后面在软件里再做消抖处理。

3.3 进入stop模式与唤醒后的恢复流程

核心操作就一句PWR_EnterSTOPMode,但前后要做的准备工作直接影响整个测试的成败。

void Enter_StopTest(void) { printf("Entering STOP Mode...\r\n"); Delay(10); // 进入stop前关闭LED,避免额外电流消耗 GPIO_ResetBits(LED_GPIO_PORT, LED_GPIO_PIN); // 进入stop模式 PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); // 唤醒后从这里继续执行 // 关键:先把系统时钟恢复过来,再做外设重新初始化 SystemInit(); RCC_Configuration(); GPIO_Configuration(); EXTI_Configuration(); NVIC_Configuration(); GPIO_SetBits(LED_GPIO_PORT, LED_GPIO_PIN); printf("Wakeup from STOP Mode!\r\n"); }

这里PWR_STOPEntry_WFI表示用WFI指令等待中断唤醒。如果你用的是WFE方式,唤醒条件会多一些,一般测试例程都建议用WFI,逻辑更简单。

唤醒后第一步是重新执行SystemInit()和时钟配置,这点极其关键。我最早测试的时候偷懒没恢复时钟,结果唤醒后LED和串口全都不正常,串口打的字符全是乱码。原因是主频从72MHz掉到了8MHz,串口波特率计算全部错位。

还有一个细节是串口外设的重新初始化。stop模式不丢失外设寄存器配置,理论上串口配置还在,但实际上唤醒后我见过好几次串口状态异常的情况,原因和时钟恢复时序有关。保险起见,唤醒后把需要用的外设全部重新初始化一遍,代码多一点,但可靠性高很多。

4. 实测数据与结果分析

4.1 测量方法和仪器选择

测低功耗电流不是随便拿个万用表就能测准的。市面上常见的九块九万用表,电流档内阻大、分辨率低,测出来到1mA级别就差不多了,想看看微安级的差异基本看个零。建议用台式万用表或者钳形表,至少四位半分辨率,电流档分辨率要到0.1μA。

更精确的方法是在电源和板子之间串一个1Ω的精密电阻,用示波器测电阻两端电压。这样能看到设备进入stop模式瞬间的电流波形,也能捕捉到唤醒瞬间的尖峰电流,这个尖峰往往是电池供电设计里最头疼的地方,因为峰值电流可能导致电池电压跌落,引起复位。

4.2 实测数据记录

我测了一组数据,用同一块板子在室温25℃下,电源电压3.3V稳定供电。

工作状态实测电流备注
正常运行,LED亮,串口空闲26.8 mA72MHz主频
正常运行,LED灭,串口空闲24.2 mA外设时钟未关闭
stop模式,LowPower稳压器7.8 μA所有外设时钟关闭
stop模式,ON稳压器11.6 μA唤醒速度更快
串口空闲但未关闭外设时钟670 μA测试失误,时钟没关完

对比下来,同样进stop模式,低压差稳压器模式比正常稳压器模式电流低差不多4μA,在电池供电的项目里这个差距很重要。另一方面,如果进入stop前没把USART和GPIO的外设时钟关掉,电流直接上到几百微安,说明“关闭不用的外设时钟”这一步不是可选项,而是必须项。

我还拿示波器测了唤醒时间,从按键按下到LED重新点亮大概需要150μs,其中大部分时间花在系统时钟恢复(等待HSE起振和PLL锁定)上。如果对唤醒时间有严格要求,可以考虑用HSI做唤醒后的临时时钟,先把关键逻辑跑起来,再切换回HSE+PLL,这样能把唤醒后的有效响应时间压到几十微秒级别。

5. 常见问题与避坑经验

5.1 唤醒后系统时钟丢失,串口乱码

这个坑我上面提过,第一次测试时候就踩了。现象是唤醒后能执行代码,但是串口打印乱码、定时器不准确。原因就是stop模式把所有时钟源都关了,唤醒后MCU默认只跑HSI 8MHz,而没有72MHz。解决办法必须在唤醒后的第一件事就是重新配置时钟,不要先操作外设。

我习惯把时钟配置、GPIO配置、外设配置打包成一个System_Reinit()函数,这样进入stop前和唤醒后调用的是同一套代码,逻辑清晰,也能减少漏配的情况发生。

5.2 EXTI无法唤醒的排查思路

如果按键按下之后系统纹丝不动,先检查三处。第一,GPIO_EXTILineConfig有没有调用,这个配置漏了,EXTI永远收不到信号。第二,中断优先级分组没有配置,或者NVIC没有使能对应的中断通道。第三,检查按键按下时的电平状态,如果按键接法相反,触发沿也要改成Rising。

还有一个容易忽略的点:如果进入stop之前还开着别的外设中断,比如串口接收中断、定时器中断,这些中断事件在stop状态下同样可以唤醒CPU。我调试的时候遇到过按一下按键,系统醒了两次的现象,后来排查发现是串口空闲中断也触发了一次唤醒,而且唤醒后没做时钟恢复直接跑飞了。

5.3 调试器连接对功耗和测试结果的影响

这是新手最容易怀疑人生的地方。用ST-Link或者J-Link连着板子测电流,数值永远不会低于几毫安。因为调试器通过SWD接口给目标芯片供电,而且调试器的电平转换器本身就在漏电。更恶心的是,调试接口的连接状态会阻止MCU真正进入低功耗模式,你看到的电流根本不能代表实际运行状态。

正确做法是:程序烧录完成后,断开调试器,用独立的稳压电源给板子供电,再测电流。如果需要看唤醒波形,用示波器探头接在LED引脚或者自定义的测试引脚上,而不是通过调试器实时查看。我建议在板子上预留一个IO口作为状态指示,进入stop前拉低,唤醒后拉高,用示波器测这个高低电平变化就能准确判断唤醒时机和工作状态。

写在最后的一点经验

这个例程我自己前后跑了三天,最深的体会是低功耗测试这件事,软件代码只占一小半功夫,剩下的大半都是在跟测量环境和外围电路较劲。最开始测出的电流仿真和实测差了十倍,排查下来发现是板子上一个LED没有完全关闭,另外一个原因是免调试器和串口转USB工具一直挂在板子上偷电。

如果你后续想在产品上真正用好stop模式,我建议把这个测试例程继续扩展,一是加入RTC定时唤醒,实现周期性的低功耗采集上报;二是加入备用寄存器保存唤醒次数,用来区分是首次上电还是从stop模式唤醒;三是把不用的引脚都配置成模拟输入或者固定电平,尽量减小漏电流路径。这些都是在实际产品里比“按按键唤醒”更有价值的场景,也是把stop模式从例程变成生产力的必经之路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 12:49:47

AI智能体驱动的自动化代码评审:从PR到质量门禁的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 12:49:40

FPGA实战:从SPI协议原理到Verilog状态机可复用代码

刚接触FPGA的同事总爱问我,SPI这么简单的4根线,有必要专门当个项目来讲吗?等他们自己在板子上调ADC或者读Flash的时候,对着示波器瞪一小时波形就明白了,SPI协议看着简单,真正在FPGA上用状态机把时序做扎实&…

作者头像 李华
网站建设 2026/9/8 12:48:10

Suno AI音乐生成实战:从哼唱到完整歌曲的技术解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 12:46:51

C#上位机与ZigBee组网实战:串口通信与传感器监控

简介:面向C#与Zigbee物联网开发者的上位机工程资源,覆盖串口通信、数据解析、MySQL存储与自校验等完整链路,适用于小区燃气监控等场景的入门与二次开发。资源包含87个文件,压缩包8.48MB,以C#源码(.cs)、VS工程配置文件…

作者头像 李华
网站建设 2026/9/8 12:45:42

i.MX6ULL平台驱动开发:从设备树到probe匹配机制详解

做嵌入式Linux驱动开发这几年,i.MX6ULL算是我用得最多的一块主控,Cortex-A7内核、资源适中、资料也多,从入门学习到小批量产品都很合适。不管点灯、读按键还是驱动外设,Linux下都绕不开Platform设备和驱动匹配机制这个问题。很多朋…

作者头像 李华
网站建设 2026/9/8 12:43:44

接入层交换机VLAN配置:AP端口Trunk与PVID实战详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华