基于FRDM-MCXN236的电子时钟设计
一、项目介绍
1.1 项目背景

本项目是参加硬禾科技联合DigiKey发起的Funpack活动第五季第4期(Funpack S5 #4)。本期板卡是来自NXP的FRDM-MCXN236开发板,核心是一颗MCX N236微控制器,搭载Arm Cortex-M33内核,主频最高150MHz,配备1MB双组闪存和352KB ECC RAM,集成SmartDMA加速器。
1.2 项目概述

本项目基于FRDM-MCXN236开发板,实现了一个完整的电子时钟系统。系统利用芯片内置的独立RTC(IRTC)模块进行精准计时,通过板载按键实现年、月、日、时、分、秒的全字段时间调整,并通过串口实时输出当前时间。按下复位按键后,IRTC时间能够继续保持,不会丢失。
本项目对应Funpack S5 #4活动任务1——电子时钟设计。
1.3 功能需求
根据活动任务1要求,本项目实现以下功能:
- 使用RTC获取系统时间
- 显示当前时、分、秒信息(通过串口输出)
- 使用按键实现时间调整
- 通过串口输出当前时间
- 掉电后时间能够保持(通过复位按键验证)
在基础要求之上,还实现了以下增强功能:
- 全字段时间调整:支持年、月、日、时、分、秒六个字段的独立调整
- 短按/长按双模式按键:SW2短按循环切换调整字段,长按保存并退出;SW3短按+1,长按-1
- 串口高频输出:每0.5秒输出一次当前时间
- 软件去抖与状态机:基于SysTick的按键扫描机制,实现4ms去抖和1秒长按判定
1.4 项目成果
- ✅ 深入理解FRDM-MCXN236的IRTC模块及其复位保持机制
- ✅ 掌握MCUXpresso SDK在VS Code环境下的开发流程
- ✅ 实现基础任务:基于IRTC的电子时钟,支持串口时间输出和复位后时间保持
- ✅ 实现全字段时间调整,支持年月日时分秒六个字段的独立修改
- ✅ 实现短按/长按双模式按键交互
二、硬件介绍
2.1 微控制器:MCX N236
MCX N236是NXP MCX N系列的高性能微控制器,基于Arm Cortex-M33内核,主频最高150MHz。芯片配备1MB双组闪存(支持完整ECC)和352KB ECC RAM,集成SmartDMA加速器。在安全方面,内置EdgeLock安全区域,提供硬件级保护。

2.2 FRDM-MCXN236开发板
FRDM-MCXN236开发板的主要板载资源包括:
- MCU-Link调试器:通过USB-C一线完成供电、调试和串口通信,支持VCOM虚拟串口
- 用户按键:SW1为复位按键,SW2(P0_20)为唤醒按键,SW3(P0_6)为ISP模式开关(可作普通GPIO输入使用)
- RGB LED:用于状态指示
- 扩展接口:支持Arduino、mikroBUS、FRDM接头等多种扩展接口
2.3 引脚分配
本项目使用的引脚分配如下:
功能 | 引脚 | 说明 |
|---|---|---|
SW2按键 | P0_20 | 模式切换/保存退出 |
SW3按键 | P0_6 | 数值增减 |
串口TX | P1_8 | LPUART4,通过MCU-Link输出 |
串口RX | P1_9 | LPUART4 |
复位按键 | SW1 | 复位MCU,验证时间保持 |
三、方案框图和项目设计思路
3.1 系统方案框图

系统方案框图展示了电子时钟的整体架构与数据流向。核心模块包括IRTC、主循环、LPUART4、SysTick和按键。IRTC作为时间基准,由OSC32K提供精准时钟源,负责日历计时;主循环负责读取IRTC当前时间、处理按键事件,并通过LPUART4将时间信息输出至PC串口终端。SysTick定时器产生1ms中断,一方面累计计数实现0.5秒的时间更新标志置位,另一方面执行按键扫描,完成去抖和短按/长按判定。按键SW2(P0_20)和SW3(P0_6)通过GPIO连接,经SysTick扫描后产生短按或长按事件,主循环据此进行时间调整状态机的切换与数值增减。调整完成后,新时间写回IRTC,实现掉电保持。整体构成一个以IRTC为时间基准、SysTick为调度时基、按键为人机交互、串口为输出通道的电子时钟系统。
3.2 项目设计思路
IRTC初始化
MCXN236的IRTC初始化有一个关键时序要求:RTC的CTRL[CLK_SEL]位默认选择FRO16K作为时钟源,FRO16K必须在切换到OSC32K之前保持使能,否则访问任何RTC寄存器都会导致系统挂起。因此初始化顺序为:
- 同时使能FRO16K和OSC32K到VBAT域和Main域
- 使能RTC时钟
- 配置
irtcConfig.clockSelect = kIRTC_Clk32K - 调用
IRTC_Init()完成初始化
每秒时间更新
由于IRTC驱动不支持独立的秒中断枚举,本项目采用SysTick定时器产生1ms中断,在中断中累计计数,每500次置位时间更新标志,实现0.5秒的串口输出周期。
按键处理机制
按键采用SysTick轮询方式,在1ms中断中执行扫描:
- 去抖:使用4位移位寄存器,连续4次采样一致才认为状态稳定
- 短按判定:按下后1000ms内释放,触发短按事件
- 长按判定:持续按下超过1000ms,触发长按事件
时间调整状态机
系统采用7状态状态机:
NORMAL → ADJUST_YEAR → ADJUST_MONTH → ADJUST_DAY
→ ADJUST_HOUR → ADJUST_MINUTE → ADJUST_SECOND → NORMAL(保存)
SW2短按循环切换调整字段,SW2长按保存并退出;SW3短按当前字段+1,SW3长按当前字段-1。
复位后时间保持
IRTC由独立时钟源供电,复位MCU时IRTC继续运行。程序初始化时检查年份范围(2020-2100),若超出范围则设置默认时间,否则保留原有时间。按下复位按键SW1后,程序重新运行,串口输出显示时间从复位前继续计时。
四、开发环境、软件流程图及关键代码
4.1 开发环境
- 硬件:FRDM-MCXN236开发板、Type-C USB线
- IDE:Visual Studio Code + MCUXpresso for VS Code扩展
- SDK:MCUXpresso SDK 26.09(FRDM-MCXN236)
- 工具链:Arm GNU Toolchain 14.2.rel1
- 构建系统:CMake + Ninja
- 串口终端:VS Code Serial Monitor,115200-8-N-1


4.2 软件流程图
4.2.1 主程序流程图

主程序流程图描述了电子时钟从上电到持续运行的整体控制流程。程序启动后,首先调用BOARD_InitHardware()完成板级基础初始化,接着依次初始化IRTC、SysTick定时器和按键GPIO。进入主循环后,程序不断检测两个标志:当SysTick累计达到0.5秒时置位的时间更新标志被触发,则读取IRTC当前时间并通过LPUART4输出;当按键扫描产生事件时,则进入时间调整状态机进行相应处理。处理完毕后回到循环起点,持续运行,实现时间的实时输出与按键响应。
4.2.2 按键扫描流程图(SysTick中断内)

按键扫描流程图展示了在SysTick 1ms中断中执行的按键去抖与长短按判定逻辑。中断服务程序首先读取SW2(P0_20)和SW3(P0_6)的当前电平,将电平移入4位移位寄存器进行软件去抖。当连续4次采样均为低电平时,判定为稳定按下,记录按下时刻并清除长按标志;当连续4次采样均为高电平时,判定为稳定释放,若此前未触发长按,则产生短按事件。同时,若按键持续按下时间达到或超过1000ms,则置位长按事件,并标记已处理,避免释放时重复触发短按。该流程以1ms为周期循环执行,确保按键响应的实时性与可靠性。
4.2.3 时间调整状态机

时间调整状态机定义了用户通过按键修改时间的交互逻辑。系统默认处于NORMAL状态,此时正常显示时间。短按SW2依次进入年、月、日、时、分、秒六个调整状态,每个状态对应一个字段的编辑;在任意调整状态下,短按SW3使当前字段值加1,长按SW3使当前字段值减1,并自动处理进位与循环(如月份12后回到1,小时23后回到0等)。当在秒调整状态下再次短按SW2,或在任意调整状态下长按SW2,则将修改后的时间写回IRTC并保存,随后返回NORMAL状态。该状态机确保了时间调整的灵活性与操作的直观性。
4.3 关键代码
IRTC初始化
static void IRTC_ClockInit(void)
{
irtc_config_t irtcConfig;
/* 关键:同时使能FRO16K和OSC32K,防止访问RTC寄存器挂起 */
CLOCK_SetupClk16KClocking(kCLOCK_Clk16KToVbat | kCLOCK_Clk16KToMain);
CLOCK_SetupOsc32KClocking(kCLOCK_Osc32kToVbat | kCLOCK_Osc32kToMain);
CLOCK_EnableClock(kCLOCK_Rtc0);
IRTC_GetDefaultConfig(&irtcConfig);
irtcConfig.clockSelect = kIRTC_Clk32K;
IRTC_Init(RTC, &irtcConfig);
/* 检查时间有效性,复位后保持原时间 */
IRTC_GetDatetime(RTC, &g_currentTime);
if (g_currentTime.year < 2020U || g_currentTime.year > 2100U) {
g_currentTime.year = 2026; g_currentTime.month = 10;
g_currentTime.day = 10; g_currentTime.weekDay = 6;
g_currentTime.hour = 14; g_currentTime.minute = 0;
g_currentTime.second = 0;
IRTC_SetWriteProtection(RTC, false);
IRTC_SetDatetime(RTC, &g_currentTime);
}
}
SysTick中断与按键扫描
void SysTick_Handler(void)
{
g_tick_count++;
if ((g_tick_count % 500U) == 0U) g_time_update_flag = true;
Button_Scan();
}
static void Button_Scan(void)
{
uint32_t sw2_level = (GPIO_PinRead(GPIO0, 20U) == 0U) ? 0U : 1U;
uint32_t sw3_level = (GPIO_PinRead(GPIO0, 6U) == 0U) ? 0U : 1U;
/* SW2去抖与长短按 */
g_sw2_history = ((g_sw2_history << 1) | sw2_level) & 0x0F;
if (g_sw2_history == 0U) {
if (!g_sw2_pressed) {
g_sw2_pressed = true;
g_sw2_press_tick = g_tick_count;
g_sw2_long_handled = false;
}
} else if (g_sw2_history == 0x0F) {
if (g_sw2_pressed) {
if (!g_sw2_long_handled) g_key_event = KEY_EVENT_SW2_SHORT;
g_sw2_pressed = false;
}
}
if (g_sw2_pressed && !g_sw2_long_handled &&
(g_tick_count - g_sw2_press_tick) >= 1000U) {
g_sw2_long_handled = true;
g_key_event = KEY_EVENT_SW2_LONG;
}
/* SW3同理,略 */
}
时间调整状态机处理
case KEY_EVENT_SW2_SHORT:
switch (g_clock_state) {
case CLOCK_STATE_NORMAL:
g_clock_state = CLOCK_STATE_ADJUST_YEAR;
g_adjustTime = g_currentTime;
break;
case CLOCK_STATE_ADJUST_YEAR:
g_clock_state = CLOCK_STATE_ADJUST_MONTH; break;
/* ... 依次切换 ... */
case CLOCK_STATE_ADJUST_SECOND:
IRTC_SetWriteProtection(RTC, false);
IRTC_SetDatetime(RTC, &g_adjustTime);
g_currentTime = g_adjustTime;
g_clock_state = CLOCK_STATE_NORMAL;
break;
}
break;
case KEY_EVENT_SW2_LONG:
if (g_clock_state != CLOCK_STATE_NORMAL) {
IRTC_SetWriteProtection(RTC, false);
IRTC_SetDatetime(RTC, &g_adjustTime);
g_currentTime = g_adjustTime;
g_clock_state = CLOCK_STATE_NORMAL;
}
break;
case KEY_EVENT_SW3_SHORT:
if (g_clock_state != CLOCK_STATE_NORMAL)
AdjustTime(&g_adjustTime, g_clock_state, +1);
break;
case KEY_EVENT_SW3_LONG:
if (g_clock_state != CLOCK_STATE_NORMAL)
AdjustTime(&g_adjustTime, g_clock_state, -1);
break;
五、功能展示
5.1 串口输出
系统启动后,串口终端显示初始化日志和当前时间:
========================================
Electronic Clock - FRDM-MCXN236
IRTC + Button + UART
========================================
[IRTC] Set default: 2026/10/10 14:00:00
System ready.
[2026-10-10 14:00:00] Electronic Clock Running
[2026-10-10 14:00:00] Electronic Clock Running
[2026-10-10 14:00:01] Electronic Clock Running
[2026-10-10 14:00:01] Electronic Clock Running
...
由于SysTick每0.5秒刷新一次,而IRTC精度为1秒,同一秒的时间会连续输出两次。
5.2 时间调整
短按SW2进入调整模式:
SW2 Pressed!
[SET MODE] Enter ADJUST_YEAR
短按SW3递增当前字段:
SW3 Pressed!
[SET] YEAR: 2027
长按SW3递减当前字段:
SW3 Pressed!
[SET] YEAR: 2026
短按SW2切换字段:
SW2 Pressed!
[SET MODE] Enter ADJUST_MONTH
长按SW2保存并退出:
SW2 Pressed!
[SET MODE] Time saved to IRTC
[SET MODE] Long press: Exit to NORMAL (saved)
[2026-11-10 14:00:15] Electronic Clock Running
5.3 复位后时间保持
在正常计时状态下,按下开发板上的复位按键SW1,MCU复位重新运行程序。串口输出显示:
[IRTC] Restored: 2026/10/10 14:05:30
System ready.
[2026-10-10 14:05:31] Electronic Clock Running
[2026-10-10 14:05:31] Electronic Clock Running
[2026-10-10 14:05:32] Electronic Clock Running
可以看到,复位后IRTC时间并未重置为默认值,而是从复位前的时刻继续计时,验证了时间保持功能正常。
六、遇到的主要难题及解决方法
6.1 编译器cc1错误
问题:VS Code下构建工程报错cannot execute 'cc1': CreateProcess: No such file or directory。
解决方法:通过MCUXpresso Installer重新安装Arm GNU Toolchain,并确保ARMGCC_DIR环境变量指向工具链根目录。
6.2 IRTC初始化挂起
问题:IRTC初始化后系统挂起,串口仅输出标题后停止。
原因:RTC的CTRL[CLK_SEL]位默认选择FRO16K作为时钟源,若FRO16K未使能,访问RTC寄存器会挂起。
解决方法:在初始化IRTC之前,同时使能FRO16K和OSC32K到VBAT域。
6.3 IRTC秒中断枚举不存在
问题:编译报错'kIRTC_SecondsInterruptEnable' undeclared。
解决方法:改用SysTick定时器产生1ms中断,累计500次后置位时间更新标志。
6.4 SysTick重载值超出24位
问题:SysTick_Config(SystemCoreClock / 2U)失败,因为150MHz/2=75,000,000超出24位上限。
解决方法:改为1ms中断,在中断中累加计数。
6.5 SW2按键无响应
问题:SW3正常,SW2(P0_20)无反应。
原因:pin_mux.c中未配置P0_20的GPIO复用功能。
解决方法:在BOARD_InitPins()中添加P0_20的PORT配置,设为kPORT_MuxAlt0(GPIO功能),并使能内部上拉。
七、心得体会
通过本次Funpack活动,我深入学习了NXP MCX N系列微控制器的开发流程。FRDM-MCXN236开发板配合MCUXpresso SDK和VS Code环境,提供了从底层驱动到应用开发的完整工具链。
在电子时钟项目的开发过程中,我不仅完成了活动任务要求的全部功能,还实现了全字段时间调整、短按/长按双模式按键等增强功能。特别是复位后时间保持功能的实现,让我对IRTC的独立供电域和复位行为有了深刻理解。
调试过程中遇到的cc1编译错误、IRTC初始化挂起、SysTick重载值超限等问题,锻炼了我分析问题和查阅技术资料的能力。通过NXP社区、SDK文档和官方例程,我逐步定位并解决了每一个问题,这种“遇到问题-分析问题-解决问题”的循环是嵌入式开发中最宝贵的经验。
FRDM-MCXN236的丰富外设和MCUXpresso生态让我能够快速搭建原型系统,从IRTC计时到UART通信,再到GPIO按键处理,整个开发流程高效而流畅。期待未来能基于这块开发板探索更多应用场景。