Funpack5-4 - 基于 FRDM-MCXN236 开发板的低功耗电子时钟
该项目使用了FRDM-MCXN236 开发板,实现了低功耗电子时钟的设计,它的主要功能为:MCU休眠和唤醒、唤醒后时间保持、OLED显示屏显示时间。
标签
Funpack活动
显示
开发板
时钟
clr
更新2026-10-19
12

一、 项目介绍

传统电子时钟通常采用持续运行的主循环完成计时、显示和按键检测,即使没有用户操作,微控制器仍需要周期性执行任务,产生不必要的功耗。对于电池供电或需要长时间运行的嵌入式设备,如何在保证计时准确性与交互响应的同时降低系统功耗,是一个值得研究的问题。

本项目基于 NXP FRDM-MCXN236 开发板,利用 MCXN236 的 Cortex-M33 内核、集成实时时钟(IRTC)和低功耗管理机制,设计了一套支持自动休眠、定时唤醒和交互式控制的电子时钟系统。

系统以独立运行的 RTC 作为时间基准,微控制器在完成必要任务后进入休眠,每秒由 RTC 中断自动唤醒,更新 OLED 显示并通过串口输出当前时间。此外,系统还支持串口校时、按键控制休眠及恢复、OLED 自动重连等功能。

本项目主要实现以下功能:

  1. RTC 实时时钟:通过片内 IRTC 维持日期和时间,支持时间读取、设置及有效性检查。
  2. 周期性休眠与唤醒:系统每秒由 RTC 中断唤醒,完成串口输出及 OLED 更新时间后重新休眠。
  3. 交互式低功耗管理:通过板载按键或串口命令进入硬件 Deep Sleep,暂停显示刷新与周期性串口输出,仅保留 RTC 后台计时,按键或串口输入可恢复正常工作。
  4. 串口交互:支持实时输出日期时间、接收校时指令及切换系统工作状态。
  5. 显示与异常恢复:通过 I²C 接口驱动 OLED,并针对显示屏启动延迟、通信异常等问题增加恢复机制。


二、设计思路

2.1 系统总体设计

系统采用 RTC 硬件计时与中断驱动的工作方式,将连续计时和周期性业务处理分离。


IRTC 运行在独立的时钟域,通过片内低频时钟持续计时,不依赖 Cortex-M33 内核保持运行。主程序负责处理 RTC 秒事件、串口指令及按键操作,并根据当前工作状态决定是否刷新显示。


系统主要由 FRDM-MCXN236 主控、OLED 显示模块、板载按键和串口通信接口组成。其中,IRTC 提供时间基准,LPI2C2 负责 OLED 通信,LPUART4 负责串口数据交互,GPIO 中断负责按键事件检测。

2.2 两级低功耗管理

根据电子时钟的实际运行需求,系统设计了两种不同的硬件低功耗模式:

对比项目

Level 1:普通 Sleep

Level 2:硬件 Deep Sleep

使用场景

正常走时的空闲间隙

用户主动进入长时间休眠

ARM 配置

SLEEPDEEP=0

SLEEPDEEP=1

CMC CKMODE

0x1

0xF

CMC 电源域

MAIN/WAKE 保持运行配置

MAIN/WAKE LPMODE=1

RTC

1Hz 中断周期唤醒

持续计时,暂停 1Hz 中断

OLED

保持显示,每秒刷新

关闭显示

唤醒方式

RTC、串口、普通按键

SW2 经 WUU0_IN4 唤醒

Level 1 主要用于正常走时。CPU 完成一次时间更新后,通过 WFI 指令进入普通 Sleep,等待下一次 RTC 秒中断或其他交互事件。

Level 2 则通过 CMC 将芯片切换至硬件 Deep Sleep。进入前关闭 OLED 显示并暂停 RTC 1Hz 中断,避免周期性唤醒;RTC 硬件仍继续计时。系统使用 WUU0_IN4 检测 SW2 按键,实现深睡眠后的异步恢复。


整体流程图:


mermaid-diagram (2).png


三、实现过程

image.png

项目使用 MCUXpresso for VS Code 进行开发,一站式编译和调试。

3.1 系统初始化与主循环

系统上电后,依次完成时钟配置、IRTC 初始化、UART 通信、OLED 显示及按键/WUU 中断配置。

RTC 初始化首先检查硬件计时有效性,仅在 RTC 数据无效或日期不合理时写入默认时间,避免复位后无条件覆盖已有计时结果。

程序随后进入主循环,集中处理 RTC 秒事件、串口命令、按键操作及显示恢复任务。在没有待处理事件时自动进入 Level 1 Sleep;当用户主动请求深睡眠时,执行 Level 2 的进入和恢复流程。

整体运行流程为:

系统初始化 → 主循环事件处理 → RTC/按键/串口业务 → 低功耗状态判断 → Sleep/Deep Sleep → 硬件中断唤醒 → 返回主循环。

/* 主循环事件处理与低功耗状态机框架 */
for (;;)
{
    /* 1. 处理串口交互命令 (set / sleep / help) */
    if (s_cmdReady) {
        ProcessSerialCommand(s_cmdBuf);
    }


    /* 2. Level 2: 硬件深度休眠状态判断与事件分发 */
    if (s_clockMode == kClockMode_DeepSleep) {
        if (IsSW2WakeupEvent()) {
            ResumeNormalFromDeepSleep(); // 唤醒恢复:点亮 OLED、开启秒更新
        } else {
            EnterDeepSleepMode();        // 保持休眠或过滤偶发唤醒后重入
        }
        continue;
    }


    /* 3. Level 1: 正常走时状态业务处理 */
    if (s_clockMode == kClockMode_Normal) {
        if (s_secTick) {
            s_secTick = false;
            IRTC_GetDatetime(RTC, &currentDatetime);
            PrintCurrentTime(&currentDatetime);
            OLED_ShowClock(&currentDatetime, "CLOCK RUNNING");
        }
        /* 空闲检测:无按键、无待发串口、无秒事件时进入 Level 1 Sleep */
        if (!s_secTick && (s_buttonFlags == 0U) && !s_cmdReady) {
            EnterLightSleepMode();
        }
    } else {
        /* 4. 按键调时模式 (时/分/秒编辑与 15s 超时托管) */
        HandleSettingModeStateMachine();
    }
}

3.2 RTC 定时唤醒与 Level 1 Sleep

IRTC 通过片内 FRO16K (16.384kHz)提供硬件时间基准,并使能 1Hz 秒中断。每当秒事件发生,RTC_IRQHandler() 清除中断标志,将 s_secTick 置位:

/*!
    * @brief IRTC 1Hz 周期中断服务函数
    */
void RTC_IRQHandler(void)
{
    uint32_t flags = IRTC_GetStatusFlags(RTC);
    if (0U != (flags & kIRTC_1hzFlag))
    {
        s_secTick = true;                   // 投递 1Hz 秒更新事件
        IRTC_SetWriteProtection(RTC, false);// 解锁寄存器写保护
        IRTC_ClearStatusFlags(RTC, flags);  // 清除硬件中断状态标志 (W1C)
    }
    SDK_ISR_EXIT_BARRIER;
}


业务完成后,程序首先检查 UART 发送完成标志,确保最后一个字符已经完全发出;随后暂停 SysTick,设置 CMC 仅门控 CPU 核心时钟,并清除 ARM SLEEPDEEP 位,执行 WFI 进入普通 Sleep。唤醒后重新使能 SysTick,返回主循环继续处理事件:

/*!
    * @brief Level 1 轻睡眠模式:仅门控 CPU Core Clock,外设与总线时钟保持开启
    */
static void EnterLightSleepMode(void)
{
    /* 1. 确保调试控制台 UART FIFO 彻底排空,避免传输中断产生乱码 */
    while (!(kLPUART_TransmissionCompleteFlag &
                LPUART_GetStatusFlags((LPUART_Type *)BOARD_DEBUG_UART_BASEADDR))) { }
    /* 2. 暂停 SysTick 定时器,防止 1ms 周期中断频繁打断系统休眠 */
    SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
    /* 3. 配置 CMC:仅门控内核时钟 (CKMODE=1),电源域维持 Active/Sleep */
    CMC0->CKCTRL    = CMC_CKCTRL_CKMODE(1U);
    CMC0->PMCTRL[0] = CMC_PMCTRL_LPMODE(0U);
    CMC0->PMCTRL[1] = CMC_PMCTRL_LPMODE(0U);
    /* 4. ARM Cortex-M33 睡眠深度配置 (SLEEPDEEP = 0) */
    SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
    /* 5. 内存同步屏障并执行睡眠指令 */
    __DSB();
    __WFI(); // CPU 停止运行,等待下一个 1Hz RTC 或按键中断唤醒
    __ISB();
    /* 6. 中断唤醒后恢复 SysTick */
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
}

这一方式将持续运行的时钟程序转换为硬件中断触发、按需执行任务、空闲自动休眠的运行机制,避免 CPU 在两次时间更新之间持续空转。

3.3 CMC 硬件 Deep Sleep

用户发送串口 sleep 命令,或在正常运行状态下长按板载 SW2/SW3 约 1.5 秒,即可请求进入硬件 Deep Sleep。

程序先关闭 OLED 显示,等待按键完全释放并执行 50ms 消抖,然后暂停 SysTick 和 IRTC 1Hz 中断,同时清除 RTC、GPIO、WUU 及 NVIC 中相应的待处理标志。

严格遵循 NXP 官方 AN14317 应用笔记的寄存器时序配置 CMC 与内核寄存器:

/*!
    * @brief Level 2 硬件深度休眠 (完全遵循 NXP AN14317 规范)
    */
static void EnterDeepSleepMode(void)
{
    /* 1. 等待串口发送缓冲区排空 */
    while (!(kLPUART_TransmissionCompleteFlag &
                LPUART_GetStatusFlags((LPUART_Type *)BOARD_DEBUG_UART_BASEADDR))) { }
    SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;

    /* 2. 确保 SW2 和 SW3 实体按键均彻底释放并完成 50ms 防抖 */
    while ((GPIO_PinRead(BOARD_SW2_GPIO, BOARD_SW2_GPIO_PIN) == 0U) ||
            (GPIO_PinRead(BOARD_SW3_GPIO, BOARD_SW3_GPIO_PIN) == 0U)) {
        SDK_DelayAtLeastUs(10000, SystemCoreClock);
    }
    SDK_DelayAtLeastUs(50000, SystemCoreClock);

    /* 3. 暂停 IRTC 1Hz 秒中断 (RTC 内部 FRO16K 持续走时,但切断 CPU 秒唤醒源) */
    IRTC_DisableInterrupts(RTC, kIRTC_1hzInterruptEnable);
    IRTC_ClearStatusFlags(RTC, kIRTC_1hzFlag);
    NVIC_ClearPendingIRQ(RTC_IRQn);

    /* 4. 清除 WUU、GPIO 及 NVIC 中断挂起标志与历史诊断缓存 */
    WUU0->PF = (1UL << 4U); // 清除 WUU0_IN4 标志位
    GPIO_GpioClearInterruptChannelFlags(BOARD_SW2_GPIO,
            (1UL << BOARD_SW2_GPIO_PIN) | (1UL << BOARD_SW3_GPIO_PIN), 0U);
    NVIC_ClearPendingIRQ(WUU_IRQn);
    NVIC_ClearPendingIRQ(GPIO00_IRQn);

    s_lastWakeSource = 0U; s_lastWakeWuuFlags = 0U; s_isrWuuFlags = 0U;

    /* 5. AN14317 核心寄存器配置:
        *    - PMPROT: 允许 Deep Sleep (0x1)
        *    - CKCTRL: 门控 Core/System/Bus 全部时钟 (必须为 0xF)
        *    - PMCTRL[0/1]: MAIN 和 WAKE 电源域均设置为 Deep Sleep (0x1) */
    CMC0->PMPROT    = CMC_PMPROT_LPMODE(1U);
    CMC0->CKCTRL    = CMC_CKCTRL_CKMODE(0xFU);
    CMC0->PMCTRL[0] = CMC_PMCTRL_LPMODE(1U);
    CMC0->PMCTRL[1] = CMC_PMCTRL_LPMODE(1U);

    (void)CMC0->PMCTRL[0]; // 回读寄存器,确保写缓冲完全排空 (Write-Drain)

    /* 6. 配置 ARM 内核 SLEEPDEEP */
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;

    /* 7. 进入芯片硬件 Deep Sleep */
    __DSB();
    __WFI();
    __ISB();

    /* 8. 唤醒瞬间立即快照底层 CMC 与 WUU 诊断寄存器 */
    s_lastWakeSource   = CMC_GetWakeupSource(CMC0); // 读取 CKSTAT[WAKEUP]
    s_lastWakeWuuFlags = WUU0->PF;

    /* 9. 恢复系统正常时钟配置 */
    SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
    CMC0->CKCTRL    = CMC_CKCTRL_CKMODE(1U);
    CMC0->PMCTRL[0] = CMC_PMCTRL_LPMODE(0U);
    CMC0->PMCTRL[1] = CMC_PMCTRL_LPMODE(0U);

    /* 10. 恢复 IRTC 1Hz 秒更新中断并重新使能 SysTick */
    IRTC_ClearStatusFlags(RTC, kIRTC_1hzFlag);
    NVIC_ClearPendingIRQ(RTC_IRQn);
    IRTC_EnableInterrupts(RTC, kIRTC_1hzInterruptEnable);

    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
}

3.4 WUU 按键唤醒与可靠恢复

项目使用 SW2 对应的 P0_20 / WUU0_IN4 作为 Deep Sleep 的专用唤醒输入,并将该 WUU 通道配置为仅在低功耗模式下有效。

进入休眠前,程序清除历史按键事件和唤醒诊断标志,避免此前的 GPIO 中断或按键释放毛刺影响下一次唤醒判断。

WFI 返回后,系统记录 CMC 硬件唤醒来源及 WUU 原始状态,并严格检查 WUU0_IN4 对应的事件,而不是将所有 WUU 请求都视为 SW2 按键。

/* 主循环中对 Deep Sleep 退出原因的判定与错误恢复过滤 */
if (s_clockMode == kClockMode_DeepSleep)
{
    /* 严格隔离:仅当确认为 SW2 (WUU0_IN4) 触发时才执行正常唤醒 */
    bool isSW2Wake = ((s_buttonFlags & (1UL << BOARD_SW2_GPIO_PIN)) != 0U) ||
                        ((s_isrWuuFlags & (1UL << 4U)) != 0U) ||
                        ((s_lastWakeWuuFlags & (1UL << 4U)) != 0U);
    if (isSW2Wake) {
        s_buttonFlags &= ~(1UL << BOARD_SW2_GPIO_PIN);
        s_isrWuuFlags = 0U;
        s_clockMode = kClockMode_Normal;
        OLED_DisplayOn(); // 点亮 OLED
        IRTC_GetDatetime(RTC, &currentDatetime);
        PRINTF("[WAKE UP] Valid SW2 (WUU0_IN4) event! CMC=0x%02X\r\n", s_lastWakeSource);
        OLED_ShowClock(&currentDatetime, "CLOCK RUNNING");
        continue;
    }
    /* 若未产生按键事件,则输出底层硬件诊断日志并调用休眠函数 */
    if (!s_cmdReady) {
        EnterDeepSleepMode();
        /* WFI 返回后打印 CMC 诊断:分析 Bit4(WUU), Bit6(IRTC), Bit1(Debug) */
        PRINTF("[WAKE DIAG] CMC=0x%02X WUU_PF=0x%08X (IN4=%d)\r\n",
                s_lastWakeSource, s_lastWakeWuuFlags,
                (s_lastWakeWuuFlags & (1UL << 4U)) ? 1 : 0);
        /* 过滤非按键唤醒 (如调试探针访问或未决毛刺):保持屏幕熄灭并静默重入休眠 */
        if (!isSW2Wake) {
            PRINTF("[SPURIOUS WAKE] Non-SW2 event. OLED stays off, re-entering...\r\n");
            s_buttonFlags = 0U;
            s_secTick = false;
        }
    }
    continue;
}

3.5 串口与按键双通道校时

项目同时提供串口和板载按键两种时间设置方式。

串口支持通过 set YYYY-MM-DD HH:MM:SS 命令直接修改 RTC 时间,例如:

set 2026-10-10 19:30:00

系统校验输入后更新硬件 RTC,返回成功提示,并在 OLED 上显示 TIME SYNCED。

板载按键则通过 SW2 切换小时、分钟和秒设置状态,SW3 修改对应数值。OLED 使用局部反色光标突出当前编辑字段,并通过状态栏提示操作。

为兼顾精确微调与快速设置,SW3 支持短按递增及长按自动连发:按住约 400ms 后,每隔 120ms 连续递增一次。同时,系统设置 15 秒无操作自动保存退出,避免误操作后长期停留在设置模式。连续连发期间会更新活动时间,防止错误触发超时。两种调时方式最终均写入同一 IRTC 硬件时间源,保证串口与 OLED 显示一致。

四、测试结果

4.1 RTC 走时与双端同步

按下 RESET 按键后,系统重新启动,串口输出启动信息、SDK 版本和操作菜单,OLED 显示当前日期、星期及时间。

正常运行期间,串口每秒输出一次 RTC 时间,OLED 同步更新秒数。系统在每次业务执行完毕后自动进入 Level 1 Sleep,并由下一个 RTC 秒中断唤醒。

测试中两端显示能够同步递增,周期性休眠与刷新功能正常。


seq1.gif

4.2 串口与板载按键校时

通过串口发送 set 2026-10-10 19:30:00 后,系统返回更新时间成功信息,OLED 显示 TIME SYNCED,随后从设定时间继续运行。

使用板载按键时,SW2 可以依次切换设置字段,SW3 支持单次递增和长按连续调整,OLED 对当前字段进行反色高亮。完成设置后,时间保存至 IRTC,并恢复正常计时界面。

同时验证了长按自动连发与 15 秒无操作自动保存功能,确认系统能够在不依赖 PC 的情况下独立完成时间设置。


seq2.gif


seq3.gif

4.3 两级低功耗与按键唤醒

正常工作时,系统周期性进入 Level 1 Sleep,并由 RTC 1Hz 中断唤醒,完成显示更新与串口输出。

随后通过串口 sleep 命令或长按板载按键进入 Level 2 Deep Sleep。OLED 关闭显示,串口停止周期性输出,RTC 仍持续计时。

按下 SW2 后,系统通过 WUU0_IN4 唤醒,恢复 OLED 显示和每秒串口输出。恢复后的时间包含整个休眠期间经过的时间,没有重新从旧时间开始计数。

针对开发过程中出现的异常自动唤醒问题,完成 RTC 中断、WUU 配置与唤醒状态机修正后,实机测试已能够正常保持深度休眠,并通过 SW2 恢复运行。


seq4.gif

4.4 复位后时间保持

在正常计时过程中按下 RESET,系统重新启动后通过 RTC 有效性检查识别已有时间数据,不再强制写入默认时间。

串口输出相应的 RTC 时间保持提示,OLED 恢复后继续显示递增的当前时间,验证了系统复位后的 RTC 计时保持能力。

需要说明的是,该测试使用 MCU 复位,而非整板完全断电。备用电源供电下的长期断电保持能力尚未单独验证。


seq5.gif

五、遇到的主要问题

5.1 SysTick 周期中断影响休眠效果

最初的低功耗设计中,虽然主循环可以执行 WFI 指令,但系统默认启用的 SysTick 仍可能每 1ms 产生一次中断,导致 CPU 被频繁唤醒,无法实现预期的长时间休眠。

针对该问题,项目在休眠前暂时关闭 SysTick,唤醒后再恢复其计数功能。

经过调整,系统的周期性业务可以主要由 RTC 秒中断驱动,避免 SysTick 在空闲期间持续触发不必要的 CPU 活动。

5.2 OLED 上电初始化失败

在项目调试过程中,OLED 曾出现上电后无法显示的问题。进一步检查 I²C 通信返回值,发现设备地址探测阶段存在 NAK 错误,状态码为 902。

分析认为,MCXN236 启动较快,而部分 OLED 模块内部的上电复位需要额外稳定时间,导致 MCU 在显示屏尚未准备完成时就尝试通信。

为解决该问题,程序增加了 50ms 上电延时、失败重试以及后台自动重连机制,降低了显示屏启动时序差异对系统工作的影响。

5.3 休眠恢复后的 OLED 状态问题

OLED 关闭显示后,其内部显示与升压控制状态可能需要重新配置。如果恢复时仅发送普通显示开启命令,部分模块可能无法正常点亮。

针对常见 OLED 控制器之间的差异,驱动增加了 SSD1306 与 SH1106 对应的电荷泵控制命令,并在恢复显示后重新刷新当前 RTC 时间。

这样既能在静默模式中停止显示输出,也能在唤醒后恢复正常的时钟界面。

5.4 串口发送与休眠时序冲突

串口发送通常经过发送缓冲区和移位寄存器两个阶段。如果程序仅判断发送缓冲区为空,就立即进入休眠,仍可能存在最后一个字符尚未完全发送的问题。

因此,项目通过查询 kLPUART_TransmissionCompleteFlag,等待串口物理发送完成后再执行休眠操作,确保时间信息和命令响应能够完整输出。

附件下载
frdmmcxn236_irtc_pack.zip
工程代码
团队介绍
无
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号