4. LPM3模式实战:RTC唤醒与看门狗定时器唤醒

LPM3模式,说白了就是MSP430深度睡眠的“甜点”。为什么这么说?因为在这个模式下,主CPU彻底断电,但ACLK时钟还在跑——这就意味着RTC和看门狗都能正常工作。我做过不少低功耗项目,LPM3用得最多,没有之一。

今天咱们就动手,把RTC唤醒和看门狗唤醒都跑一遍。你跟着我走,保证能摸清门道。

4.1 LPM3模式的核心特征

先看一张表,把LPM3和其他模式做个对比。这样你心里就有谱了。

模式 CPU状态 ACLK MCLK SMCLK 典型电流
AM(活跃) 运行 开 开 开 ~300 µA
LPM0 关闭 开 关闭 开 ~85 µA
LPM3 关闭 开 关闭 关闭 ~1.5 µA
LPM4 关闭 关闭 关闭 关闭 ~0.1 µA

看到没?LPM3比LPM0省了将近两个数量级的电流,而且还能用RTC和看门狗。LPM4虽然更省,但醒来只能靠外部中断,灵活性差很多。

关键点:LPM3模式下,ACLK必须保持运行。ACLK通常由32.768kHz晶振驱动,这是RTC和看门狗工作的前提。

4.2 RTC唤醒实战

RTC唤醒,就是让单片机睡下去,每隔一段时间自动醒来干活。我习惯用RTC做周期性数据采集——比如每秒钟读一次传感器,然后继续睡。

下面这段代码,演示了如何配置RTC并进入LPM3模式:

#include <msp430.h>

void init_rtc(void) {
    // 配置RTC为1秒中断
    RTCCTL01 = RTCMODE | RTCTEVIE;  // 启用RTC模式,使能事件中断
    RTCSEC = 0x00;                  // 秒寄存器清零
    RTCMIN = 0x00;                  // 分钟清零
    RTCHOUR = 0x00;                 // 小时清零
    RTCDOW = 0x01;                  // 星期几(随便设)
    RTCDAY = 0x01;                  // 日
    RTCMON = 0x01;                  // 月
    RTCYEAR = 0x2024;               // 年

    // 设置中断间隔:每秒触发一次
    RTCCTL01 |= RTCRDYIE;           // 就绪中断使能
}

void main(void) {
    WDTCTL = WDTPW | WDTHOLD;       // 关看门狗
    init_rtc();

    __bis_SR_register(LPM3_bits | GIE);  // 进入LPM3,开启全局中断
    // 程序不会跑到这里,除非被唤醒
}

// RTC中断服务函数
#pragma vector = RTC_VECTOR
__interrupt void RTC_ISR(void) {
    switch(__even_in_range(RTCIV, 16)) {
        case 0:  break;             // 无中断
        case 2:                     // RTCRDYIFG
            // 每秒执行一次的任务
            P1OUT ^= BIT0;          // 翻转LED,表示被唤醒
            break;
        case 4:                     // RTCTEVIFG
            break;
        default: break;
    }
}

个人习惯:我每次写RTC初始化,都会先确认晶振是否起振。曾经有个项目,32.768kHz晶振匹配电容没选对,RTC死活不走。后来用示波器一看,波形都变形了。换电容后一切正常。

4.3 看门狗定时器唤醒

看门狗定时器(WDT)除了能复位系统,还能当间隔定时器用。在LPM3模式下,WDT可以用ACLK计数,到达设定时间后产生中断,把CPU叫醒。

这个用法特别适合做“心跳”任务——比如每隔几秒检查一次电池电压。

void init_wdt_interval(void) {
    // 配置WDT为间隔定时器模式
    // 时钟源:ACLK(32.768kHz)
    // 分频:32768,得到1秒间隔
    WDTCTL = WDT_ADLY_1000;         // 1秒间隔
    SFRIE1 |= WDTIE;                // 使能WDT中断
}

void main(void) {
    // 关闭默认看门狗
    WDTCTL = WDTPW | WDTHOLD;

    init_wdt_interval();

    __bis_SR_register(LPM3_bits | GIE);

    while(1) {
        // 唤醒后执行的代码
        __bis_SR_register(LPM3_bits | GIE);  // 再次睡眠
    }
}

// WDT中断服务函数
#pragma vector = WDT_VECTOR
__interrupt void WDT_ISR(void) {
    // 清除中断标志(硬件自动清除)
    // 执行唤醒后的任务
    P1OUT ^= BIT0;                  // 翻转LED
}

注意:WDT_ADLY_1000这个宏定义在不同型号的MSP430上可能不一样。我建议你查一下具体型号的头文件。曾经有个同事直接复制代码,结果WDT间隔变成了1ms,系统几乎没睡过觉。

4.4 两种唤醒方式的对比

RTC和WDT都能在LPM3下唤醒,但适用场景不同。我整理了一张对比表:

特性 RTC唤醒 WDT唤醒
时间精度 高(晶振驱动) 中(分频器)
最小间隔 1秒 ~0.5ms(取决于分频)
最大间隔 无上限(日历模式) ~32秒(最大分频)
额外功能 可记录绝对时间 可兼做系统复位
功耗开销 略高(RTC模块) 极低

我个人建议:需要精确时间戳的场景用RTC,只需要周期性唤醒的用WDT。比如做数据记录仪,RTC是必须的;做无线传感器节点,WDT就够用了。

4.5 核心流程:LPM3睡眠与唤醒

下面这张图,把整个流程串起来了。你一看就明白。

LPM3睡眠与唤醒流程 LPM3睡眠与唤醒核心流程 系统初始化 配置唤醒源(RTC/WDT) 进入LPM3睡眠 唤醒事件触发 执行中断服务函数 返回主循环/再次睡眠 说明 • 唤醒源可以是RTC或WDT • 中断服务执行完后自动返回 • 主循环中可再次进入LPM3

这个流程看起来简单,但有个坑我得提醒你:中断服务函数里不要做耗时操作。我见过有人把传感器读取和数据处理都放在中断里,结果中断还没执行完,下一个唤醒事件又来了。系统变得不稳定。

我的做法:中断里只设一个标志位,主循环里检查标志位再干活。这样中断服务函数执行时间控制在几微秒内,系统响应更可靠。

4.6 功耗实测与优化

理论讲完了,咱们看看实际数据。我用MSP430FR5969开发板测过两组数据:

唤醒方式 睡眠电流 唤醒后工作电流 平均功耗(1秒周期)
RTC唤醒 1.6 µA 2.5 mA(持续2ms) ~6.6 µA
WDT唤醒 1.4 µA 2.5 mA(持续2ms) ~6.4 µA

两者差距不大。但如果唤醒周期拉长到10秒,RTC的优势就出来了——因为RTC可以记录绝对时间,而WDT需要软件计数累加。

避坑指南:我曾经在WDT唤醒的项目里,用软件变量计数来模拟分钟级唤醒。结果变量被意外修改,导致唤醒周期错乱。后来改用RTC,问题迎刃而解。所以,长时间间隔的唤醒,我建议优先用RTC。

4.7 小结

LPM3模式是MSP430低功耗设计的核心。RTC和WDT两种唤醒方式,各有千秋。你只要记住:

  • 需要精确时间 → 用RTC
  • 只需要周期性唤醒 → 用WDT
  • 中断服务函数要短小精悍
  • 主循环里做实际工作