Funpack5-4 - 基于FRDM-MCXN236实现电子时钟设计
该项目使用了FRDM-MCXN236开发板,实现了电子时钟的设计,它的主要功能为:使用RTC获取系统时间且通过串口显示。
标签
嵌入式系统
Funpack活动
开发板
Bread
更新2026-10-19
3

基于FRDM-MCXN236实现电子时钟设计

一、项目概述

本项目基于 NXP FRDM-MCXN236 开发板,完成电子时钟系统的设计与开发。按照任务要求,利用芯片内置 RTC 实时时钟模块获取系统时间,实现时、分、秒时间信息采集;通过板载 GPIO 按键完成时间校准调整;借助 UART 串口向上位机输出当前时间;依靠 RTC 后备电源域实现掉电之后(由于没有纽扣电池安装口,在本项目中采用复位按键即按下复位按键的时候模拟系统掉电)。

本项目核心功能:系统上电后 RTC 自动计时,硬件原生完成秒、分、时进位;使用两个按键进行时间设置,一个按键切换待调整字段(时 / 分 / 秒),另一个按键对选中字段数值递增;MCU 周期性读取 RTC 时间并通过串口打印时间字符串;断开开发板主电源后(按下复位按键),再次上电读取的时间不会丢失。

二、开发平台与硬件资源介绍

本次项目采用 NXP FRDM‑MCXN236 开发板作为硬件开发平台,配合 MCUXpresso IDE 软件开发环境,完成电子时钟系统的设计与调试。整个项目无需外接硬件模块,全部使用开发板原生外设资源,平台简洁、稳定,适合嵌入式实时时钟功能开发与验证。

2.1 板卡介绍

FRDM-MCXN236 是一款紧凑且可扩展的开发板,可让您快速基于MCX N23x MCU开展原型设计。它们提供行业标准的接口,可轻松访问MCU的I/O、集成的开放标准串行接口、外部闪存和板载MCU-Link调试器。通过MCUXpresso Developer Experience(MCUXpresso开发人员体验)提供其他工具,如面向附加板的扩展板中心和面向软件示例的应用代码中心。

image.png

FRDM Development Board for MCX N23x MCUs

其主控为 MCXN236 超低功耗通用 MCU,是 NXP 新一代 Cortex‑M33 内核单片机。芯片内置 硬件 RTC 实时时钟模块,支持独立计时域,在主芯片复位时不会清空 RTC 计时数据,非常适合本项目 “复位模拟掉电保持” 的实验需求。

相比传统外设,该芯片 RTC 模块具有以下优势:

  • 复位不清除时间寄存器,仅完全断电才清零;
  • 硬件自动完成秒、分、时、日、月、年进位;
  • 自带寄存器写保护机制,防止时间误改写;
  • 计时精度高,无需外部晶振电路。

同时芯片自带 GPIO、UART 等常用外设,满足按键输入、串口输出功能需求。

2.2 板载外设资源介绍

2.2.1 GPIO 按键输入模块

开发板自带两路可编程 GPIO 按键,本项目分别用于:

  • 按键 1:切换调时字段(秒 / 分 / 小时循环切换)
  • 按键 2:时间数值递增(对当前选中字段加 1)

程序采用软件周期扫描与消抖处理,保证按键识别稳定、无抖动重复触发。

2.2.2 UART 串口模块

板载 USB 虚拟串口,用于 MCU 与 PC 通信。程序每秒检测时间变化,通过串口向上位机输出格式化时间字符串,实现实时时钟显示功能,是本项目唯一的可视化输出窗口。

2.2.3 硬件复位按键

由于本项目未使用纽扣电池,因此采用开发板 硬件复位按键模拟掉电重启场景。
原理:

  • USB 不断电时,复位只会重启程序,不会重置 RTC 寄存器;
  • 利用该特性模拟 “设备掉电重启”,验证 RTC 时间保持能力;
  • 满足项目要求的 “掉电时间不丢失” 实验效果。

2.3 软件开发环境介绍

2.3.1 MCUXpresso IDE

MCUXpresso IDE 是 NXP 官方专为 Kinetis、LPC、MCX 系列 ARM 内核 MCU 推出的集成开发环境,基于 Eclipse 平台开发,是本项目 FRDM-MCXN236 开发板的核心开发软件。该软件集成了代码编辑器、编译器、链接器、Flash 下载器、在线调试器与外设配置工具,一站式完成嵌入式项目从工程创建、代码编写、编译链接、程序烧录到在线调试的全流程开发工作。

QQ_1791721752784.png

IDE 内置 MCUXpresso Config Tools 图形化配置工具,支持可视化配置芯片时钟树、GPIO、RTC、UART 等外设。开发者只需要在图形界面选择功能,软件就能够自动生成底层外设初始化代码,不用手动编写大量底层寄存器配置代码,大幅降低 MCXN 系列芯片的外设开发难度,提升开发效率。

QQ_1791721977349.png

同时 IDE 自带 GDB 调试功能,支持断点调试、变量实时查看、寄存器监视、内存查看等调试手段,在本次电子时钟项目开发中,用于排查 RTC 时间读取异常、按键抖动、串口输出异常等问题。

IDE 配套 NXP 官方的 MCU SDK 软件包,SDK 中封装好了 RTC、GPIO、UART 等外设驱动 API 函数,可以直接调用现成驱动接口,快速实现 RTC 读写时间、GPIO 按键检测、串口打印等功能,减少底层驱动开发工作量。软件兼容性强,对 FRDM-MCXN236 开发板原生支持,编译稳定,下载程序便捷,非常适合嵌入式课程实训项目开发。

QQ_1791722157015.png

2.3.2 串口调试助手

串口调试助手是 PC 端上位机工具,电脑通过 USB 虚拟串口接收开发板 UART 发送的数据。在本项目中,开发板会周期性读取 RTC 时间,将格式化后的时、分、秒字符串发送到 PC,串口助手负责接收并实时展示时间信息。
借助串口助手,可以直观验证 RTC 正常走时效果、按键调时是否生效,以及复位模拟掉电后时间保持功能。同时可以观察程序调试打印信息,方便定位软件逻辑问题,是嵌入式开发中重要的观测调试工具。

QQ_1791722226562.png


三、系统总体方案与设计思路

3.1 设计目标

本项目基于 FRDM-MCXN236 开发板,设计一套简易电子时钟系统。系统利用芯片内置硬件 RTC 模块实现计时功能,通过板载 GPIO 按键完成时间校准,使用 UART 串口向 PC 端输出实时时分秒信息;通过按下开发板复位按键模拟掉电重启,验证 RTC 计时数据在芯片供电不间断时不会丢失,完成课程要求的全部功能指标:

  1. RTC 硬件自动计时,实现时、分、秒正常走时,自动进位;
  2. 双按键控制:按键 1 循环切换调时字段(秒 / 分 / 时),按键 2 对选中字段数值递增;
  3. 时间变化时,串口自动打印当前时:分:秒;
  4. USB 保持供电,按下复位按键模拟掉电重启,重启后 RTC 时间保持不变。

3.2 总体硬件方案

整个系统全部使用开发板自带资源,无需外接电路。

  • 主控:MCXN236,内置硬件 RTC 模块,负责独立计时;
  • 输入:两个 GPIO 按键,作为人机交互接口,用于时间调整;
  • 通信:板载 USB 虚拟 UART 串口,实现 MCU 与 PC 的数据传输;
  • 验证工具:硬件复位按键,用于模拟设备掉电重启场景。

硬件工作逻辑:USB 为开发板持续供电,MCU 上电后初始化 RTC、GPIO、UART 外设;RTC 硬件独立运行、持续计时;程序循环扫描按键,检测用户调时操作并更新 RTC 时间;RTC 时间发生变化时,通过串口发送时间字符串至 PC 串口助手进行显示。按下复位按键时,仅软件程序重启,芯片电源不断,RTC 寄存器内计时数据保留,以此模拟掉电后时间保持效果。

image.png

3.3 总体软件设计思路

软件采用主循环轮询的框架设计,无操作系统,逻辑简单清晰。程序启动后先完成外设初始化,读取 RTC 初始时间,随后进入无限主循环。
在主循环内依次完成按键扫描、RTC 时间读取、时间判断、串口打印等任务:

  1. 按键扫描:采用软件消抖处理,识别按键动作。按键 1 按下,切换当前待修改字段(秒 / 分 / 小时循环);按键 2 按下,对选中字段数值加 1,到达上限自动归零,并将新时间写入 RTC;
  2. RTC 读取:循环读取 RTC 内部的时、分、秒数据;
  3. 时间判断:对比本次和上一次读取的秒数值,若秒发生改变,代表时间更新,触发串口打印;
  4. 串口输出:将格式化后的时间字符串发送到 PC 端串口助手显示。
开始(系统上电/复位)
↓
初始化RTC、GPIO按键、UART串口
↓
读取RTC当前时间
↓
进入主循环
↓
按键扫描(10ms软件消抖)
├─按键1按下 → 切换调整字段(秒/分/小时循环)
└─按键2按下 → 读取RTC时间,修改选中字段,上限归零,写入RTC
↓
读取RTC最新时间
↓
判断秒是否发生变化
├─是:串口打印 当前时:分:秒
└─否:不打印
↓
回到主循环

四、软件详细设计与功能展示

本项目软件基于 NXP MCXN236 官方 SDK 开发,采用主循环轮询架构,无操作系统。整体代码划分为 RTC 驱动模块、按键扫描模块、主逻辑控制模块三大部分。程序实现硬件 RTC 计时、SW2 按键切换调整字段、SW3 短按 / 长按时间递增、串口打印时间,并且通过复位按键模拟掉电保持时间。

4.1 全局变量与宏定义

用于存放全局状态标记、按键长按参数、RTC 写入保护参数。

/************************ 全局标记 ************************/
static uint8_t adjustSel = 0U; // 0秒 1分 2时 3日 4月 5年
static bool flag_change_time = false; // 需要修改时间标志
static uint32_t lastRtcWriteMs = 0;
static uint32_t msTick = 0U; // 全局毫秒计数器
#define RTC_WRITE_INTERVAL_MS 500U // RTC两次写入最小间隔500ms

// 长按配置
#define KEY_LONG_PRESS_DELAY_MS 600U // 按下后等待600ms开始连续加
#define KEY_CONTINUE_INTERVAL_MS 200U // 长按连续增加的间隔
static uint32_t sw3PressTick = 0;
static bool sw3LongPressActive = false;

/************************ 函数声明 ************************/
status_t IRTC_TimeInit(void);
bool IRTC_GetValidTime(irtc_datetime_t *timeBuf);
status_t IRTC_SetUserTime(irtc_datetime_t *timeBuf);
void IRTC_CheckAndSetDefaultTime(void);
uint8_t GetMonthMaxDay(uint16_t year, uint8_t month);
bool IRTC_CheckDateTimeValid(irtc_datetime_t *t);

void KEY_Init(void);
void KEY_Scan(uint32_t tickMs);


4.2 RTC 功能模块

RTC 模块是本项目计时核心,使用芯片内置 IRTC 硬件外设,硬件独立计数,复位不丢失计时数据。包含 RTC 初始化、安全读取时间、写时间、时间合法性校验、闰年与月份最大天数计算等功能。

/************************ RTC底层函数 ************************/
status_t IRTC_TimeInit(void)
{
irtc_config_t irtcConfig;
IRTC_GetDefaultConfig(&irtcConfig);
#if defined(APP_RTC_CLK_SRC)
irtcConfig.clockSource = APP_RTC_CLK_SRC;
#endif
if (IRTC_Init(RTC, &irtcConfig) != kStatus_Success)
{
PRINTF("IRTC init failed!\r\n");
return kStatus_Fail;
}
//清除IRTC所有中断标志,关闭IRTC中断,防止旧中断残留(VBAT保持)
RTC->IER = 0U;
RTC->STATUS = RTC->STATUS; //写STATUS清除中断标志位

return kStatus_Success;
}

bool IRTC_GetValidTime(irtc_datetime_t *timeBuf)
{
irtc_datetime_t t1, t2;
do
{
IRTC_GetDatetime(RTC, &t1);
IRTC_GetDatetime(RTC, &t2);
} while (memcmp(&t1, &t2, sizeof(irtc_datetime_t)) != 0);

*timeBuf = t1;
return IRTC_CheckDateTimeValid(timeBuf);
}

status_t IRTC_SetUserTime(irtc_datetime_t *timeBuf)
{
if(!IRTC_CheckDateTimeValid(timeBuf))
{
PRINTF("ERR: Invalid time, skip write!\r\n");
return kStatus_InvalidArgument;
}

/********* 解锁IRTC写保护 *********/
IRTC_SetWriteProtection(RTC, false);
__DSB();

status_t ret = IRTC_SetDatetime(RTC, timeBuf);
__DSB();

IRTC_SetWriteProtection(RTC, true); //写完上锁

return ret;
}

void IRTC_CheckAndSetDefaultTime(void)
{
irtc_datetime_t rtcTime;
IRTC_GetDatetime(RTC, &rtcTime);

if(rtcTime.year == 2112U)
{
irtc_datetime_t setTime = {
.year = 2026,
.month = 10,
.day = 11,
.hour = 10,
.minute = 0,
.second = 0
};
IRTC_SetUserTime(&setTime);
PRINTF("Detect default RTC, set time:2026-10-11 10:00:00\r\n");
}
}

uint8_t GetMonthMaxDay(uint16_t year, uint8_t month)
{
uint8_t dayTab[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
uint8_t maxDay = dayTab[month];
if(month == 2)
{
if( ((year %4 ==0) && (year%100 !=0)) || (year%400 ==0) )
{
maxDay = 29;
}
}
return maxDay;
}

bool IRTC_CheckDateTimeValid(irtc_datetime_t *t)
{
if(t->month <1 || t->month >12) return false;
if(t->day <1 || t->day > GetMonthMaxDay(t->year, t->month)) return false;
if(t->hour >23) return false;
if(t->minute >59) return false;
if(t->second >59) return false;
return true;
}


模块说明:

  • IRTC_TimeInit:初始化 IRTC 外设,关闭中断,清除中断标志;
  • IRTC_GetValidTime:连续两次读取 RTC 时间,保证读出数据稳定一致;
  • IRTC_SetUserTime:写入 RTC 时间,自带写保护解锁与上锁,同时校验时间合法性;
  • IRTC_CheckAndSetDefaultTime:上电检测 RTC 出厂默认值,仅第一次烧录自动设置初始时间,复位不会覆盖已设置时间;
  • GetMonthMaxDay与IRTC_CheckDateTimeValid:校验年月日时分秒范围,支持闰年判断,防止非法时间写入 RTC。

QQ_1791723316913.png

上图为断开USB供电(这里模拟的是VBAT纽扣电池也彻底没电,而不是正常掉电!!!)后恢复供电,系统自动初始化到默认时间。其他均为正常计数。

QQ_1791723561875.png

上图为在掉电场景(按下Reset复位键),时间继续保持并计数。


4.3 按键扫描模块

SW2 用于切换待修改字段,SW3 用于时间数值增加,支持短按单次加 1、长按连续递增,采用轮询状态机扫描。

/************************ 按键扫描【短按+长按】************************/
void KEY_Init(void)
{
gpio_pin_config_t sw_config = {
kGPIO_DigitalInput,
0U,
};
GPIO_PinInit(BOARD_sw2_GPIO, BOARD_sw2_GPIO_PIN, &sw_config);
GPIO_PinInit(BOARD_sw3_GPIO, BOARD_sw3_GPIO_PIN, &sw_config);
}

void KEY_Scan(uint32_t tickMs)
{
static uint8_t sw2State = 1;
static uint8_t sw3State = 1;

uint8_t sw2Now = GPIO_PinRead(BOARD_sw2_GPIO, BOARD_sw2_GPIO_PIN);
uint8_t sw3Now = GPIO_PinRead(BOARD_sw3_GPIO, BOARD_sw3_GPIO_PIN);

//==== SW2:切换调整项,松开触发 ====
if(sw2Now != sw2State)
{
sw2State = sw2Now;
if(sw2Now == 1)
{
adjustSel ++;
if(adjustSel >=6) adjustSel =0;
const char *selName[] = {"Second","Minute","Hour","Day","Month","Year"};
PRINTF("SW2: Select -> %s\r\n", selName[adjustSel]);
}
}

//==== SW3:短按一次+1,长按持续+1 ====
if(sw3Now != sw3State)
{
sw3State = sw3Now;
if(sw3Now == 0) // 按下沿
{
sw3PressTick = 0;
sw3LongPressActive = false;
}
else //松开沿
{
if(!sw3LongPressActive)
{
flag_change_time = true;
}
sw3PressTick = 0;
sw3LongPressActive = false;
}
}
else
{
if(sw3Now == 0) //按键持续按住
{
sw3PressTick += tickMs;
if(!sw3LongPressActive)
{
if(sw3PressTick >= KEY_LONG_PRESS_DELAY_MS)
{
sw3LongPressActive = true;
flag_change_time = true;
sw3PressTick = 0;
}
}
else
{
if(sw3PressTick >= KEY_CONTINUE_INTERVAL_MS)
{
flag_change_time = true;
sw3PressTick = 0;
}
}
}
}
}


模块说明:

  • KEY_Init:初始化 SW2、SW3 为 GPIO 输入模式;
  • KEY_Scan:10ms 周期调用,检测按键电平变化。SW2 松开触发切换字段;SW3 区分短按、长按,置位flag_change_time标志通知主程序修改 RTC 时间。

QQ_1791723717549.png

按下SW2按键


QQ_1791723769684.pngQQ_1791723792264.png

SW3按键短按(左)与长按(右)

QQ_1791723934259.png

板卡实物图

从上述的代码以及功能展示中可以看出,本次项目实现了RTC获取系统时间,并且通过串口打印显示出时分秒等信息。并且使用复位按键完美的模拟了系统掉电的场景以及通过断开usb电源模拟彻底掉电(VBAT纽扣电池也没电了)的情况。并且展示出了使用GPIO控制SW2和SW3两个按键对时间进行调整的功能,较好的完成了本次项目的所有需求。


五、总结

5.1 项目难题与解决方法

虽然本次项目的功能基本得到实现,但是在设计的过程中依旧遇到了一些难题:

  1. RTC 单次读取时间不稳定、偶尔跳变:在项目初期调试时,直接单次读取 RTC 时间,偶尔会读到错误、错乱的时间数据。分析原因是 RTC 硬件在更新寄存器的瞬间读取数据,会出现高低字节不统一、跨秒读取不一致的问题,导致时间显示跳动、异常,从而串口输出乱码。如下图所示,为了解决该问题,在程序中采用双次读取校验机制,连续读取两次 RTC 时间,只有两次数据完全一致才判定为有效时间并返回。该方法完全解决了 RTC 读取不稳定、时间跳变的问题,保证系统读取的时间始终准确可靠。
    c5b52557-a32a-4329-aee2-fd4996fd979d.jpg
  2. 快速按键导致频繁写 RTC 造成系统异常:在使用按键调整时间时候,连续快速按键会导致程序高频重复写入 RTC 寄存器,可能造成 RTC 寄存器状态异常、时间错乱,导致程序进入中断而卡死,为了解决该问题,增加 RTC 写入时间冷却机制,设置 500ms 最小写入间隔,短时间内多次按键只会执行一次写入,有效保护 RTC 硬件寄存器,提高系统稳定性。
  3. 在配置GPIO按键中,出现了SW2按键不可用的情况,打印GPIO的值也一直是低电平的情况,使用万用表排查引脚是正常的,于是根据原理图考虑可能是被WUU0的外设默认拉低了,于是重新配置了GPIO,问题得到解决。
    QQ_1791725033050.pngQQ_1791725135827.png

5.2 心得体会

本次基于 FRDM-MCXN236 的电子时钟设计项目,是一次完整的嵌入式软件开发实训,从需求分析、代码编写、模块调试到最终功能验证,完整走完了嵌入式项目开发全流程。本次项目不再是简单点亮 LED,而是涉及外设驱动、硬件原理、时序保护、按键算法、数据容错等综合性开发内容,让我收获极大。通过参加本次活动,我进一步认识到了嵌入式电子世界的有趣,并且在调试过程中一步步优化程序,发现问题解决问题让我找到了莫大的成就感,相比与上一次参加Funpack活动,我只是使用串口打印了一个hello digikey,而这次实现了一个完整的RTC系统,也算是一个小小的进步吧!但是我知道本次完成的项目对于嵌入式世界来说只是冰山一角,还有许多要学的地方。另外,本次项目采用复位模拟掉电的实验方式,让我深刻理解 MCU 复位与断电的区别:复位只会重启程序,不会清空寄存器数据,而断电会丢失数据。通过该方式成功模拟了工业设备断电保存时间的功能,贴近实际工程应用。

整体来看,本次项目提升了我的 C 语言编程能力、嵌入式外设开发能力、问题排查能力,让我对嵌入式实时系统、硬件寄存器保护、人机交互逻辑有了更加深刻、直观的理解,为今后更复杂的嵌入式项目开发打下了扎实的基础!!!



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