二十八、立创·梁山派天空星开发板RTC掉电走时实验:基于HC32F4A0的备份域与外部32.768KHz晶振配置

二十八、立创·梁山派天空星开发板RTC掉电走时实验:基于HC32F4A0的备份域与外部32.768KHz晶振配置 二十八、立创·梁山派天空星开发板RTC掉电走时实验基于HC32F4A0的备份域与外部32.768KHz晶振配置最近在做一个数据记录仪的项目需要设备在完全断电后时间还能继续往前走这样下次上电时数据的时间戳才是连续的。这功能听起来简单但很多新手朋友在配置RTC实时时钟时总会遇到掉电后时间就归零的问题。今天我就以立创·梁山派天空星开发板HC32F4A0芯片为例手把手带你搞定RTC的掉电走时功能。这个实验的核心就是利用芯片的“备份域”和外部低速晶振。简单来说备份域是芯片里一块特殊的存储区域它有自己的独立电源通常由纽扣电池供电即使主电源VDD断掉只要电池有电这块区域里的数据包括RTC的时间就能保持住。而外部32.768KHz晶振则是为RTC提供精准、低功耗的时钟源确保断电后计时依然准确。注意要实现掉电走时硬件上必须准备好两样东西外部32.768KHz晶振天空星开发板高配版已经板载了这颗晶振。如果你的板子是“青春版”需要自己焊接这颗晶振否则程序会卡死。纽扣电池开发板背面预留了电池座CR1220但需要你自己焊接上并安装好电池。没有电池断电后备份域就没电了一切白搭。好了准备工作就绪咱们开始进入正题。1. RTC掉电走时的核心原理在动手写代码前咱们先花两分钟把原理搞明白这样后面配置起来心里才有底。HC32F4A0的RTC模块和一部分寄存器被划分在一个叫“备份域”的区域里。这个区域非常独立独立供电可以由主电源VDD供电也可以由VBAT引脚接纽扣电池供电。当VDD掉电后只要VBAT有电备份域就能继续工作。独立时钟RTC的时钟可以来自芯片内部的低速RC振荡器LRC约32KHz也可以来自外部的32.768KHz晶振LXT。内部时钟在VDD掉电后会停止而外部晶振依靠电池供电可以持续运行。写保护为了防止程序跑飞意外修改关键数据比如时间备份域上电后默认是锁住的要操作它必须先解锁。所以我们的配置流程就是围绕这三件事展开的解锁备份域、选择外部晶振作为时钟源、正确初始化RTC并保存配置状态。2. 代码框架与关键宏定义我们先来看看整个程序需要用到的头文件和几个关键的宏定义。这些定义是后续操作的“地址地图”和“暗号”。创建一个rtc.h头文件写入以下内容#ifndef __RTC_H__ #define __RTC_H__ #include hc32_ll.h // 核查码暗号 #define BACKUP_DATA 0x77 // 备份寄存器地址范围 96~127 #define RTC_BACKUP_REG_START (96U) #define RTC_BACKUP_DATA_SIZE (32U) // 备份寄存器读取的地址我们使用起始地址 #define RTC_BACKUP_CHECK_ADDR RTC_BACKUP_REG_START // 函数声明 void rtc_init(void); void rtc_date_config(void); void rtc_write_backupReg(void); uint8_t rtc_check_backupReg(void); uint8_t *RTC_DisplayWeekday(uint8_t u8Weekday); void RTC_ShowDateTime(void); #endif这里定义了三个关键东西BACKUP_DATA (0x77)这是一个“核查码”相当于一个暗号。我们把它存到备份寄存器里每次上电都去读一下。如果读到了这个暗号就说明RTC之前已经初始化过了不需要重复配置避免时间被重置。RTC_BACKUP_REG_START (96)备份寄存器在芯片里的起始地址。HC32F4A0有一片备份寄存器地址96~127掉电后数据不会丢失我们正好用它来存我们的“暗号”。RTC_BACKUP_CHECK_ADDR我们存放和读取“暗号”的具体地址这里就用了起始地址96。3. 手把手配置RTC现在我们来创建rtc.c文件实现核心功能。我会把整个初始化过程rtc_init()拆解开一步步讲清楚。3.1 第一步解除写保护与状态检查任何对备份域的操作第一步必须是解除写保护。同时我们要先检查一下“暗号”看看RTC是不是已经配置过了。void rtc_init(void) { // 1. 关闭所有外设的写保护这是操作备份域的前提 LL_PERIPH_WE(LL_PERIPH_ALL); // 2. 检查备份寄存器中的“暗号”是否已存在 uint8_t ret rtc_check_backupReg(); if(ret 0) { // 如果已经存在暗号说明RTC早已初始化直接返回避免覆盖时间 printf(RTC 已经配置过直接使用\r\n); return; } // 如果没找到暗号则继续下面的初始化流程...rtc_check_backupReg()函数的实现很简单就是去指定的备份寄存器地址读一个数看看是不是我们约定的0x77。// 检查备份寄存器中的核查码 uint8_t rtc_check_backupReg(void) { uint8_t Check_Data PWC_BKR_Read(RTC_BACKUP_CHECK_ADDR); if(Check_Data BACKUP_DATA) { return 0; // 找到了返回0 } return 1; // 没找到返回1 }3.2 第二步复位与停止RTC如果是第一次配置我们需要对相关模块进行复位并确保RTC处于停止状态这样才能安全地进行参数设置。// 3. 重启VBAT域确保备份域处于干净状态 PWC_VBAT_Reset(); // 4. 尝试重置RTC计数器 if(LL_ERR_TIMEOUT RTC_DeInit()) { printf(重置 RTC 失败\r\n); return; } // 5. 停止RTC。在配置前必须先让它停下来。 RTC_Cmd(DISABLE);3.3 第三步配置RTC时钟源与参数这是最关键的一步我们要告诉RTC使用外部32.768KHz晶振作为时钟源。// 6. 初始化RTC结构体 stc_rtc_init_t stcRtcInit; (void)RTC_StructInit(stcRtcInit); // 先用默认值填充结构体 // 7. 配置RTC结构体参数 stcRtcInit.u8ClockSrc RTC_CLK_SRC_LRC; // 时钟源低速晶振外部32.768KHz stcRtcInit.u8HourFormat RTC_HOUR_FMT_24H; // 时间格式24小时制 stcRtcInit.u8IntPeriod RTC_INT_PERIOD_PER_SEC; // 中断周期每秒一次可用于唤醒 // 8. 将配置写入RTC硬件 (void)RTC_Init(stcRtcInit);重点解释RTC_CLK_SRC_LRC 这里的LRC在HC32的库中指的是低速晶振时钟源即我们板载的32.768KHz外部晶振。务必选择这个才能实现掉电走时。如果选择内部时钟源主电源一断时钟就停了。3.4 第四步设置初始日期和时间时钟跑起来了我们得给它设定一个起点。这里需要配置两个结构体一个用于日期一个用于时间。// RTC 日期和时间配置函数 void rtc_date_config(void) { stc_rtc_date_t stcRtcDate; stc_rtc_time_t stcRtcTime; // 设置日期2024年4月11日星期四 stcRtcDate.u8Year 24U; // 年份0-99对应2000-2099 stcRtcDate.u8Month RTC_MONTH_APRIL; // 月份使用库定义的宏四月 stcRtcDate.u8Day 11U; // 日 stcRtcDate.u8Weekday RTC_WEEKDAY_THURSDAY; // 星期库定义的宏周四 // 设置时间21点28分01秒 stcRtcTime.u8Hour 21U; // 小时 (0-23) stcRtcTime.u8Minute 28U; // 分钟 (0-59) stcRtcTime.u8Second 1U; // 秒 (0-59) stcRtcTime.u8AmPm RTC_HOUR_24H; // 上午下午指示24小时制下固定为此值 // 将日期和时间写入RTC硬件寄存器 if (LL_OK ! RTC_SetDate(RTC_DATA_FMT_DEC, stcRtcDate)) { DDL_Printf(设置日期失败\r\n); } if (LL_OK ! RTC_SetTime(RTC_DATA_FMT_DEC, stcRtcTime)) { DDL_Printf(设置时间失败\r\n); } }在rtc_init()函数中调用这个配置函数/* 更新日期和时间 */ rtc_date_config();3.5 第五步启动RTC并写入核查码所有配置完成后就可以启动RTC了。别忘了把我们约定的“暗号”写入备份寄存器这样下次上电时程序就知道已经初始化过了。/* 启动 RTC 计数 */ RTC_Cmd(ENABLE); /* 写入核查码到备份寄存器 */ rtc_write_backupReg(); }rtc_write_backupReg()函数实现如下// 写入核查码到备份寄存器 void rtc_write_backupReg(void) { printf(开始写入核查码\r\n); PWC_BKR_Write(RTC_BACKUP_CHECK_ADDR, BACKUP_DATA); }4. 读取与显示时间RTC配置好之后它会自己默默地走时。我们需要一个函数来读取并显示当前时间。为了方便显示星期我们先定义一个中文星期数组。uint8_t WeekDay[8][9] {星期日,星期一,星期二,星期三,星期四,星期五,星期六,错误!}; // 根据星期值返回对应的中文字符串 uint8_t *RTC_DisplayWeekday(uint8_t u8Weekday) { switch (u8Weekday) { case RTC_WEEKDAY_SUNDAY: return (WeekDay[0][0]); case RTC_WEEKDAY_MONDAY: return (WeekDay[1][0]); case RTC_WEEKDAY_TUESDAY: return (WeekDay[2][0]); case RTC_WEEKDAY_WEDNESDAY: return (WeekDay[3][0]); case RTC_WEEKDAY_THURSDAY: return (WeekDay[4][0]); case RTC_WEEKDAY_FRIDAY: return (WeekDay[5][0]); case RTC_WEEKDAY_SATURDAY: return (WeekDay[6][0]); default: return (WeekDay[7][0]); } } // 显示当前日期和时间 void RTC_ShowDateTime(void) { stc_rtc_date_t stcCurrentDate; stc_rtc_time_t stcCurrentTime; // 从RTC硬件读取当前的日期和时间 RTC_GetDate(RTC_DATA_FMT_DEC, stcCurrentDate); RTC_GetTime(RTC_DATA_FMT_DEC, stcCurrentTime); // 格式化打印到串口 printf(20%02d年%02d月%02d日 【%02d:%02d:%02d】 %s\r\n, stcCurrentDate.u8Year, stcCurrentDate.u8Month, stcCurrentDate.u8Day, stcCurrentTime.u8Hour, stcCurrentTime.u8Minute, stcCurrentTime.u8Second, RTC_DisplayWeekday(stcCurrentDate.u8Weekday)); }5. 主函数与实验验证最后我们在main.c中调用这些函数完成整个实验。#include board.h #include bsp_uart.h #include stdio.h #include rtc.h int32_t main(void) { // 硬件初始化 board_init(); uart1_init(115200U); // 初始化串口1用于打印信息 // RTC初始化内含掉电判断逻辑 rtc_init(); while(1) { // 每秒打印一次当前时间 RTC_ShowDateTime(); delay_ms(1000); } }实验现象第一次给开发板上电通过串口助手会看到打印“开始写入核查码”然后时间从2024年04月11日 【21:28:01】 星期四开始每秒更新。此时拔掉USB供电线确保已焊接好电池并安装开发板主电源断开。等待十几秒或更长时间。重新插上USB线供电。串口助手会打印“RTC 已经配置过”并且显示的时间是断电后经过的时间而不是重新从21:28:01开始。恭喜你这说明你的RTC已经成功实现了掉电走时。电池在断电期间为备份域和外部晶振供电维持了RTC的运行。最后再强调一下硬件准备这个实验成功的前提一定是你的天空星开发板已经焊接了外部32.768KHz晶振并且在背面电池座上安装了CR1220纽扣电池。如果掉电后时间还是复位了首先检查这两点。