首先,个人不太认同使用mcx156的LPTMR模块适配RTC驱动的做法,但是既然群友提到了可以这么做,那就把这个模块适配一下玩玩。
硬件资源
从MCXAP100M96FS6RM.pdf上可以知道,LPTMR模块可以用作1s计时,但是存在一个问题,就是计数器不能设置,只能读。这就导致我们使用此模块适配RTC时,只能通过rtc驱动内部维护的变量来实现计时计算。其结构框图如下:
此处提到了lptmr的计数器无法初始化,只能读取。
驱动适配
KConfig修改
对应芯片的KConfig文件存储位置为:bsp\nxp\mcx\mcxa\frdm-mcxa156\board\Kconfig
我们仅仅需要在该文件的On-chip Peripheral Drivers项目中增加硬件RTC相关配置即可:
menuconfig BSP_USING_RTC config BSP_USING_RTC bool "Enable RTC" select RT_USING_RTC default y if BSP_USING_RTC config BSP_USING_ALARM bool "Enable Alarm" default y endif
SConscript修改
MCXA系列芯片驱动的SConscript文件存储位置为:bsp\nxp\mcx\mcxa\Libraries\drivers\SConscript
由于此文件中之前便有了RTC的配置,因此不需要修改。
驱动文件添加
在SConscript同一目录下添加drv_rtc.c文件,并填入以下内容。
/* * Copyright (c) 2006-2025, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2025-02-28 oxlm Initial version. */ #include <rtthread.h> #include <rtdevice.h> #include <sys/time.h> #include "fsl_common.h" #include "fsl_lptmr.h" #ifdef RT_USING_RTC struct rtc_device_object { rt_rtc_dev_t dev; time_t ts; rt_bool_t is_ts_set; time_t ts_new; #ifdef BSP_USING_ALARM struct rt_rtc_wkalarm alarm; #endif }; static struct rtc_device_object rtc_dev; void LPTMR0_IRQHandler(void) { LPTMR_ClearStatusFlags(LPTMR0, kLPTMR_TimerCompareFlag); if(rtc_dev.is_ts_set) { rtc_dev.is_ts_set = RT_FALSE; rtc_dev.ts = rtc_dev.ts_new; } rtc_dev.ts++; #ifdef BSP_USING_ALARM if(rtc_dev.alarm.enable) { rt_alarm_update(&rtc_dev.dev.parent, 0); } #endif /* * Workaround for TWR-KV58: because write buffer is enabled, adding * memory barrier instructions to make sure clearing interrupt flag completed * before go out ISR */ __DSB(); __ISB(); } static rt_err_t mcx_rtc_init(void) { lptmr_config_t lptmrConfig; LPTMR_GetDefaultConfig(&lptmrConfig); LPTMR_Init(LPTMR0, &lptmrConfig); LPTMR_SetTimerPeriod(LPTMR0, USEC_TO_COUNT(1000000, 16000U)); LPTMR_EnableInterrupts(LPTMR0, kLPTMR_TimerInterruptEnable); EnableIRQ(LPTMR0_IRQn); LPTMR_StartTimer(LPTMR0); return RT_EOK; } static rt_err_t mcx_rtc_get_time(time_t *ts) { if(rtc_dev.is_ts_set) { *ts = rtc_dev.ts_new; } else { *ts = rtc_dev.ts; } return RT_EOK; } static rt_err_t mcx_rtc_set_time(time_t *ts) { rtc_dev.ts_new = *ts; rtc_dev.is_ts_set = RT_TRUE; return RT_EOK; } #ifdef BSP_USING_ALARM rt_err_t mcx_rtc_get_alarm(struct rt_rtc_wkalarm *alarm) { rt_memcpy(alarm, &rtc_dev.alarm, sizeof(struct rt_rtc_wkalarm)); return RT_EOK; } rt_err_t mcx_rtc_set_alarm(struct rt_rtc_wkalarm *alarm) { rt_memcpy(&rtc_dev.alarm, alarm, sizeof(struct rt_rtc_wkalarm)); return RT_EOK; } #endif static const struct rt_rtc_ops ops = { .init = mcx_rtc_init, .get_secs = mcx_rtc_get_time, .set_secs = mcx_rtc_set_time, #ifdef BSP_USING_ALARM .get_alarm = mcx_rtc_get_alarm, .set_alarm = mcx_rtc_set_alarm, #else .get_alarm = RT_NULL, .set_alarm = RT_NULL, #endif .get_timeval = RT_NULL, .set_timeval = RT_NULL, }; int rt_hw_rtc_init(void) { rtc_dev.is_ts_set = RT_FALSE; rtc_dev.ts = 0; rtc_dev.dev.ops = &ops; if (rt_hw_rtc_register(&rtc_dev.dev, "rtc", RT_DEVICE_FLAG_RDWR, RT_NULL) != RT_EOK) { rt_kprintf("rtc init failed"); return -RT_ERROR; } return RT_EOK; } INIT_DEVICE_EXPORT(rt_hw_rtc_init); #endif /*RT_USING_RTC */
时钟树修改
在 bsp\nxp\mcx\mcxa\frdm-mcxa156\board\MCUX_Config\board\pin_mux.c中的函数BOARD_InitPins中添加如下代码:
#ifdef BSP_USING_RTC CLOCK_SetClockDiv(kCLOCK_DivLPTMR0, 1u); CLOCK_AttachClk(kFRO12M_to_LPTMR0); CLOCK_SetupFRO16KClocking(kCLKE_16K_SYSTEM | kCLKE_16K_COREMAIN); #endif
至此,代码修改部分已经完成。
开启RTC功能
menuconfig设置
配置好后按Q键并按Y键保存。
生成工程
添加测试代码
在main.c中添加如下代码。
void user_alarm_callback(rt_alarm_t alarm, time_t timestamp) { time_t now = (time_t)0; struct timeval tv = { 0 }; gettimeofday(&tv, RT_NULL); now = tv.tv_sec; rt_kprintf("local time: %.*s", 25U, ctime(&now)); } void alarm_sample(void) { rt_device_t dev = rt_device_find("rtc"); struct rt_alarm_setup setup; struct rt_alarm * alarm = RT_NULL; static time_t now; struct tm p_tm; if (alarm != RT_NULL) return; now = time(NULL) + 1; gmtime_r(&now,&p_tm); setup.flag = RT_ALARM_SECOND; setup.wktime.tm_year = p_tm.tm_year; setup.wktime.tm_mon = p_tm.tm_mon; setup.wktime.tm_mday = p_tm.tm_mday; setup.wktime.tm_wday = p_tm.tm_wday; setup.wktime.tm_hour = p_tm.tm_hour; setup.wktime.tm_min = p_tm.tm_min; setup.wktime.tm_sec = p_tm.tm_sec; alarm = rt_alarm_create(user_alarm_callback, &setup); if(RT_NULL != alarm) { rt_alarm_start(alarm); } } MSH_CMD_EXPORT(alarm_sample,alarm sample);
结果验证
编译后将固件下载至板卡,复位板卡后输入命令alarm_sample并回车,得到如下结果:
sram heap, begin: 0x0x200015dc, end: 0x0x2001dc00 \ | / - RT - Thread Operating System / | \ 5.2.0 build Feb 28 2025 00:37:09 2006 - 2024 Copyright by RT-Thread team using armclang, version: 6210000 MCXA156 HelloWorld msh > msh > msh >alar alarm_sample msh >alarm_sample msh >local time: Thu Jan 1 08:00:02 1970 local time: Thu Jan 1 08:00:03 1970 local time: Thu Jan 1 08:00:04 1970 local time: Thu Jan 1 08:00:05 1970 local time: Thu Jan 1 08:00:06 1970 local time: Thu Jan 1 08:00:07 1970 local time: Thu Jan 1 08:00:08 1970 local time: Thu Jan 1 08:00:09 1970 local time: Thu Jan 1 08:00:10 1970 local time: Thu Jan 1 08:00:11 1970 local time: Thu Jan 1 08:00:12 1970 local time: Thu Jan 1 08:00:13 1970 local time: Thu Jan 1 08:00:14 1970 local time: Thu Jan 1 08:00:15 1970 local time: Thu Jan 1 08:00:16 1970 local time: Thu Jan 1 08:00:17 1970 local time: Thu Jan 1 08:00:18 1970 local time: Thu Jan 1 08:00:19 1970 local time: Thu Jan 1 08:00:20 1970 local time: Thu Jan 1 08:00:21 1970 local time: Thu Jan 1 08:00:22 1970 local time: Thu Jan 1 08:00:23 1970 local time: Thu Jan 1 08:00:24 1970 local time: Thu Jan 1 08:00:25 1970 local time: Thu Jan 1 08:00:26 1970 local time: Thu Jan 1 08:00:27 1970 local time: Thu Jan 1 08:00:28 1970
至此,功能部分验证完成。
最后
正如我一开始所说,这方法仅适用于玩玩,毕竟不是真正的硬件RTC,与其这么干还不如之接弄软件RTC实在。