一、任务活动介绍
Funpack 活动是由硬禾科技联合 DigiKey 发起的“玩成功就全额退”活动。第五季第 4 期所使用的板卡为 NXP FRDM-MCXN236 开发板。本项目选择的任务如下。
任务 1:电子时钟设计【难度 ⭐️⭐️】
基于 FRDM-MCXN236 开发板实现一个电子时钟系统,能够显示当前时间并支持时间设置功能。任务要求包括:
- 使用 RTC 获取系统时间;
- 显示当前时、分、秒信息;
- 使用按键调整时间;
- 通过串口输出当前时间;
- 在具备后备供电的条件下,掉电后保持时间。
本项目在基础任务之上增加了日期显示和桌面化交互,使用 0.91 英寸 SSD1306 OLED 同时显示日期与时间。
二、项目描述

本项目基于 NXP FRDM-MCXN236 开发板实现桌面电子时钟。系统使用内部 IRTC 记录日期和时间,通过 SSD1306 128×32 OLED 显示,并利用板载 SW2、SW3 按键完成时间设置。
项目主要功能包括:
- 显示年月日和时分秒;
- 支持按键设置时间及字段闪烁提示;
- 自动判断闰年和月份天数;
- 每秒通过串口输出当前时间;
- RESET 后保持 RTC 继续运行;
- 支持软件 I²C和硬件 I²C 两种 OLED 驱动方式。
工程基于 MCUXpresso SDK 的 IRTC 示例扩展,采用中断与主循环相结合的结构,具有实现简单、资源占用少和运行稳定等特点。
三、芯片及器件选型
3.1 FRDM-MCXN236 开发板

FRDM-MCXN236 开发板的核心为 MCX N236 微控制器,主要资源如下。
项目 | 参数 |
|---|---|
CPU 内核 | Arm Cortex-M33 |
最高主频 | 150 MHz |
Flash | 1 MB 双组 Flash |
RAM | 352 KB ECC RAM |
加速单元 | SmartDMA |
RTC | 带日历功能的内部 IRTC,位于 VBAT 域 |
调试接口 | 板载 MCU-Link |
扩展接口 | Arduino 兼容接口 |
本项目使用外设 | IRTC、GPIO、PORT、LPUART、SysTick、LPI2C2(可选) |
MCX N236 的 IRTC 能够直接保存年、月、日、星期、时、分、秒信息,并支持独立 VBAT 域,因此适合实现需要跨系统复位保持计时的电子时钟。
3.2 OLED 显示模块

本项目使用 0.91 英寸 SSD1306 OLED 模块。
项目 | 参数 |
|---|---|
控制芯片 | SSD1306 |
分辨率 | 128×32 |
通信方式 | I²C |
默认 7 位地址 | 0x3C |
显存大小 | 128×32÷8 = 512 字节 |
显示内容 | 英文标题、日期、时间及设置状态 |
OLED 具有体积小、对比度高、无需背光和接口简单等特点,适合作为桌面电子时钟的显示器。
四、方案框图与设计思路
系统以 FRDM-MCXN236 开发板为核心,利用内部 IRTC 完成日期和时间的持续计时。SW3 作为 SET 按键,用于进入、确认或取消时间设置;SW2 作为 PLUS 按键,用于增加当前设置字段。系统通过 GPIO 完成按键扫描与消抖,并由 SysTick 提供稳定的毫秒级时间基准。当前日期和时间通过 SSD1306 OLED 显示,同时经调试串口每秒输出。OLED 默认采用交换引脚后的软件 I²C驱动,也可通过宏定义切换为硬件 LPI2C2。系统 RESET 期间 IRTC 能够继续计时,若需在完全断电后保持时间,则需要为 VBAT 域提供独立后备电源。
五、电路设计
5.1 开发板参考原理图
FRDM-MCXN236 参考原理图可从电子森林资源库的FRDM-MCXN236 开发板页面获取。本项目没有设计独立 PCB,主要使用开发板内部 IRTC、板载按键和扩展接口连接 OLED。

5.2 OLED 连线
本项目使用 0.91 英寸、128×32 分辨率的 SSD1306 OLED。为了方便接线,默认采用 GPIO 模拟软件 I²C,所使用的引脚如下:
OLED 引脚 | 开发板连接 | 功能 |
|---|---|---|
VCC | 3.3 V | 模块电源 |
GND | GND | 电源地 |
SDA | P1_2 | 软件 I²C 数据线 |
SCL | P1_0 | 软件 I²C 时钟线 |
工程同时保留硬件 LPI2C2 驱动。如需使用硬件 I²C,应按以下方式连接:
OLED 引脚 | 开发板连接 | 复用功能 |
|---|---|---|
SDA | P4_0 | FC2_P0 |
SCL | P4_1 | FC2_P1 |
软硬件 I²C可通过 board/app.h 中的宏定义选择:
/* 0:软件 I2C;1:硬件 LPI2C2 */
#define BOARD_OLED_USE_HARDWARE_I2C 0
默认值为 0,即使用 P1_2 作为 SDA、P1_0 作为 SCL。修改为 1 后,程序将使用硬件 LPI2C2,此时需要按照硬件外设的固定引脚功能连接 OLED。
5.3 按键分配
按键 | MCU 引脚 | 功能 |
|---|---|---|
SW2 | P0_20 | PLUS,增加当前设置字段 |
SW3 | P0_6 | SET,长按进入/取消,短按确认 |
两个按键均采用低电平有效方式,并启用内部上拉和无源输入滤波。
5.4 RTC 后备供电说明

IRTC 日历寄存器不受普通软件复位影响,因此按下或按住 RESET 时,RTC 可以继续运行。
当前开发板没有单独连接 VBAT 后备电源,所以“按住 RESET”只能模拟主控停止运行,不能等效为整板断电。要实现真正的完全掉电时间保持,需要根据开发板原理图为 VBAT 域增加纽扣电池或超级电容,根据原理图需断开R76电阻,并在TP32上增加VBAT供电。
六、软件设计与关键代码
6.1 工程结构
工程以 MCUXpresso SDK 的 FRDM-MCXN236 IRTC 示例为基础,主要文件如下。

文件 | 功能 |
|---|---|
source/irtc.c | 主程序、IRTC 初始化、有效性判断、时间写入和串口输出 |
source/clock_app.c/.h | 按键扫描、设置状态机、日历处理、闪烁和界面绘制 |
source/ssd1306.c/.h | SSD1306 驱动、软件 I²C及硬件 LPI2C2 后端 |
source/oled_font.h | 6×8 ASCII 字库 |
board/hardware_init.c | 按键和 OLED 总线初始化 |
board/pin_mux.c/.h | UART、按键、硬件/软件 I²C 引脚复用 |
board/app.h | OLED 接口模式、引脚和通信参数配置 |
6.2 软件主流程
系统上电或 RESET 后,首先初始化系统时钟、GPIO、串口、OLED、SysTick 和内部 IRTC。程序判断 RTC 时间是否有效,若无效则写入默认日期和时间,随后进入 10 ms 周期主循环。正常模式下,系统持续扫描按键,并每秒刷新 OLED 和串口输出;长按 SW3 后进入时间设置模式。设置过程中,SW2 用于增加当前字段,短按 SW3 用于确认并切换字段。当年、月、日、时、分、秒全部设置完成后,程序校验日期并写入 IRTC。设置期间长按 SW3 或 30 秒无操作,系统会放弃修改并返回正常显示模式。
6.3 关键代码
- RTC 时间有效性判断与复位保持
程序启动后首先读取 IRTC 状态。如果 RTC 无效或时间字段超出合法范围,则写入默认时间;如果时间有效,则保留原有时间。因此,按下 RESET 后不会重新初始化时间,IRTC 可以继续计时。
static bool APP_TimeIsValid(void)
{
irtc_datetime_t datetime;
uint32_t flags = IRTC_GetStatusFlags(RTC);
if (0U != (flags & (uint32_t)kIRTC_InvalidFlag))
{
return false;
}
IRTC_GetDatetime(RTC, &datetime);
if ((datetime.year < APP_YEAR_MIN) ||
(datetime.year > APP_YEAR_MAX) ||
(datetime.month < 1U) || (datetime.month > 12U) ||
(datetime.day < 1U) ||
(datetime.day > APP_GetDaysOfMonth(datetime.year,
datetime.month)) ||
(datetime.hour > 23U) ||
(datetime.minute > 59U) ||
(datetime.second > 59U))
{
return false;
}
return true;
}
启动时根据检测结果决定是否写入默认时间:
retained = APP_TimeIsValid();
if (!retained)
{
APP_LoadDefaultTime();
PRINTF("Cold start detected, default time loaded.\r\n");
}
else
{
PRINTF("Warm reset detected, retained time is kept running.\r\n");
}
- 时间设置状态机
系统使用状态机依次设置年、月、日、时、分、秒。每次短按 SW3 后进入下一字段,确认秒字段后返回正常模式,并通知主程序将编辑结果写入 IRTC。
bool APP_AdvanceField(void)
{
bool finished = false;
switch (s_clockMode)
{
case kAppMode_SetYear:
s_clockMode = kAppMode_SetMonth;
break;
case kAppMode_SetMonth:
s_clockMode = kAppMode_SetDay;
break;
case kAppMode_SetDay:
s_clockMode = kAppMode_SetHour;
break;
case kAppMode_SetHour:
s_clockMode = kAppMode_SetMinute;
break;
case kAppMode_SetMinute:
s_clockMode = kAppMode_SetSecond;
break;
case kAppMode_SetSecond:
s_clockMode = kAppMode_Run;
finished = true;
break;
default:
s_clockMode = kAppMode_Run;
break;
}
s_blinkTicks = 0U;
s_blinkVisible = false;
return finished;
}
进入设置模式时,程序先复制当前 RTC 时间,避免在设置过程中直接修改硬件时间:
void APP_EnterSetting(const irtc_datetime_t *current)
{
memcpy(&s_editTime, current, sizeof(s_editTime));
APP_SanitizeDatetime(&s_editTime);
s_clockMode = kAppMode_SetYear;
s_blinkTicks = 0U;
s_blinkVisible = false;
}
- 闰年及日期合法性处理
不同月份的天数并不相同,二月还需要判断闰年。程序使用完整的公历规则计算每个月的最大天数,避免出现 2 月 30 日、4 月 31 日等非法日期。
uint8_t APP_GetDaysOfMonth(uint16_t year, uint8_t month)
{
uint8_t days[12] = {
31U, 28U, 31U, 30U, 31U, 30U,
31U, 31U, 30U, 31U, 30U, 31U
};
if ((month < 1U) || (month > 12U))
{
return 31U;
}
if (2U == month)
{
if ((((year % 4U) == 0U) &&
((year % 100U) != 0U)) ||
((year % 400U) == 0U))
{
return 29U;
}
}
return days[month - 1U];
}
增加日期时,根据当前年月确定循环上限:
case kAppMode_SetDay:
{
uint8_t maxDay =
APP_GetDaysOfMonth(s_editTime.year,
s_editTime.month);
s_editTime.day++;
if (s_editTime.day > maxDay)
{
s_editTime.day = 1U;
}
break;
}
小时、分钟和秒则通过取模运算实现自动循环:
s_editTime.hour =
(uint8_t)((s_editTime.hour + 1U) % 24U);
s_editTime.minute =
(uint8_t)((s_editTime.minute + 1U) % 60U);
s_editTime.second =
(uint8_t)((s_editTime.second + 1U) % 60U);
- 软件 I²C字节发送
由于 OLED 模块的 SDA、SCL 顺序与硬件 LPI2C2 引脚相反,工程默认使用 GPIO 模拟 I²C。GPIO 输出模式用于拉低总线,输入模式用于释放总线,从而模拟 I²C 的开漏结构。
static void SSD1306_SoftDriveLow(uint32_t pin)
{
BOARD_OLED_SOFT_I2C_GPIO->PDDR |= (1UL << pin);
}
static void SSD1306_SoftRelease(uint32_t pin)
{
BOARD_OLED_SOFT_I2C_GPIO->PDDR &= ~(1UL << pin);
}
发送一个字节后,程序在第九个时钟读取 OLED 返回的 ACK:
static bool SSD1306_SoftWriteByte(uint8_t value)
{
uint32_t bit;
bool acknowledged;
for (bit = 0U; bit < 8U; bit++)
{
if (0U != (value & 0x80U))
{
SSD1306_SoftRelease(
BOARD_OLED_SOFT_I2C_SDA_PIN);
}
else
{
SSD1306_SoftDriveLow(
BOARD_OLED_SOFT_I2C_SDA_PIN);
}
SSD1306_SoftDelay();
if (!SSD1306_SoftRaiseClock())
{
return false;
}
SSD1306_SoftDelay();
SSD1306_SoftDriveLow(
BOARD_OLED_SOFT_I2C_SCL_PIN);
value <<= 1U;
}
/* 第九个时钟读取低电平有效的 ACK */
SSD1306_SoftRelease(
BOARD_OLED_SOFT_I2C_SDA_PIN);
SSD1306_SoftRaiseClock();
SSD1306_SoftDelay();
acknowledged = !SSD1306_SoftRead(
BOARD_OLED_SOFT_I2C_SDA_PIN);
SSD1306_SoftDriveLow(
BOARD_OLED_SOFT_I2C_SCL_PIN);
return acknowledged;
}
- OLED 刷新与串口时间输出
IRTC 每秒产生一次中断事件。主循环检测到该事件后重新读取 RTC,刷新 OLED,并通过调试串口输出完整日期和时间。
串口时间格式化代码如下:
static void APP_PrintDatetime(
const irtc_datetime_t *datetime)
{
PRINTF("RTC: %04u-%02u-%02u %02u:%02u:%02u\r\n",
(unsigned int)datetime->year,
(unsigned int)datetime->month,
(unsigned int)datetime->day,
(unsigned int)datetime->hour,
(unsigned int)datetime->minute,
(unsigned int)datetime->second);
}
每秒事件处理代码如下:
if (s_secondTick)
{
s_secondTick = false;
redraw = true;
printTime = true;
}
if (redraw)
{
IRTC_GetDatetime(RTC, &now);
APP_SanitizeDatetime(&now);
}
if (printTime)
{
APP_PrintDatetime(&now);
}
if (redraw && oledReady)
{
APP_Display(&now);
}
串口输出示例:
RTC: 2025-07-25 12:30:45
RTC: 2025-07-25 12:30:46
七、实物演示
- 上电显示:上电之后,由于刚上电,默认显示的时间为2025-01-01 00:00:00,然后间隔1s增加时间。

- 设置时间和切换显示模式:长按SW3进入设置模式,设置正确的时候后,即可按最新时间运行。然后可以通过SW2切换显示模式,共三种显示模式。



- 长按进入时间设置:进入设置模式后,左上角会显示※标志,对应的设置位,会闪烁显示。设置过程中,按SW2增加对应位,长按可以快速增加。

- 串口输出内容:1s周期发送最新时间。

- RTC测试:由于VBAT没有单独接入电源,我们以按住RESET按键一段时间,观察时间是否正确。测试结果RTC是一直运行的,具体效果查看视频。
八、难点与解决方法
8.1 两个按键完成六个字段设置
问题: 按键数量有限,既要进入设置,又要调整和确认字段,还要避免误触。
解决: 将 SW3 设计为多功能 SET 键:正常模式长按进入、设置模式短按确认、设置模式长按取消;SW2 专门负责增加。当前字段闪烁提供视觉反馈,并加入 30 秒无操作超时。
8.2 日期边界和闰年处理
问题: 日期字段不能简单按 1~31 循环,否则会产生 2 月 30 日等非法时间。
解决: 建立统一的月份天数函数,并使用完整公历闰年规则。修改年、月后立即修正日字段,最终写入前再次进行完整合法性检查。
九、总结
本项目基于 FRDM-MCXN236 实现了具备 OLED 日期时间显示、双按键设置、串口输出、日期校验和 RESET 计时保持功能的桌面 RTC 电子时钟。针对 OLED 引脚顺序问题,系统提供可切换的软硬件 I²C 驱动,后续还可扩展独立 VBAT 供电、闹钟、亮度调节和低功耗等功能。
通过本次实践,掌握了 MCX N236 的 IRTC、GPIO、PORT、LPUART、LPI2C 和 SysTick 等外设的使用方法,也进一步理解了 RTC 后备供电、I²C 开漏总线、按键状态机和日历合法性处理。