基于 FRDM-MCXN236 的数字时钟设计与实现报告
1. 任务
本项目基于 NXP FRDM-MCXN236 开发板和 MCUXpresso SDK,实现一套具有掉电计时保持、LCD 日期时间显示和双按键设置功能的数字时钟系统。系统使用片内 IRTC 保存日期时间,通过 FLEXIO0 模拟 SPI 总线驱动 ST7789 LCD,并通过板载 SW2、SW3 完成日期时间设置。
1.1 任务需求
- 使用 RTC 获取系统时间
- 显示当前时、分、秒信息
- 使用按键实现时间调整
- 通过串口输出当前时间
- 掉电后时间能够保持
1.2 完成的功能
工程已经实现以下功能:
* 使用 RTC 获取并维护当前日期时间。
* LCD 同时显示年、月、日、时、分、秒。
* SW3 长按 300 ms 进入或退出时钟设置模式,长按时间可配置。
* SW2 长按 300 ms 切换当前设置字段。
* SW2 短按使当前字段加 1,SW3 短按使当前字段减 1。
* 通过串口输出启动、设置和显示信息。
2. 实现思路
2.1 软件思路
本工程采用裸机轮询结构,不使用 RTOS。主循环以 5 ms 为基本周期,依次完成按键采样、事件消费、RTC 读取、设置状态更新和 LCD 刷新。
软件分为五个层次:
应用层:RTC 初始化、设置状态机、日期合法性处理、主循环和串口日志
输入层:GPIO 初始化、20 ms 去抖、短按/长按事件识别
显示层:界面布局
LCD 协议层:ST7789驱动
硬件适配层:FLEXIO SPI、DC/RST/BL GPIO 与通用 LCD 总线回调的绑定
按键短按和长按不能在“刚按下”时同时确定,因此采用以下判定方式:
- 稳定按下后开始累计保持时间。
- 达到 300 ms 时立即产生一次长按事件,并记录该次按压已经上报长按。
- 如果在达到 300 ms 前释放,则在释放完成去抖后产生一次短按事件。
- 长按释放时不再产生短按事件,从而避免一次长按执行两种操作。
设置模式使用 `s_editTime` 保存待设置日期时间。进入设置模式时从当前 RTC 时间复制;短按只修改 `s_editTime`;退出设置模式时重新计算星期并调用 `IRTC_SetDatetime()` 写回 RTC。
2.2 方案框图

2.3 硬件思路
开发板使用 MCXN236 Cortex-M33 MCU。没有留出VBAT接口, VBAT供电接口与MCU供电接在一起。因此掉电支持使用按下复位按键模拟,在不修改开发板的基础上,不能真实掉电。LCD的通信使用FLEXIO作为SPI进行。DC、RST 和背光信号使用普通 GPIO 控制。
按键 SW2、SW3 一端接地,输入端使用内部上拉,因此:
- 释放时 GPIO 读取为高电平;
- 按下时 GPIO 读取为低电平;
- 软件中以低电平作为 `pressed=true`。
3. 代码实现
3.1 `main.c`——系统初始化与主循环
系统入口依次完成板级硬件、RTC、LCD 和按键初始化,然后进入 5 ms 主循环:
BOARD_InitHardware();
RTC_AppInit();
DISPLAY_Init();
BUTTON_Init();
while (1U)
{
BUTTON_Update(MAIN_LOOP_DELAY_MS);
sw2Event = BUTTON_GetEvent(kBUTTON_SW2);
sw3Event = BUTTON_GetEvent(kBUTTON_SW3);
if (!s_settingActive)
{
RTC_AppGetTime(&s_currentTime);
}
CLOCK_HandleButtons(sw2Event, sw3Event);
/* 根据正常/设置模式选择显示 RTC 时间或编辑副本 */
/* 仅在日期时间或闪烁隐藏字段变化时刷新 LCD */
DelayMs(MAIN_LOOP_DELAY_MS);
}
通过比较 `s_lastDisplayedTime` 和 `s_lastHiddenField`,程序不会在每次 5 ms 循环中重复刷新屏幕,而只在日期时间变化、设置值变化或闪烁状态变化时刷新。
3.2 RTC 第一次运行检测
上电后, 读取RTC判断时间是否与RTC复位值相同, 若相同, 则认为是第一次运行。若是第一次运行,则设置默认时间,反之,则不设置时间。
static bool IfSettedTime(void)
{
irtc_datetime_t hardCodeTime = {.year = HARD_CODE_TIEM_YEAR,
.month = HARD_CODE_TIEM_MONTH,
.day = HARD_CODE_TIEM_DAY,
.weekDay = HARD_CODE_TIEM_WEEKDAY,
.hour = HARD_CODE_TIEM_HOUR,
.minute = HARD_CODE_TIEM_MINUTE,
.second = HARD_CODE_TIEM_SECOND};
irtc_datetime_t rtcTime;
RTC_AppGetTime(&rtcTime);
if (memcmp(&rtcTime, &hardCodeTime, sizeof(hardCodeTime)) == 0)
{
return false;
}
return true;
}
3.3 `button.c`——按键消抖和长短按识别
按键配置参数如下:
#define BUTTON_DEBOUNCE_MS (20U)
#ifndef BUTTON_LONG_PRESS_MS
#define BUTTON_LONG_PRESS_MS (300U)
`BUTTON_Update()` 对两个按键分别维护原始状态、稳定状态、稳定时间、保持时间和长按已上报标志。长按达到阈值时立即上报一次;短按只在释放并完成去抖后上报:
if (s_stablePressed[id] && !s_longPressReported[id])
{
const uint32_t remainingMs = BUTTON_LONG_PRESS_MS - s_holdMs[id];
s_holdMs[id] += (elapsedMs < remainingMs) ? elapsedMs : remainingMs;
if (s_holdMs[id] >= BUTTON_LONG_PRESS_MS)
{
s_longPressReported[id] = true;
s_event[id] = kBUTTON_EventLongPress;
}
}
事件通过 `BUTTON_GetEvent()` 读取并清除,确保一次动作只被主循环消费一次。
3.4 设置模式控制
按键业务逻辑集中在 `CLOCK_HandleButtons()` 中:
if (sw3Event == kBUTTON_EventLongPress)
{
if (s_settingActive)
{
CLOCK_ExitSetting();
}
else
{
CLOCK_EnterSetting();
}
return;
}
if (s_settingActive && (sw2Event == kBUTTON_EventLongPress))
{
s_selectedField = (display_field_t)((uint32_t)s_selectedField + 1U);
if (s_selectedField >= kDISPLAY_FieldCount)
{
s_selectedField = kDISPLAY_FieldYear;
}
}
进入设置模式时保存当前 RTC 时间到 `s_editTime`,默认选中“年”;退出时调用 `RTC_AppSetTime()` 保存。如果 IRTC 拒绝写入,设置模式不会被错误地标记为已保存。
4.功能展示
4.1 串口输出时间功能

4.2按键设置时间
4.2.1进入设置模式

4.2.2减小选中时间段

4.2.3增大选中时间段

4.2.4切换选中时间段

4.2.5退出设置模式

4.2.6掉电保持

5. 心得体悟和遇到的困难
5.1心得体悟
本工程让我进一步理解了嵌入式系统中模块边界的重要性。LCD 协议层只负责 ST7789 命令和像素数据,不直接操作 MCXN236 寄存器;硬件适配层只负责把通用总线回调绑定到 FLEXIO SPI 和 GPIO。这样更换 MCU 或 SPI 外设时,不需要重写整个 LCD 协议层。
按键处理中,短按并不是“按下瞬间”就能确认的。只有等待释放,才能确定本次按压没有达到长按阈值。因此把短按事件放在释放阶段生成,是保证长按和短按互斥的关键。
设置日期比设置时分秒更复杂。月份天数不同,二月还受到闰年影响。如果只对数字简单加减,可能产生 2025-02-29、04-31 等无效日期,导致 IRTC 写入失败。因此日期调整必须同时维护日历约束和星期字段。
闪烁提示不需要额外引入硬件定时器或 RTOS 任务。本工程已有固定 5 ms 主循环,只需累计经过时间,并在半周期到达时切换隐藏字段即可完成稳定的 1 s 闪烁效果。
5.2 遇到的困难
5.2.1 长按结束后又触发短按
最容易出现的问题是按住 300 ms 已经执行长按操作,释放时又执行一次短按操作。解决方法是为每个按键增加 `s_longPressReported` 标志:长按上报后置位,释放时只有该标志为假才生成短按事件。
5.2.2 调整年份或月份后日期失效
例如从 2024-02-29 调整到 2025 年,如果日期仍保留 29 日,IRTC 会拒绝该日期。解决方法是在年份或月份变化后重新计算当月最大天数,并把超出的日期限制到当月最后一天。
5.2.3 设置过程中 RTC 仍在计时
如果每次按键都直接读取并写回 RTC,正在编辑的字段可能与不断变化的秒值互相干扰。工程采用编辑副本:进入设置时复制一次,设置期间冻结编辑值,退出时统一写回。这使设置过程可预测,也减少了 RTC 写操作。
5.2.4 LCD 命令和数据传输容易混淆
ST7789 使用 DC 引脚区分命令和数据。单字节传输看起来相同,但命令必须在 DC 低电平下发送,参数和像素必须在 DC 高电平下发送。实现时分别保留 `LCD2Inch_WriteCommand()` 和数据写接口,避免错误复用导致屏幕初始化或像素输出异常。
6. 总结
本工程完成了基于 FRDM-MCXN236、IRTC 和 ST7789 LCD 的数字时钟软件设计。系统能够显示完整日期时间,通过两个按键完成六个字段的选择和加减设置,并用闪烁提示当前字段。软件实现考虑了按键去抖、长短按互斥、闰年和月末合法性、RTC 写保护、显示增量刷新以及日期字模扩展接口。
当前软件构建和逻辑测试已经通过。下一步需要在开发板上烧录固件,补充 LCD 显示、按键设置、串口日志和 RTC 掉电保持的实测图片,完成最终硬件验收。