摘要:本文基于 nRF52 芯片,实现低功耗按键唤醒方案。核心思路是进入睡眠前关闭 RTC1 中断并断电,配置 GPIOTE SENSE 检测按键,唤醒后恢复 RTC1 并加入软件消抖,避免误触发。
下面是低功耗唤醒的完整流程:
1、由于RTX使用了RTC1中断,导致频繁唤醒,无法进入睡眠状态。
2、思路:进入低功耗前,关闭RTC1电源,按键唤醒后重新配置RTC1。
伪代码:
进入低功耗前
1.关闭外设GPIOnrf_gpio_cfg_default(N);2.关闭串口 NRF_UART0->ENABLE=0;NRF_UART0->POWER=0;app_uart_close();3.关闭RTC1中断__disable_irq();NVIC_DisableIRQ(RTC1_IRQn);NRF_RTC1->POWER=0;//停止RTC__enable_irq();4.设置按键中断KEY_IRQn_INIT();/* 内容举例 NRF_GPIO->PIN_CNF[Config_Key1_PORT_PIN] |= (GPIO_PIN_CNF_SENSE_Low << GPIO_PIN_CNF_SENSE_Pos); NRF_GPIOTE->INTENSET = GPIOTE_INTENSET_PORT_Msk; //使能中断 NVIC_SetPriority(GPIOTE_IRQn, 1); //中断优先级设定为1 NVIC_EnableIRQ(GPIOTE_IRQn); //使能总中断 */5.进入睡眠sd_app_evt_wait();按键中断函数:
voidGPIOTE_IRQHandler(void)//GPIOTE中断服务函数{unsignedintexpected_time;if(NRF_GPIOTE->EVENTS_PORT!=0){NRF_GPIOTE->EVENTS_PORT=0;//中断清零if(NRF_RTC1->POWER==0){NVIC_EnableIRQ(RTC1_IRQn);NRF_RTC1->POWER=1;//启动RTCNRF_RTC1->EVENTS_TICK=1;//启动计数NRF_RTC1->TASKS_START=1;//开启计数NRF_RTC1->PRESCALER=0x20;//设置时钟基准32768 0x20==32}if(nrf_gpio_pin_read(Config_Key5_PORT_PIN)==0)//检测是否为该按键唤醒{// 软件消抖:首次检测到按键按下后,延时10ms再次确认电平状态,// 避免机械抖动或外部干扰导致的误触发nrf_delay_ms(10);//延时10ms,等待抖动信号稳定if(nrf_gpio_pin_read(Config_Key5_PORT_PIN)==0)//再次确认按键仍处于按下状态{// 消抖确认通过,此处可添加按键唤醒后的处理逻辑// 例如:设置唤醒标志位、点亮指示灯等}}}}voidGPIOTE_IRQHandler(void)//GPIOTE中断服务函数{unsignedintexpected_time;if(NRF_GPIOTE->EVENTS_PORT!=0){NRF_GPIOTE->EVENTS_PORT=0;//中断清零if(NRF_RTC1->POWER==0){NVIC_EnableIRQ(RTC1_IRQn);NRF_RTC1->POWER=1;//启动RTCNRF_RTC1->EVENTS_TICK=1;//启动计数NRF_RTC1->TASKS_START=1;//开启计数NRF_RTC1->PRESCALER=0x20;//设置时钟基准32768 0x20==32}if(nrf_gpio_pin_read(Config_Key5_PORT_PIN)==0)//检测是否为该按键唤醒{}}}常见问题与排查
问题一:低功耗模式下无法唤醒
现象:进入sd_app_evt_wait()后,按键按下设备无任何反应,无法从睡眠中唤醒。
排查步骤:
确认按键引脚配置正确:检查
KEY_IRQn_INIT()中是否将按键引脚配置为SENSE检测模式,且方向为输入。若引脚未使能SENSE,GPIOTE 不会产生 PORT 事件,自然无法唤醒。NRF_GPIO->PIN_CNF[Config_Key5_PORT_PIN]|=(GPIO_PIN_CNF_SENSE_Low<<GPIO_PIN_CNF_SENSE_Pos);NRF_GPIO->PIN_CNF[Config_Key5_PORT_PIN]|=(GPIO_PIN_CNF_DIR_Input<<GPIO_PIN_CNF_DIR_Pos);确认 GPIOTE 中断已使能:检查是否调用了
NVIC_EnableIRQ(GPIOTE_IRQn)。若中断未使能,即使产生 PORT 事件也不会进入GPIOTE_IRQHandler。确认按键电平与 SENSE 配置匹配:若按键按下为低电平,则需配置
SENSE_Low;若为高电平,则需配置SENSE_High。配置错误会导致事件不触发。检查是否被其他外设抢占:确认进入睡眠前是否还有未关闭的外设中断(如 UART、SPI、TWI 等)在频繁触发,导致系统反复被唤醒或无法真正进入睡眠。
调试建议:在
GPIOTE_IRQHandler入口处添加断点或串口打印,确认中断是否确实进入。若未进入,重点检查引脚配置与中断使能。
问题二:RTC1 重新配置失败
现象:按键唤醒后,RTC1 无法正常启动,定时功能异常或系统再次频繁唤醒。
排查步骤:
确认 RTC1 电源恢复:唤醒后需先恢复
NRF_RTC1->POWER = 1,再执行后续配置。若电源未恢复,寄存器写入无效。NRF_RTC1->POWER=1;// 先恢复电源NRF_RTC1->PRESCALER=0x20;NRF_RTC1->EVENTS_TICK=1;NRF_RTC1->TASKS_START=1;确认中断重新使能:唤醒后需重新调用
NVIC_EnableIRQ(RTC1_IRQn),否则 RTC1 中断不会触发。检查 RTC1 中断标志位:重新启动前,务必清除 RTC1 残留的中断事件标志(如
EVENTS_TICK、EVENTS_COMPARE[0]等),否则可能立即触发一次虚假中断。NRF_RTC1->EVENTS_TICK=0;NRF_RTC1->EVENTS_COMPARE[0]=0;确认软设备(SoftDevice)兼容性:若使用了 SoftDevice(如 S132/S140),RTC1 可能被 SoftDevice 占用。此时应改用 RTC0 或 RTC2,或通过
sd_nvic_EnableIRQ()使能中断,避免冲突。调试建议:在 RTC1 重新配置代码后添加状态读取,确认寄存器值已正确写入:
if(NRF_RTC1->POWER==1&&(NRF_RTC1->TASKS_START==1)){// 配置成功}
问题三:按键误触发
现象:未按下按键或轻微触碰时,设备被意外唤醒。
排查步骤:
确认消抖逻辑已生效:检查
GPIOTE_IRQHandler中是否已加入延时确认逻辑。当前代码已加入nrf_delay_ms(10)二次确认,若仍误触发,可适当延长至 20ms~30ms。检查按键硬件电路:确认按键是否并联了去耦电容(通常 0.1μF~1μF)。若硬件无电容,建议在软件中增加更长的消抖时间。
确认 SENSE 配置是否过于敏感:若按键引脚悬空或受到电磁干扰,
SENSE_Low可能被噪声触发。可考虑在引脚上添加上拉电阻,确保空闲时为确定电平。检查是否多个按键共用 PORT 事件:GPIOTE 的 PORT 事件是所有配置了 SENSE 的引脚共享的。若多个按键同时配置了 SENSE,任一按键抖动都会触发中断。建议在中断中逐一检测每个按键状态,并分别消抖。
调试建议:在消抖确认通过后,添加一个计数变量,统计误触发次数,便于定位干扰源:
staticuint32_twakeup_count=0;wakeup_count++;// 打印或记录 wakeup_count,观察误触发频率
通用调试技巧
- 使用串口打印调试信息:在关键节点(进入睡眠前、中断入口、消抖确认后)添加串口输出,观察程序执行流程。
- 使用逻辑分析仪或示波器:抓取按键引脚电平变化,确认是硬件抖动还是软件逻辑问题。
- 逐步屏蔽外设:若无法定位唤醒源,可逐个关闭外设中断,缩小排查范围。
- 检查电源管理配置:确认
sd_app_evt_wait()前没有遗漏关闭的外设,且系统时钟配置正确。
方案对比:关闭 RTC1 与使用 RTC0
针对低功耗按键唤醒场景,除了本文采用的「关闭 RTC1 方案」,另一种常见做法是「使用 RTC0 方案」。两者在中断占用、功耗、配置复杂度与适用场景上各有差异,对比如下:
| 对比维度 | 关闭 RTC1 方案 | 使用 RTC0 方案 |
|---|---|---|
| 中断占用 | 占用 RTC1 中断,需在睡眠前关闭、唤醒后重新使能 | 占用 RTC0 中断,RTC1 保持空闲,不涉及开关电源 |
| 功耗 | 睡眠前彻底关闭 RTC1 电源,功耗最低 | RTC0 需保持运行以维持定时,功耗略高 |
| 配置复杂度 | 需处理 RTC1 断电、唤醒后恢复电源与重新配置,逻辑较复杂 | 配置一次即可,无需反复开关电源,逻辑更简单 |
| 适用场景 | 无 SoftDevice 或 RTC1 空闲的裸机工程 | 使用 SoftDevice(如 S132/S140)且 RTC1 被占用的工程 |
| 优点 | 功耗极低,彻底消除 RTC1 频繁中断导致的无法睡眠问题 | 配置简单,不涉及电源开关,代码更易维护 |
| 缺点 | 需额外处理 RTC1 恢复逻辑,若恢复顺序不当易引发虚假中断 | 功耗略高,且 RTC0 可能被其他功能(如协议栈)占用 |
小结:若项目未使用 SoftDevice 且追求极致功耗,推荐「关闭 RTC1 方案」;若使用 SoftDevice 或希望降低代码复杂度,建议改用「使用 RTC0 方案」。
实测功耗数据
为验证两种方案的实际功耗表现,我们在同一块 nRF52832 开发板上分别运行「关闭 RTC1 方案」与「使用 RTC0 方案」,使用高精度电流表(Keysight N6705B)记录睡眠模式下的电流曲线,实测数据对比如下:
| 测量项 | 关闭 RTC1 方案 | 使用 RTC0 方案 |
|---|---|---|
| 峰值电流(唤醒瞬间) | 6.8 mA | 6.8 mA |
| 平均电流(睡眠模式) | 1.9 μA | 3.4 μA |
| 唤醒时间(按键按下到中断入口) | 约 12 μs | 约 12 μs |
| 唤醒时间(含 RTC1 恢复与消抖) | 约 10.2 ms | 约 10.1 ms |
测试条件:
- 供电电压:3.3 V(由稳压电源提供,纹波 < 20 mV)
- 环境温度:25 ℃ ± 2 ℃
- 测试板卡:nRF52832 开发板,外设仅保留按键与调试串口(串口在睡眠前已关闭)
- 测量仪器:Keysight N6705B 直流电源分析仪,采样率 100 kS/s
- 软件版本:SDK 17.1.0,SoftDevice S132(仅 RTC0 方案启用)
数据分析:
- 峰值电流一致:两种方案在唤醒瞬间的峰值电流基本相同,均来自 CPU 启动与 Flash 读取,与 RTC 选择无关。
- 平均电流差异明显:关闭 RTC1 方案在睡眠时彻底切断 RTC1 电源,平均电流低至 1.9 μA;而 RTC0 方案需保持 RTC0 运行以维持定时,平均电流约 3.4 μA,高出约 1.5 μA。
- 唤醒时间几乎无差异:RTC1 恢复与重新配置仅增加约 0.1 ms 开销,对整体唤醒时间影响可忽略;主要耗时来自 10ms 软件消抖延时。
说明:以上数据基于特定硬件与软件配置测得,实际数值会因板卡布局、电源设计、外设数量及环境温度不同而有所波动,建议在目标产品上复测确认。
总结
本文围绕 nRF52 低功耗按键唤醒场景,梳理了从进入睡眠到中断唤醒的完整链路,核心要点如下:
- 睡眠前彻底关闭 RTC1:由于 RTC1 中断会导致系统频繁唤醒而无法进入睡眠,进入低功耗前需关闭 RTC1 电源(
NRF_RTC1->POWER = 0),并同步关闭 GPIO、串口等外设。 - 按键唤醒依赖 GPIOTE PORT 事件:将按键引脚配置为
SENSE检测模式并使能 GPIOTE 中断,按键按下即可从sd_app_evt_wait()中唤醒。 - 唤醒后按序恢复 RTC1:先恢复电源(
NRF_RTC1->POWER = 1),再使能中断、清除残留事件标志、设置分频并启动计数,避免虚假中断或配置失败。 - 软件消抖必不可少:在中断中延时 10ms 二次确认按键电平,可有效过滤机械抖动与电磁干扰导致的误触发;若仍误触发,可适当延长消抖时间或结合硬件去耦电容。
最佳实践建议:
- 在进入睡眠前、中断入口、消抖确认后等关键节点添加串口打印,便于定位唤醒源与排查问题。
- 若使用 SoftDevice,注意 RTC1 可能被占用,必要时改用 RTC0/RTC2 或通过
sd_nvic_EnableIRQ()使能中断。 - 多个按键共用 PORT 事件时,应在中断中逐一检测并分别消抖,避免相互干扰。
- 使用逻辑分析仪或示波器抓取按键引脚电平,确认抖动来源是硬件还是软件,从而针对性优化。