STM8S103停机模式实战:微安级低功耗配置与唤醒设计

发布时间:2026/9/13 11:07:30
STM8S103停机模式实战:微安级低功耗配置与唤醒设计 简介STM8S103低功耗停机模式实验资源面向嵌入式初学者与STM8开发者解决如何配置PWR_CR寄存器、执行停机指令并实现外部中断唤醒的实际问题。资源基于STM8S103F3开发板包含完整的IAR工程文件与标准外设库共159个文件以头文件.h、源文件.c、链接脚本.xcl、编译中间文件.o/.lst及调试脚本.bat/.ewd为主另含hex烧录文件与工程配置便于直接编译、烧录和在线调试。压缩包约3.18MB结构清晰适合对照学习低功耗模式的状态保存、中断标志处理与外设关闭流程。已有385人学习下载。通过分析工程源码和实验文档可掌握STM8S103停机模式下的功耗控制技巧并延伸至STM8L等系列的低功耗设计为电池供电项目打下基础。1. 停机模式不是“断电”而是让 STM8S103 以微安级电流等待一声中断一块正常运行的 STM8S103工作电流随主频和外部负载升高而升高电池供电场景下如果 CPU 还在持续取指系统很难把功耗压下去。实验 25 的停机模式核心思想不是“降频省电”而是用一条 STOP 指令把 CPU 和绝大多数外设全部停住只保留内部 RC 振荡器和唤醒源等待事件靠这招把整板电流压到几十微安以下。它非常适合遥控器、便携仪表、传感器节点这类对响应不太苛刻但看重电池寿命的应用。下面从 PWR_CR 寄存器、实际代码、电流测量和唤醒跳坑把整个流程讲清楚。2. STM8S103 停机模式原理PWR_CR 的 STOP 位与电源域切分2.1 停机模式的功耗链路与“唯一例外”在讨论代码前先理解一件事STM8S103 停机时并不是所有电压都断开而是核心电源域和大多数外设时钟被切断。PWR_CR 寄存器中的 STOP 位决定停机使能内部 RC 振荡器是否保持则由 CLK_ICKR 寄存器控制。正常运行时主时钟由 HSE 或 HSI 提供外设通过 CLK_PCKENR 逐位使能时钟。停机模式会把 PCKENR 打开的时钟链路全部断掉这样 GPIO 等外设保持在上电配置但不再翻转。CPU 执行到 STOP 指令后取指停止程序计数器停在下一条指令处。唤醒后CPU 从 STOP 之后的指令继续执行而不是从复位向量开始这是停机模式和复位最本质的区别。低功耗模式CPU外设时钟典型唤醒源预期电流空闲模式暂停可选保持任意中断、复位1 mA 量级活跃停机停止大部分停止RC 可保持外部中断、RTC 等数十 μA停机模式停止全部停止外部中断、复位十几 μA 以下这里的电流量级是工程估计实际取决于 LDO 是否关闭和 I/O 静态电流。表中的“停机模式”对应本实验要实现的场景也是所有模式中功耗最低的一种。2.2 进入停机前的寄存器序列很多初次接触的人以为写 PWR_CR 的 STOP 位就立刻停机了其实它只是“允许停机”真正的停机动作由 STOP 指令触发。寄存器配置、状态保存、清理中断和停止外设是一个组合动作少任何一步都可能让系统在进入后立刻被唤醒甚至因为持续取指导致电流不降。2.2.1 STM8S103 的 CLK_PCKENR 与 PWR_CR 配置顺序下面是一段典型的进入停机前初始化代码基于 SDCC 直接操作寄存器。/* 进入停机前关闭不需要的外设时钟 */ CLK-PCKENR1 0x00; /* 关闭定时器、UART2、SPI、I2C 等外设时钟 */ CLK-PCKENR2 0x00; /* 关闭剩余外设时钟 */ /* 允许停机模式PWR_CR 的 STOP 位置 1 */ PWR-CR | 0x01; /* 只影响停机使能位不复位其他电源控制位 */ /* 设置外部中断唤醒引脚这里以 PA1 为例 */ PA_CR1 | 0x02; /* PA1 设为带上拉输入 */ PA_CR2 | 0x02; /* PA1 允许外部中断 */ /* 清除可能残留的中断标志 */ EXTI-CR1 0x00; /* 选择下降沿触发同时避免旧事件直接唤醒 */ /* 执行停机指令 */ halt();代码逻辑前两行把外设时钟关闭这样即使某个外设电平处于开漏状态也不会产生时钟翻转电流PWR_CR 的 STOP 位配合后续halt()才真正进入停机。PA1 配置成上拉输入后下降沿可以产生唤醒信号。halt()是编译器提供的将 STM8 的HALT指令包装起来的函数不能省略。参数说明PCKENR1 和 PCKENR2 分别管两组外设具体每位对应的模块需要查 STM8S103 数据手册。EXTI-CR1的低两位设置 PA 口的触发条件这里 0x00 表示下降沿如果设成上升沿则按键按下结束才唤醒不利于快速响应。在停机模式下外部中断引脚必须配置为输入模式并打开中断使能否则无法唤醒。2.3 常见误区没有清除待处理中断标志会立刻退出停机外部中断标志如果在上一轮触发后没有清零执行halt()时系统会认为中断已经到达CPU 直接从停机状态恢复表现为“按一次按钮实际唤醒了两三次”。所以进入停机前应该先读中断状态写入1清除标志再执行停机。还有一类误区是把所有引脚都保持为默认高电平但外部电路恰好是低电平有效停机时外部信号把引脚拉低产生持续灌电流。工程上常用的做法是把未使用引脚全部配置为模拟输入避免悬空产生不确定电平。3. 实验 25 工程实战从唤醒工程到 STM8S103 停机模式代码复现3.1 KeQianMCU 项目文件里到底有什么源材料中列出的文件是KeQianMCU_STM8S103F3_Library.Debug.cspy.bat、KeQianMCU_STM8S103F3_Library.cspy.bat、KeQianMCU_STM8S103F3_Library.pbd.browse以及stm8s_tim1.c、stm8s_tim5.c、stm8s_can.c、stm8s_tim2.c、stm8s_tim3.c、stm8s_i2c.c、stm8s_uart2.c。这里的.cspy.bat是 IAR C-SPY 命令行调试入口不是主程序真正控制停机行为的是开发者在 main 函数里调用的电源管理序列。这些.c文件是 ST 标准外设库的驱动源文件实验 25 不会全部用到工程默认把用不到的模块文件一起打包方便后续扩展。实际工程中进入停机模式只需要包含stm8s.h和必要的 GPIO/EXTI 驱动。3.2 实际可跑的停机模式主流程在示例工程里通常会有这样的一个 main 函数。#include stm8s.h /* 全局唤醒标志中断里置位 */ volatile uint8_t flag_wakeup 0; void GPIO_Init_Key(void) { /* PD3 配置成外部中断输入 */ PD-DDR ~BIT3; /* 输入 */ PD-CR1 | BIT3; /* 带上拉 */ PD-CR2 | BIT3; /* 允许外部中断 */ EXTI-CR2 ~0x0F; /* 清原有配置 */ EXTI-CR2 | 0x04; /* PD 上升沿触发 */ } void main(void) { CLK-CKDIVR 0x03; /* 主频降到 2 MHz降低待机前功耗 */ GPIO_Init_Key(); while (1) { /* 进入停机前先关闭外设时钟 */ CLK-PCKENR1 0x00; CLK-PCKENR2 0x00; /* 打开停机允许位 */ PWR-CR | 0x01; __enable_interrupt(); halt(); /* 停机等待外部中断唤醒 */ /* 唤醒后恢复外设时钟 */ CLK-PCKENR1 0xFF; CLK-PCKENR2 0xFF; /* 模拟数据处理 */ flag_wakeup; } } /* PD3 外部中断服务函数 */ INTERRUPT_HANDLER(EXTI_PORTD_IRQHandler, 6) { EXTI-CR2 ~0x0F; /* 清除边沿遗留 */ flag_wakeup 1; }这段代码里CLK-CKDIVR 0x03把主时钟分频到 2 MHz便于唤醒后快速进入工作。PWR-CR | 0x01打开停机允许后halt()会立即把内核时钟停止唤醒后程序从flag_wakeup继续执行因此必须使用volatile修饰标志位防止编译器把循环优化掉。参数说明EXTI-CR2的高/低四位分别对应 PD 和 PE 口。上面0x04设定 PD 口上升沿触发如果按键接法是按键一端接地、PD3 平时接高电平则需要用下降沿对应值要改成 0x08具体见参考手册中 EXTI_CR2 的位定义。3.3 为什么不能直接在主循环里反复执行 halt有的项目出现按键按下后系统总是处于停止状态是因为唤醒后的中断标志被再次识别。实验 25 的指导思路是唤醒后的处理逻辑写在halt()之后而不是全部塞进 ISR。ISR 只负责清除标志和置位否则未处理的中断事件会让系统在halt()处反复进入退出功耗反而更高。3.3.1 用 IAR 命令行调试脚本验证停机点工程里的KeQianMCU_STM8S103F3_Library.Debug.cspy.bat是 IAR C-SPY 的命令行调试脚本双击会启动仿真器并加载调试配置。实际调试时可以在halt()那一行打断点观察 PC 指针是否停在 HALT 指令地址如果把断点设在halt()之前单步执行时会发现执行完 HALT 后无法继续单步这是正常现象因为内核时钟已经停止。如果要在批处理基础上加日志可以使用-log all让 C-SPY 输出完整寄存器变化记录便于定位是哪一步没有进入预期状态。4. 验证停机模式与抖掉“假复位”陷阱电流实测和唤醒时序4.1 万用表与示波器的接法评估停机效果必须量电流。常见做法是万用表选 mA 档串在电源回路上测整体电流电流降下来后再切换到 μA 档。如果开发板上有 LDO 和指示灯测量结果会被外围电路干扰建议把 STM8S103 的 VDD 脚飞线出来单独供电跳开 LDO 和 LED。# 用 IAR 命令行会话启动调试器观察运行状态 KeQianMCU_STM8S103F3_Library.Debug.cspy.bat -log all这里的-log all是 C-SPY 输出详细日志的选项可以观察复位、停机、唤醒时的寄存器变化。测量电流的同时用示波器观察按键唤醒产生的电平跳变能确认外部中断信号是否真实到达引脚。4.1.1 电流档位选择的细节不要用万用表的自动量程直接测微安级电流自动切换量程时的内阻变化会产生瞬间压降可能导致芯片欠压复位。正确做法是先短接表笔让系统正常运行再用 mA 档测工作电流最后切换到 μA 档测停机电流切换过程要保证 VDD 电压不低于 3.0 V。4.2 唤醒后“假复位”的几个根源停机模式唤醒后PC 会从halt()后一条指令继续执行。如果发现程序总是从头跑说明唤醒源被配置成了复位源或者硬件看门狗没有被正确喂狗。如果 IWDG 没有关停机时看门狗依然在计数唤醒瞬间复位程序从头执行。如果 PWR_CR 配置的唤醒源和复位源同时使能可能出现复位行为。如果内部 RC 唤醒时间较长电源轨出现跌落也会触发 BOR 复位需要检查 VDD 电容建议 100 nF 10 μF 搭配。4.2.1 用 RST_SR 寄存器确认复位来源STM8S103 的复位状态寄存器可以区分是外部复位、看门狗复位还是上电复位。唤醒异常复位后可以在 main 开头读取该寄存器打印或记录。uint8_t rst_status RST-SR; /* 读取复位原因 */ if (rst_status RST_SR_IWDGF) { /* 说明是独立看门狗溢出复位 */ } RST-SR 0x00; /* 写清零 */这段代码检查 IWDGF 位如果置位就说明停机期间看门狗没有停止。正常情况下停机模式下 IWDG 可以保持计数但如果你希望停机后不被复位必须在进入停机前刷新一次 IWDG并把唤醒时间控制在看门狗周期内或者临时关闭看门狗。4.3 现象定位表现象可能原因处理办法停机电流明显高于 100 μA外设时钟没关完或 GPIO 悬空遍历关闭 PCKENR未使用引脚设为模拟输入按键唤醒后程序从头跑硬件看门狗仍在运行进入停机前先喂狗或初始化里关闭 IWDG多次按键才唤醒中断标志残留在halt()前清除 EXTI 边沿标志唤醒后 UART 收到乱码主频恢复时序不对唤醒后重新配置 CLK_CKDIVR 并等待时钟稳定4.4 一个常见的低功耗设计检查清单不要在设计里看到“停机”就觉得万事大吉。我见过很多项目在进入停机前把引脚保持为输出低电平结果外部电路是推挽驱动两边直接对灌电流。最稳妥的检查顺序是先关闭外设时钟再把所有未使用引脚设为模拟输入最后执行停机指令。每次改完代码后至少测量 10 组上电和唤醒电流确认没有偶发高功耗。5. 进阶用停机模式配合 RTC/看门狗做周期唤醒以及从 STM8L 停机程序移植的差异5.1 定时唤醒的另一种思路有的项目需要系统每 10 秒采集一次但只靠外部中断会卡住。STM8S103 的 RTC 没有独立电源域停机模式下无法像 STM8L 那样保持 RTC 走时工程上常用带中断输出的外部 RTC 芯片或者干脆用看门狗定时唤醒。如果非要在内部做周期唤醒更实际的做法是退回到 idle 低功耗休眠模式用定时器中断定期唤醒代价是待机电流会上升到 1 mA 量级。认清这个边界才不会被测试数据误导。5.2 STM8L 停机程序移植到 STM8S103 的注意事项对于从 STM8L 项目迁移过来的代码例如bend8w5提供的 STM8L 停机程序需要特别关注 PWR 寄存器地址和位段差异。STM8L 的 PWR_CR1 和 STM8S 的 PWR_CR 不能互换STM8L 支持 RTC 异步唤醒和多种低功耗模式而 STM8S103 没有独立的 RTC 电源域进入停机前必须把外部中断作为唯一唤醒源或者配合外部看门狗事件。/* 典型的 STM8L 停机配置移植到 STM8S103 时需要改寄存器名 */ PWR-CR1 | 0x01; /* STM8L 写法在 STM8S103 上应改为 PWR-CR */这里的关键是把CR1改为CR并且确认唤醒源引脚在 STM8S103 的 EXTI 映射范围内。如果原来的代码使用 STM8L 的 RTC 事件唤醒换成 STM8S103 后需要外接 RTC 芯片或者改用外部中断引脚。5.3 引脚电平固定是最后一道收尾发布前建议用示波器确认所有引脚没有浮空抖动。一个很实用的收尾技巧在进入停机函数末尾加上GPIO_DeInit()然后重新配置关键引脚把不需要的引脚全部设置为推挽输出低电平但外部控制电源的引脚要反着来避免误关负载。这样整板电流才会全部贡献给真正的负载而不是被 I/O 口漏电流吃掉。本文还有配套的精品资源点击获取