1. 背景
在进行低功耗架构设计时,系统理论上在 Stop 2 模式下的静态电流应控制在 50uA 以下。然而,实测时电流竟然高达1.1MA左右。

1.1 硬件排查
怀疑是外围电路存在漏电。结合原理图,将矛头指向了两个按键检测电路。
-
实验 1(断开测试): 拆除按键、滤波电容及上拉/限流电阻。功耗暴跌1.1MA ,回落到了理想状态。
-
实验 2( 交叉验证 ): 重新焊回电容,功耗未变;但焊回电阻后,功耗再次飙升。
-
实验 3(极限飞线): 尝试用飞线直接短路电阻,功耗依然未降。
难道是按键本身存在绝缘电阻过低导致的漏电?查阅元器件 Datasheet 确认其绝缘内阻在兆欧级别,绝不可能贡献毫安级的电流。此时硬件排查陷入僵局------拆电阻功耗就跌,装电阻功耗就涨,但电阻和按键本身又没有质量问题。
由以上推论,并非硬件漏电的问题,开始将目光转向软硬件 耦合 处的控制状态。
1.2 软件追凶
既然硬件通路无误,那么唯一的可能就是:GPIO 引脚上的电平状态,触发了板卡某种持续耗电机制,甚至阻止了 MCU 真正进入 Stop 2 模式。
顺着这个思路,逐步复盘软件行为:
-
按键在进入stop2模式前,均配置为 EXTI 外部中断输入。联系到硬件排查中"拆除按键器件后功耗下降"的现象,推测根因为:EXTI 中断未被完全屏蔽,导致 MCU 无法完全进入低功耗状态,持续维持在较高功耗的唤醒待机状态。
-
查阅 STM32 参考手册(RM),重点核查 Stop2 模式的进入条件与中断屏蔽要求。手册明确说明:若 EXTI 中断屏蔽寄存器(IMR1)中对应位未清除,相关中断线仍处于使能状态,MCU 将无法完全进入 Stop2 模式。
-
审查进入低功耗前的初始化代码,发现中断屏蔽寄存器 EXTI→IMR1 中,按键对应的 bit0 与 bit1 未被清零,EXTI 通道始终处于开放状态。
1.3 代码修复
在进入 Stop2 模式前,显式清除对应 EXTI 中断屏蔽位:
cpp
/* 进入 Stop2 前,屏蔽按键 EXTI 中断线(PX0 / PX1),防止阻塞低功耗入口 */
CLEAR_BIT(EXTI->IMR1, (1U << 0) | (1U << 1)); /* K1=line0, K2=line1 */
2. STM32的中断唤醒机制

NVIC(Cortex-M 标准):管的是"RUN 模式下 ISR 要不要跳"。
EXTI IMR1(STM32 私有):管的是"边沿要不要送往 PWR、用于把 MCU 从 STOP 抬醒"。
这两条路径是并联的,互相不影响。
3. 陷阱记录
MX_GPIO_Init 把 K1/K2 配成 GPIO_MODE_IT_RISING_FALLING 的时候,HAL 一次性设了:
EXTI->IMR1 |= (1<<0) | (1<<1)
EXTI->RTSR1 |= (1<<0) | (1<<1)
EXTI->FTSR1 |= (1<<0) | (1<<1)
NVIC_ISER使能 EXTI0_IRQn / EXTI1_IRQn
进 STOP2 前的清理只关了 NVIC:
HAL_NVIC_DisableIRQ(EXTI0_IRQn);
HAL_NVIC_DisableIRQ(EXTI1_IRQn);
IMR1、RTSR1、FTSR1 三个寄存器原样保留。EXTI → PWR/WIC 那条唤醒通路还是开着的。
进 STOP2 前,把 K1/K2 从 input 切到 analog。在硬件层面:
cpp
切换前(input): EXTI line 输入 = pin 实际电平 = HIGH(上拉电路)
│
│ ← MODER 写 0b11,input buffer 关
│
切换后(analog): EXTI line 输入 = 强制 0 ← 这是 HIGH→LOW
这一瞬间,EXTI line 0/1 输入从 1 跳到 0,是一次下降沿 。FTSR1 开着,EXTI 把它当真实下降沿锁进 PR1。再走 IMR1 那条路(还开着)送到 PWR/WIC,于是:
cpp
进 STOP2 的 WFI → 立刻被 pending wake-up 弹出 → 回到 RUN
↑ │
│ ▼
│ Power_Mode_OnStopWakeup(重配时钟、重启 LPTIM)
│ │
│ ▼
└──── 主循环跑一轮 → 再进 STOP2 ────┘
MCU 进入 "假 STOP2" 循环:每次 STOP2 只待几十微秒就被自己产生的伪边沿弹出。
4. 进入 STOP 模式中断配置必做清单
cpp
/**
* @brief 注销除 K3 外的 EXTI 与 NVIC
*/
static void EXTI_Suspend_NonK3_WakeSources(void)
{
/* 关掉 NVIC */
HAL_NVIC_DisableIRQ(EXTI0_IRQn);
HAL_NVIC_DisableIRQ(EXTI1_IRQn);
/* 关闭 K1/K2 的 EXTI 唤醒掩码:进 STOP2 不允许 PB0/PB1 边沿唤醒。
* 单纯 NVIC_DisableIRQ 不够------STOP 模式下唤醒由 IMR1 决定,不是 NVIC。 */
CLEAR_BIT(EXTI->IMR1, (1U << 0) | (1U << 1)); /* K1=line0, K2=line1 */
/* 清除中断标志 */
__HAL_GPIO_EXTI_CLEAR_IT(K1_Pin | K2_Pin | K3_Pin);
/* 保留需要的中断 */
NVIC_ClearPendingIRQ(EXTI2_IRQn);
HAL_NVIC_EnableIRQ(EXTI2_IRQn);
}
5. 名词解释
5.1 EXTI(External Interrupt /Event Controller ,外部中断/事件 控制器 )
监测单片机 GPIO 引脚上的电平变化,并在检测到特定变化时,立刻通知 CPU 做出响应,或者直接触发某个硬件联动。
5.2 ISR ( Interrupt Service Routine , 中断服务程序 或中断服务 函数 )
如果说 EXTI(外部中断) 是单片机的"门铃",那么 ISR 就是你去开门并拿外卖的"具体动作"。它是单片机内核在接收到中断请求时,强制暂停当前正在执行的主程序,转而执行的一段特定的、具有最高优先级的短代码。
5.3 PWR(Power Control,电源控制/电源管理器)
监控供电质量("防低血糖"与"安全带")
芯片的供电电压必须稳定,忽高忽低会导致内部逻辑混乱。PWR 内部集成了几个非常重要的电压监控器:
POR/PDR(上电/掉电复位): 当你刚插上电池,电压从 0V 往上涨,或者拔掉电池电压往下跌时,硬件会自动触发复位,防止芯片在半通电、电压不稳的状态下"跑飞"写坏内部 Flash。
BOR(欠压复位): 保证系统必须在安全的电压阈值以上才能运行,低于这个值直接强制芯片复位。
PVD(可编程电压检测器): 这是一个非常有用的软件预警机制。比如你的电池快没电了,电压跌到了 2.2V,PVD 会立刻触发一个中断通知 CPU:"主人,电池快枯竭了!" CPU 可以在 ISR 里赶紧把核心数据保存到 EEPROM 闪存中,并做最后的优雅关机,避免数据丢失。
控制"电源域"(Power Domains)的分断
单片机内部被 PWR 划分成了不同的物理电源区域。在需要省电时,PWR 可以像拉闸断电一样,把不需要的区域完全断电:
备份域(Backup Domain): 通常由纽扣电池(VBAT)供电,里面包含 RTC(硬件时钟) 和一些备份寄存器。即使单片机主电池没电了,只要备份域有电,时钟就永远在走,数据就不会丢。
核心域(VCORE Domain): 包含 CPU 内核、SRAM 内存和大部分数字外设。
闪存域(Flash Memory): 存储代码的地方。
- 管理"低功耗模式"(Low-Power Modes)
这是工程师最常跟 PWR 打交道的功能。PWR 通过切断时钟或关闭电源域,为单片机配置了不同的低功耗模式:
Sleep(睡眠模式): 仅仅关闭 CPU 时钟。外设还能用。功耗从 mA 级降到几百 uA,任何中断都能瞬间唤醒。
Stop(停止模式): 关闭所有总线时钟,1.2V 核心电源域不关但断开时钟。此时 CPU 和外设全部定格。功耗直奔 uA 级别。只能通过外部中断(EXTI)或低功耗定时器(LPTIM)唤醒。
Standby / Shutdown(待机/关机模式): 把除了备份域(RTC)以外的所有核心电源全断掉,SRAM 里的内存数据彻底丢失。功耗压榨到 nA(纳安)级。唤醒后相当于单片机重新彻底重启(Reset)。
5.4 WIC(Wakeup Interrupt Controller,唤醒中断控制器)
WIC 的核心存在意义
在没有 WIC 之前,单片机如果想被中断唤醒,NVIC必须一直保持通电和时钟开启。因为 NVIC 属于内核结构,这意味着内核的一部分无法彻底进入最省电的停机状态。
有了 WIC 之后:
深度断电成为可能: 当单片机通过执行
WFI(Wait For Interrupt)指令进入Stop 模式时,内核(包含 CPU、NVIC、SysTick 等)的时钟可以全部关闭,甚至把 VCORE 电压降到最低。硬件自动交接: 在内核闭眼睡觉的前一指令周期,NVIC 会把当前使能的中断信号控制权,自动同步/镜像给 WIC。随后内核彻底断电停摆。
极低功耗监听: WIC 是一个纯硬件的组合逻辑电路,它不需要任何时钟驱动(0Hz 运行),只消耗微弱的静态电流(纳安 nA 级别)。
WIC 的工作流程
当单片机处于 Stop 模式,外部发生了一个中断:
信号捕获: 引脚的电平跳变穿过外部电路,直接触发了 WIC 的监测线。
硬件唤醒(Wakeup Signal): WIC 识别到这是一个被允许的唤醒源,立刻向单片机的 PWR(电源控制器) 发出硬件信号:"快送电,有紧急情况!"
时钟与电源恢复: PWR 立刻拉高内核电压(VCORE),重新启动时钟振荡器(如 MSI),恢复内核总线时钟。
权力交还: 内核通电复活后,WIC 将控制权重新移交给 NVIC。
处理中断: 此时醒来的 CPU 并在物理上感知到 NVIC 里的挂起标志,开始保护现场,进入对应的 ISR(中断服务程序) 执行代码。
5.5 WIC、NVIC 与 EXTI 的物理关系
这三个概念经常同时出现,它们在单片机内部各司其职,构成了一条完整的唤醒生物链:
EXTI(外部中断): 属于内核 之外的外设。负责把物理引脚上的"电平跳变"变成一个"中断请求信号"。
WIC(唤醒 控制器 ): 属于内核 外围的常驻硬件 。负责在内核断电期间 盯着 EXTI 传过来的信号,并负责拉闸送电、唤醒内核。
NVIC( 向量 中断 控制器 ): 属于内核 内部的核心外设 。负责在内核通电期间,管理成百上千个中断的优先级、排队、并指引 CPU 跳转到哪个 ISR 代码去运行。
cpp
/**
* @brief 外部中断/事件控制器
*/
typedef struct
{
__IO uint32_t IMR1; /*!< EXTI 中断屏蔽寄存器 1, Address offset: 0x00 */
__IO uint32_t EMR1; /*!< EXTI 事件屏蔽寄存器 1, Address offset: 0x04 */
__IO uint32_t RTSR1; /*!< EXTI 上升沿触发选择寄存器 1, Address offset: 0x08 */
__IO uint32_t FTSR1; /*!< EXTI 下降沿触发选择寄存器 1, Address offset: 0x0C */
__IO uint32_t SWIER1; /*!< EXTI 软件触发中断/事件寄存器 1, Address offset: 0x10 */
__IO uint32_t PR1; /*!< EXTI 挂起寄存器 1, Address offset: 0x14 */
uint32_t RESERVED1; /*!< 保留, 0x18 */
uint32_t RESERVED2; /*!< 保留, 0x1C */
__IO uint32_t IMR2; /*!< EXTI 中断屏蔽寄存器 2 Address offset: 0x20 */
__IO uint32_t EMR2; /*!< EXTI 事件屏蔽寄存器 2 Address offset: 0x24 */
__IO uint32_t RTSR2; /*!< EXTI 上升沿触发选择寄存器 2, Address offset: 0x28 */
__IO uint32_t FTSR2; /*!< EXTI 下降沿触发选择寄存器 2, Address offset: 0x2C */
__IO uint32_t SWIER2; /*!< EXTI 软件触发中断/事件寄存器 2, Address offset: 0x30 */
__IO uint32_t PR2; /*!< EXTI 挂起寄存器 2, Address offset: 0x34 */
} EXTI_TypeDef;