本帖最后由 口天土立口 于 2025-11-20 14:52 编辑
#技术资源# #申请原创#
@21小跑堂
1. 外设介绍
日历时间在很多产品中,都是必备的功能,而APM32F427拥有硬件RTC,无需再软件实现日历时间的功能。而备份域的加入,为程序的复位判断提供比较好的依据,可以辅助程序判断复位后是否需要重新初始化RTC为默认时间。 实际产品应用中,有高精度日历时间需求的话,会在电路上再配置一颗专业的RTC独立芯片,此芯片一般能做到24小时的误差小于1秒,同时再每天定时联网校准一次时间,可基本忽略时间的误差,例如南方电网或国家电网给用户安装的电表就是类似的操作。而允许时间日历误差较大的产品中,MCU内置的RTC就能满足需求,同时结合定时联网校准时间,误差也不至于偏差太离谱,当然在这种场景中,请尽量加入Vbat供电和LSE作为RTC的时钟。 RTC的使用,一般会提供Vbat供电,此供电一般为焊接在电路板上的电池(纽扣电池或小容量锂电池),像在电脑的主板上就有一颗纽扣电池,用于提供在产品主电源断电之后的RTC持续工作电源,以防止时间复位。而LSE为RTC提供精准的时钟源,同时外部晶振相对受环境影响较小,减少RTC的误差。
2. 硬件 APM32F427ZG TINY板
3. 驱动介绍 RTC寄存器默认处于禁止写入状态,需要使能PMU模块,并开启PMU对应寄存器内的使能写备份区域位才允许写入RTC。if (RTC_ReadBackup(RTC_BKP_DATA) != BKP_DATA)的操作,判断RTC的配置是否需要重新初始化,在MCU不断电而产生的复位情况下,备份区域和RTC的配置都不会丢失,所以不需要重新初始化,初始化反而导致日历时间倒退不是最新的时间。只有MCU完全断电再上电的场景,会导致备份区域的数据丢失,判断不一致后进入RTC初始化流程。 - /* RTC初始化标记 */
- #define RTC_BKP_DATA (RTC_BAKP_REG_0)
- #define BKP_DATA (0x12345678)
- /*
- * @brief RTC初始化
- *
- * @param None
- *
- * @retval None
- *
- */
- void bsp_rtc_init(void)
- {
- RTC_Config_T RtcStruct;
- RTC_TimeConfig_T timeConfig;
- RTC_DateConfig_T dateConfig;
-
- /* 使能RTC区域写, 否则无法开启LSE */
- RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_PMU);
- PMU_EnableBackupAccess();
-
- /* 备份值一致则无需重复初始化 */
- if (RTC_ReadBackup(RTC_BKP_DATA) != BKP_DATA) {
- /* 复位RTC域 */
- RCM_EnableBackupReset();
- __NOP();__NOP();__NOP();__NOP();
- RCM_DisableBackupReset();
-
- /* 开启LSE */
- RCM_ConfigLSE(RCM_LSE_OPEN);
- while (RCM_ReadStatusFlag(RCM_FLAG_LSERDY) != SET);
- /* RTC 时钟为LSE */
- RCM_ConfigRTCCLK(RCM_RTCCLK_LSE);
- RCM_EnableRTCCLK();
-
- /* 配置RTC */
- RTC_Reset();
- RTC_ConfigStructInit(&RtcStruct);
- RtcStruct.format = RTC_HOURFORMAT_24;
- /* 时钟先异步分配,再同步分频到RTC使用
- * 1HZ = 32768 / 256 / 128
- */
- RtcStruct.synchPrediv = 256 - 1;
- RtcStruct.asynchPrediv = 128 - 1;
- RTC_Config(&RtcStruct);
- /* 配置默认时间 */
- RTC_ConfigTimeStructInit(&timeConfig);
- timeConfig.hours = 12;
- timeConfig.minutes = 10;
- timeConfig.seconds = 10;
- timeConfig.h12 = RTC_H12_AM;
- if (RTC_ConfigTime(RTC_FORMAT_BIN, &timeConfig) == SUCCESS) {
- /* 配置默认日期 */
- RTC_ConfigDateStructInit(&dateConfig);
- dateConfig.weekday = RTC_WEEKDAY_TUESDAY;
- dateConfig.date = 23;
- dateConfig.month = RTC_MONTH_SEPTEMBER;
- dateConfig.year = 25;
- RTC_ConfigDate(RTC_FORMAT_BIN, &dateConfig);
- }
- RTC_WriteBackup(RTC_BKP_DATA, BKP_DATA);
- }
-
- /* 禁止RTC寄存器可写 */
- PMU_DisableBackupAccess();
- }
读取RTC时间,需注意要连续读两次,确保两次读到的时间日期数据都一致的才使用。因为读数据过程中,RTC依然保持在运行,同时时间和日期是两个寄存器,存在数据读取的先后顺序,而读取的过程中,会存在时间和日期跨秒、跨分、跨时、跨日、跨月和跨年的问题,例如:本来应该读取到的时间寄存器数据为23:59:59,日期寄存器为24年12月31日,但因为刚读完时间寄存器为23:59:59,日期寄存器就加了1秒进入了25年1月1日,所以如果不重复连续再读一遍才确认使用的话,就存在本来应该是为24年12月31日 23:59:59的日期时间,变成了25年1月1日 23:59:59。 - /*
- * @brief 获取RTC时间
- *
- * @param dt: 日期时间
- *
- * @retval None
- *
- */
- void bsp_rtc_datetime_get(struct datetime_t *dt)
- {
- RTC_TimeConfig_T timeConfig;
- RTC_TimeConfig_T timeConfig2;
- RTC_DateConfig_T dateConfig;
- RTC_DateConfig_T dateConfig2;
-
- if (dt != NULL) {
- do {
- RTC_ReadTime(RTC_FORMAT_BIN, &timeConfig);
- RTC_ReadTime(RTC_FORMAT_BIN, &timeConfig2);
- RTC_ReadDate(RTC_FORMAT_BIN, &dateConfig);
- RTC_ReadDate(RTC_FORMAT_BIN, &dateConfig2);
- /* 确保两次读取的数据完全一致,避免卡跨时间点位置导致时间错乱 */
- if ((memcmp(&timeConfig, &timeConfig2, sizeof(RTC_TimeConfig_T)) == 0) && \
- (memcmp(&dateConfig, &dateConfig2, sizeof(RTC_DateConfig_T)) == 0)) {
- dt->second = timeConfig.seconds;
- dt->minute = timeConfig.minutes;
- dt->hour = timeConfig.hours;
- dt->day = dateConfig.date;
- dt->month = (dateConfig.month < RTC_MONTH_OCTOBER) ? \
- dateConfig.month : (dateConfig.month - RTC_MONTH_OCTOBER + 10);
- dt->year = 2000 + dateConfig.year;
- dt->week = dateConfig.weekday;
- break;
- }
- } while (1);
- }
- }
同样的,配置日期时间也是需要注意配置进去再读取回来是一致的,否则跨时间点问题会导致配置与预期不一致。 另外,更改RTC相关寄存器,都要先使能PMU模块的备份区域写使能位。 - /*
- * @brief 设置RTC时间
- *
- * @param dt: 日期时间
- *
- * @retval None
- *
- */
- void bsp_rtc_datetime_set(struct datetime_t *dt)
- {
- RTC_TimeConfig_T timeConfig;
- RTC_TimeConfig_T timeConfig2;
- RTC_DateConfig_T dateConfig;
- RTC_DateConfig_T dateConfig2;
- uint8_t state;
- uint8_t state2;
-
- /* 填充数据 */
- timeConfig.seconds = dt->second;
- timeConfig.minutes = dt->minute;
- timeConfig.hours = dt->hour;
- dateConfig.date = dt->day;
- dateConfig.month = (RTC_MONTH_T)((dt->month < 10) ? dt->month : (dt->month - 10 + RTC_MONTH_OCTOBER));
- dateConfig.year = dt->year % 100;
- dateConfig.weekday = (RTC_WEEKDAY_T)dt->week;
-
- if (dt != NULL) {
- do {
- /* 允许RTC寄存器可写 */
- PMU_EnableBackupAccess();
- /* 配置时间日期 */
- state = RTC_ConfigTime(RTC_FORMAT_BIN, &timeConfig);
- state2 = RTC_ConfigDate(RTC_FORMAT_BIN, &dateConfig);
- if ((state != SUCCESS) || (state2 != SUCCESS)) {
- continue;
- }
- RTC_ReadTime(RTC_FORMAT_BIN, &timeConfig2);
- RTC_ReadDate(RTC_FORMAT_BIN, &dateConfig2);
- /* 确保读取的数据与写入的完全一致 */
- if ((memcmp(&timeConfig, &timeConfig2, sizeof(RTC_TimeConfig_T)) == 0) && \
- (memcmp(&dateConfig, &dateConfig2, sizeof(RTC_DateConfig_T)) == 0)) {
- break;
- }
- } while (1);
- /* 禁止RTC寄存器可写 */
- PMU_DisableBackupAccess();
- }
- }
RTC的闹钟中断功能与EINT的中断线17绑定,使用时,需同时使能EINT以及初始化中断线17。
|