项目介绍
本项目基于任务1:电子时钟设计
基于 FRDM-MCXN236 开发板,实现一个电子时钟系统,能够显示当前时间并支持时间设置功能。
功能要求:
使用 RTC 获取系统时间 完成
显示当前时、分、秒信息 完成
使用按键实现时间调整 完成
通过串口输出当前时间 完成
掉电后时间能够保持 完成,由于FRDM-MCXN236没有引出VDD_VBAT(RTC供电),而是通过R76电阻接在VDD上,不太方便焊接并引出VDD_VBAT引脚接电池,实现掉电后时间保持。因此本项目通过按下RESET键复位MCU一段时间,让MCU停止工作,而RTC由于接在VDD,没有断电继续计时运行,当松开RESET时,MCU开始工作,获取RTC的最近计时并显示,从而实现模拟掉电(MCU停止工作RTC继续工作)时间能够保持。
手册介绍:

电路图:

简短的所有使用到的硬件介绍
本项目使用的硬件就只有FRDM-MCXN236,通过数据线连接至电脑,实现串口通信(显示时间),串口使用的是开发板默认调试口DEBUG_UART

方案框图和项目设计思路介绍
项目框图如下:
主要功能是
1. 主循环每秒读取并串口打印 RTC 时间,同时查询按键按下标志,如果按下,则执行对应操作,如果SW3按下:切换要修改的时间项(年 / 月 / 日 / 时 / 分 / 秒),如果SW2按下:当前选中项数值 + 1,自动做月份、日期、闰年边界判断,写入 RTC。不直接在中断里修改时间(中断只置按键按下标志)
2. 外部中断INT_0_IRQHANDLER判断按键是否按下,如果有按键按下,对应的按键置按下标志,然后立即清除中断位。

设计思路:按任务1:电子时钟设计,主要涉及RTC时钟,串口,按键。
RTC时钟和串口直接参考官方例程frdmmcxn236_irtc,都已经配置好基本外设,然后在此基础上加上按键中断,并完善程序功能即可。
需要注意一点,官方例程是在代码里给时钟赋值一个初始值,而我们要实现掉电时间保持,就不能一运行就给RTC赋值,不然掉电/复位后,程序一运行就恢复到初始时间,而应该是上电运行后,获取RTC当前时间,第一次烧录时间是出厂默认时间:2112/1/1 0:0:0,我们通过按键设置好时间后,后面只要不断开发板电源,随便怎么复位,RTC都在实时运行走时,MCU只要一运行,就可以获取RTC最新时间,从而完美实现掉电时间保持。
调试软件介绍、软件流程图及关键代码介绍
本项目使用MCUXpresso IDE v25.6.136,在IDE内下载FRDM-MCXN236开发板的SDK,包含了各类例程,我们可以在官方例程基础上修改,从而实现所需功能。

软件流程如下:

关键代码介绍
1. 由于没有屏幕,在设置时间时,是按时间位切换,但需要显示当前调整的是哪个时间位(年、月、日、时、分、秒),才能方便的条件。
于是设计了一个数组
unsigned char set_time[7][12]={"","Set Year","Set Month","Set Day","Set Hour","Set Minute","Set Second"};
每按下一次SW3,set_time[set_time_flag][12]中的set_time_flag就加1,这样我们就可以指示调整时间位,
if(flag_switch == 1) //SW3按下,设置时间位
{
flag_switch = 0; //清标志
set_time_flag ++;
if(set_time_flag>6)
{
// 退出设置模式,写入RTC
set_time_flag = 0;
}
}
并通过spintf功能,把调整时间位加在时间字符串后面,实现只是功能。
sprintf(time, "%d/%d/%d %d:%d:%2d %s\r\n",
datetimeGet.year, datetimeGet.month, datetimeGet.day,
datetimeGet.hour, datetimeGet.minute, datetimeGet.second,set_time[set_time_flag]);
// 串口格式化输出年月日时分秒
PRINTF(time);
2. SW2的功能是,在设置好调整时间位后,每按下一次SW2,对应时间位(根据set_time_flag的值判读)就加1,但加到最大后要归位,比如年加到2099以上就归位到2000,月加到12以上就归1,但每月天数有4种情况,1/3/5/7/8/12月31天,4/6/9/11月30天,闰年2月29天,平年2月28天,这些要通过各自条件判断去实现,否则如果只设置大于31天置0,那4/6/9/11月永远加不到31(RTC内部会根据月份限制),那就永远无法置0。
if(flag_add ==1) //SW2按下,时间位+1
{
flag_add =0;
datetime = datetimeGet;
switch(set_time_flag)
{
case 1: datetime.year++;if(datetime.year>2099) datetime.year=2000; break;
case 2: datetime.month++; if(datetime.month>12) datetime.month=1; break;
case 3:
datetime.day++;
{
uint8_t maxday = 31;
if(datetime.month == 4 || datetime.month ==6 || datetime.month ==9 || datetime.month ==11)
maxday =30;
else if(datetime.month ==2)
{
if( ((datetime.year%4==0)&&(datetime.year%100!=0)) || (datetime.year%400==0) )
maxday=29;
else
maxday=28;
}
if(datetime.day > maxday) datetime.day=1;
}
break;
case 4: datetime.hour++; if(datetime.hour>23) datetime.hour=0; break;
case 5: datetime.minute++; if(datetime.minute>59) datetime.minute=0; break;
case 6: datetime.second++; if(datetime.second>59) datetime.second=0; break;
default: break;
}
}
3. 修改时间需要先关闭RTC写保护,允许修改RTC时间,写入时间后再打开写保护,否则在设置时间时MCU会运行报错。
// =========关闭IRTC写保护,允许修改RTC时间=========
IRTC_SetWriteProtection(RTC, false);
// 写入时间
IRTC_SetDatetime(RTC, &datetime);
// 写入完成,重新上锁(可选,推荐)
IRTC_SetWriteProtection(RTC, true);
功能展示图及说明

项目中遇到的难题及解决方法
遇到的最大难题是:一开始没进行RTC写保护这块的设置,导致设置时间时,在Debug状态下,经常收到CPU Fault的报错信息,后面查阅了一些资料才知道NXP的MCU有这项设置,放置异常修改时间,加上解除写保护,设置好时间再写保护就正常了。
心得体会
通过本项目,我完整的实现了实时时钟的完整功能,特别是在没有外接显示屏,仅依靠串口显示的方式,就实现了人机交互,通过一个二维数组set_time[7][12],实现2重功能,
功能1:根据对应的调整时间位set_time_flag ,将set_time[set_time_flag][12]字符串显示在时间后面,指示当前调整的是哪个时间位(年、月、日、时、分、秒);
功能2:根据对应的调整时间位set_time_flag ,将RTC对应时间位+1。
非常感谢硬禾举办这样的活动,即增长了知识,也收获了项目经验。