Funpack5-4 - 基于 FRDM-MCXN236 开发板,实现一个电子时钟系统
该项目使用了FRDM-MCXN236,实现了电子时钟的设计,它的主要功能为:使用 RTC 获取系统时间显示当前时、分、秒信息,利用按键调整时间,时间可通过串口输出和LCD显示, 能够掉电保持。
标签
嵌入式系统
Funpack活动
开发板
57SSG
更新2026-10-19
23

基于 FRDM-MCXN236 的数字时钟设计与实现报告

1. 任务

本项目基于 NXP FRDM-MCXN236 开发板和 MCUXpresso SDK,实现一套具有掉电计时保持、LCD 日期时间显示和双按键设置功能的数字时钟系统。系统使用片内 IRTC 保存日期时间,通过 FLEXIO0 模拟 SPI 总线驱动 ST7789 LCD,并通过板载 SW2、SW3 完成日期时间设置。

1.1 任务需求

  1. 使用 RTC 获取系统时间
  2. 显示当前时、分、秒信息
  3. 使用按键实现时间调整
  4. 通过串口输出当前时间
  5. 掉电后时间能够保持

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 方案框图

image.png

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 掉电保持的实测图片,完成最终硬件验收。

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