任务介绍
本项目实现了基于 FRDM-MCXN236 开发板的电子时钟系统,通过片内 RTC 外设维持系统时间,支持 RESET 按键复位及 Sleep/Deep Sleep/Power Down 等多种低功耗模式唤醒后时间保持不丢失。主控通过 Zephyr Shell 子系统提供串口命令行接口,实现时间查询、时间/日期调整与低功耗模式切换;同时通过三线 GPIO bit-bang 协议驱动外接的 TM1638 模块,在 8 位数码管上实时显示"时分秒"与"年月日"两种状态,并复用模块自带的 8 个按键完成状态切换与数值调整,整体基于 Zephyr RTOS 多线程架构实现采集-维护-显示的完整闭环。
硬件平台

本次使用 NXP 推出的 FRDM-MCXN236 开发板,该板搭载 MCX N236 MCU,内核为 Arm® Cortex®-M33,具备独立电源域设计,其中 VBAT 域为 RTC、32kHz 晶振振荡器等模块单独供电,保证主电源掉电或系统复位后计时不中断,适用于低功耗工业仪表、便携式计量终端及需要长期保持时间基准的物联网节点等场景。板上通过 Arduino 兼容扩展头外接一块 TM1638 模块,该模块集成 8 位共阴数码管、8 个独立 LED 与 8 个按键,仅需 STB/CLK/DIO 三根信号线即可完成显示驱动与按键扫描。
主控设备:FRDM-MCXN236 开发板
- 搭载 MCX N236 MCU(Arm Cortex-M33 内核)
- 板载 MCU-Link 片上调试器,支持一键烧录与串口透传
- RTC、OSC_32K 位于 VBAT 独立电源域,复位/低功耗不影响计时
- 支持 Zephyr RTOS 多线程调度与电源管理(PM)框架
时间基准:片内 RTC 外设
- 通过 Zephyr
rtc_set_time/rtc_get_time标准 API 读写 rtc_time结构遵循 POSIXstruct tm惯例,年份存储为"自1900年起年数"- 上电首次检测年份超出 [2024, 2099] 合法区间即视为寄存器无效并写入默认时间
显示与输入终端:TM1638 数码管按键模块
- STB/CLK/DIO 三线 bit-bang 协议,不占用硬件 SPI 外设
- 8 位数码管支持独立寻址,7 段码表驱动数字 0~9 及小数点
- 8 个按键通过 0x42 读键命令一次性读取 4 字节并解码

任务分析与实现
本系统实现了基于 Zephyr RTOS 的多线程电子时钟平台。
方案框图(数据链路):
主要功能包括三层功能链路:
时间维护层
- RTC 上电自检:读取寄存器年份,超出合法区间则写入默认时间 2024-01-01 00:00:00
- VBAT 域持续供电,RESET 按键复位、Sleep/Deep Sleep/Power Down 唤醒后时间均保持
- 通过
enum pm_state配合pm_policy_state_lock_get/put控制可达的最深低功耗状态
命令交互层
- Zephyr Shell 注册
clock命令组,子命令get/set/setdate/sleep clock set HH MM SS保留日期部分仅更新时分秒,范围校验 0~23/0~59/0~59clock sleep <0|1|2>映射 Sleep/Deep Sleep/Power Down 三种电源状态,WAKEUP 按键中断唤醒
显示交互层
- TM1638 bit-bang 时序:写时 CLK 上升沿锁存 DIO,读时 CLK 下降沿采样
- 显示周期 50ms 刷新一次,时分秒模式小数点每 500ms 闪烁模拟冒号
- 按键二次确认去抖(间隔 80ms),仅 S8 边沿触发显示模式切换,杜绝误触发
任务与优先级划分:
任务名 | 优先级 | 栈大小 | 职责 |
clock_thread | 5 | 1024 字 | 每秒读取 RTC 并通过 LOG 输出时间日期 |
shell_thread | (系统默认) | (系统默认) | 处理串口命令,调用 RTC 读写与 PM 接口 |
display_thread | 7 | 2048 字 | TM1638 初始化、按键扫描去抖、数码管刷新 |
tm1638_test(调试用) | 6 | 1536 字 | 验证阶段使用,数字递增与按键回显 |
时间维护与显示链路的搭建过程
本项目基于 Zephyr MCXN236 板级支持包完成了 RTC 时间维护、Shell 命令交互与 TM1638 显示驱动的整合。以下详细描述关键流程与核心要点。
一、开发前的准备
1. 硬件环境
- 主控芯片:MCX N236(Arm Cortex-M33 内核)
- 开发板:FRDM-MCXN236
- 外设资源:
- 片内 RTC(VBAT 域)用于时间基准
- GPIO0/GPIO1/GPIO3 三组端口用于 TM1638 三线通信
- WAKEUP 按键(SW2)用于低功耗唤醒
- UART 用于 Shell 命令行交互
2. 软件环境
- RTOS:Zephyr(
CONFIG_PM、CONFIG_PM_DEVICE使能电源管理) - Shell:
CONFIG_SHELL+CONFIG_SHELL_BACKEND_SERIAL - 设备树:
zephyr,user单节点集中声明自定义 GPIO - 编译工具链:Zephyr SDK(GCC ARM Embedded)
二、核心搭建步骤
1. RTC 有效性判断与初始化
系统上电后先读取 RTC 当前寄存器值,若年份超出合法区间(寄存器上电默认值可能是任意"未来年份"),则判定为无效并写入默认时间:
int year = rtc_tm.tm_year + 1900;
bool rtc_invalid = (ret < 0) || (year < 2024) || (year > 2099);
if (rtc_invalid) {
rtc_tm.tm_year = 2024 - 1900;
rtc_tm.tm_mon = 0;
rtc_tm.tm_mday = 1;
rtc_set_time(rtc_dev, &rtc_tm);
}
2. Shell 命令组注册
通过 SHELL_STATIC_SUBCMD_SET_CREATE 将 get/set/setdate/sleep 挂载到统一的 clock 命令下,参数个数由 SHELL_CMD_ARG 强制校验:
SHELL_STATIC_SUBCMD_SET_CREATE(clock_cmds,
SHELL_CMD_ARG(get, NULL, "读取当前时间", cmd_clock_get, 1, 0),
SHELL_CMD_ARG(set, NULL, "clock set HH MM SS", cmd_clock_set, 4, 0),
SHELL_CMD_ARG(sleep, NULL, "clock sleep <0|1|2>", cmd_clock_sleep, 2, 0),
SHELL_SUBCMD_SET_END);
3. 低功耗状态锁定与唤醒
进入指定深度前,先锁定比目标更深的 PM 状态,避免系统越级进入更深模式,WAKEUP 按键 GPIO 中断置位标志后循环退出:
for (int i = mode_idx + 1; i < ARRAY_SIZE(pm_modes); i++)
pm_policy_state_lock_get(pm_modes[i], PM_ALL_SUBSTATES);
while (!wakeup_triggered) { k_sleep(K_MSEC(10)); }
4. TM1638 三线 bit-bang 时序
写字节时 CLK 低电平设置 DIO、上升沿锁存;读字节时切换 DIO 为带上拉输入,CLK 下降沿后采样:
gpio_pin_set_dt(&dev->clk, 0);
gpio_pin_set_dt(&dev->dio, (data >> i) & 0x01);
gpio_pin_set_dt(&dev->clk, 1); /* 上升沿锁存 */
5. 按键二次确认去抖
首次采样到非零按键后延时 80ms 二次采样,两次结果一致才视为有效按下,杜绝 GPIO 瞬态噪声误触发模式切换:
uint8_t sample1 = tm1638_get_keys(dev);
if (sample1 == 0) { ks->prev_keys = 0; return 0; }
k_sleep(K_MSEC(KEY_DEBOUNCE_MS));
uint8_t sample2 = tm1638_get_keys(dev);
if (sample1 != sample2) return 0;
代码详解
整体软件流程
一、RTC 与低功耗初始化序列
系统上电后按 RTC 校验 → WAKEUP 按键配置 → 显示/日志线程创建的顺序完成初始化:
rtc_init_time();
gpio_pin_interrupt_configure_dt(&wakeup_btn, GPIO_INT_EDGE_FALLING);
gpio_add_callback(wakeup_btn.port, &wakeup_cb_data);
k_thread_create(&clock_thread_data, ...);
二、TM1638 显示与按键处理线程
display_thread_fn 每周期读取 RTC、扫描按键、刷新心跳 LED 与数码管,S8 边沿切换显示模式,其余按键依据当前模式调用 adjust_time/adjust_date:
uint8_t new_key = key_scan(&disp_dev, &ks);
if (new_key & TM1638_KEY_S8) {
mode = (mode + 1) % DISP_MODE_COUNT;
}
if (mode == DISP_MODE_TIME) {
if (new_key & TM1638_KEY_S1) adjust_time(0, +1);
}
数码管显示仅解析到 2024~2099、0~23/0~59 等合法区间内的字段,年份调整超出边界时按回绕(wrap)处理而非截断,避免出现非法日期卡死。
三、时分秒/年月日显示渲染
show_time 将小时/分钟数字写入位 0~3,并用小数点位模拟冒号每 500ms 闪烁;show_date 则将 4 位年份与月日分别写入位 0~7,月、日末位小数点作为日期分隔符:
tm1638_display_digit(dev, 1, h % 10, colon);
tm1638_display_digit(dev, 3, m % 10, colon);
由于 TM1638 段码表中数字 "5" 与字母 "S" 共用相同的 7 段编码(0x6D),测试例程中用于提示"第几号按键按下"的 "S0X" 显示在视觉上会呈现为 "50X",属于正常现象而非显示错误。
效果展示
串口 Shell 交互界面

开发板运行状态

TM1638 数码管顶部实时刷新"时分秒"六位数字,小数点位每秒闪烁模拟冒号效果;按下 S8 后切换为"年月日"八位显示,LED7 同步点亮作为模式指示;按下 S1~S6 可在对应模式下对时/年、分/月、秒/日分别加减,调整结果立即写回 RTC 并在下一周期生效。
遇到的难题与解决办法
问题一:RTC 寄存器上电默认值被误判为合法时间,显示年份高达 2112。
解法:将原本仅判断下界的有效性检查 year < 2024 改为同时判断上下界的区间检查 year < 2024 || year > 2099,确保硬件寄存器的任意"未来垃圾值"都会被识别为无效并触发默认时间初始化。
问题二:TM1638 按键解码交叉错乱,实际按下 S2 却回显为 S3,其余按键依次错位。
解法:重新对照数据手册按键矩阵布局,发现同一字节的 bit0/bit4 分别对应 S1S4 与 S5S8 两组而非原先误用的 i*2/i*2+1 交叉映射,修正为 bit0→keys[i]、bit4→keys[i+4] 后按键编号与物理按键一一对应。
活动感想
通过本项目实践,深入掌握了在 Zephyr RTOS 平台上完成 RTC 时间维护、Shell 命令行交互与自定义外设 bit-bang 驱动协同工作的完整流程,理解了 VBAT 电源域对时间保持的作用、PM 状态锁定与唤醒源配置的具体机制,以及设备树 zephyr,user 节点声明自定义 GPIO 的标准写法。整个开发过程中,需重点关注:
- RTC 读回数据与 POSIX
struct tm年份/月份偏移约定的双向转换 - 低功耗状态锁定范围与 WAKEUP 中断唤醒逻辑的正确配合
- 按键去抖与显示模式切换的单一触发源约束,避免噪声误触发
FRDM-MCXN236 良好的电源域隔离设计与 Zephyr 完善的 RTC/PM/Shell 子系统支持,加上 TM1638 简洁的三线协议,使得在资源有限的 MCU 上实现时间保持可靠、交互直观的电子时钟系统成为可能。感谢本次实践机会,让我对嵌入式实时时钟系统与低功耗电源管理的软硬件协同设计有了更深刻的理解。