一、RTC 功能概述
实时时钟 RTC 是 CW32F030 内部一个专用的高精度计数器/定时器,负责维护完整的日历信息:秒、分、时(支持 12/24 小时制)、星期、日、月份和年份,全部以 BCD 码格式存储,硬件自动做闰年修正,大小月和闰年都不用软件操心。除了最基本的走时,CW32F030 的 RTC 还集成了不少实用的功能:两个完全独立的闹钟(闹钟 A 和闹钟 B),时间、日期可组合设定,闹钟匹配时产生中断,还能通过 RTC_OUT 引脚输出匹配标志;周期中断模块,周期从 0.5 秒一直到 1 个月;16 位自动唤醒定时器,定时范围 61 微秒到约 145 小时,专为低功耗设计,可工作在 MCU 的全部工作模式;时间戳功能,外部引脚触发时把当前日期时间保存下来;1Hz 方波输出引脚;以及精度高达 0.119ppm 的内部时钟误差补偿电路。特别要说的是,CW32F030 内置了一路经独立校准的 32kHz RC 时钟(LSI),所以不接外部晶振 RTC 也能跑。RTC 可工作在运行模式、休眠模式和深度休眠模式,深度休眠下高速时钟全部关闭,低速时钟(LSE、LSI)和 RTC 仍继续运行,这正是低功耗项目用 RTC 定时唤醒的基础。
二、时钟源与硬件配置
RTC 的计数时钟 RTCCLK 通过控制寄存器 RTC_CR1 的 SOURCE 位域选择,可选 LSE(外部 32.768kHz 晶振)、LSI(内部 32kHz RC)或 HSE 分频。HSE 因精度受限,只能把 RTC 当作一般定时/计数器使用,不能用于日历;真正做日历和闹钟要用 LSE 或 LSI。
时钟源的选择本质上是精度和成本的权衡。低功耗、走时精度要求不高的场合直接用内部 LSI,省掉晶振和负载电容,电路最简单,但内部 RC 温漂比晶振大,长期走时会偏;要求走时准确的场合(时钟、电表、带时间戳的记录仪等)外接 32.768kHz 晶振走 LSE。无论选 LSE 还是 LSI,软件上都必须先使能该时钟源并等待其稳定,再配置 RTC。
LSE 的硬件电路有讲究。晶振接在 OSC32_IN 和 OSC32_OUT 两个引脚之间,两端各接一个起振电容到地。电容取值要与晶振标称负载电容匹配,计算公式为 CL=(CL1×CL2)/(CL1+CL2)+C0,其中 C0 为板上杂散电容,一般按 4~6pF 估算;例如标称 12.5pF 的晶体,两个起振电容常见取 15pF。晶振和起振电容要尽量靠近芯片引脚放置,走线短,并远离高频信号和电源走线,否则可能起振困难或走时漂移。很多“RTC 不走”的板子,最后查出来都是晶振电路的问题。
三、初始化配置流程
CW32F030 的 RTC 寄存器默认带锁。上电复位后,除 RTC_KEY 和 RTC_ICR 之外的所有 RTC 寄存器都处于锁定保护状态,直接写是写不进去的。初始化前必须先向 RTC_KEY 键值寄存器依次写入 0xCA、0x53 解除锁定;配置完成后再向 RTC_KEY 依次写入 0xCA、0x00 重新锁定,防止程序异常时意外改写 RTC。
标准初始化流程(用户手册 12.3.3)大致为:解锁 → 配置 RTC_CR0.H24 选择 12/24 小时制 → 配置 RTC_CR1.SOURCE 选择时钟源 → 写入初始时间和日期 → 按需配置周期中断、自动唤醒、闹钟 A/B → 配置 RTC_IER 中断使能 → 配置时间戳 → 将 RTC_CR0.START 置 1 启动 RTC → 重新锁定。使用官方标准库时,把日期时间填进 RTC_InitTypeDef 后调用 RTC_Init 即可一步完成。
RTC_InitTypeDef RTC_InitStruct = {0};
RTC_InitStruct.DateStruct.Year = 0x26; /* BCD:2026 年 */
RTC_InitStruct.DateStruct.Month = 0x09;
RTC_InitStruct.DateStruct.Day = 0x19;
RTC_InitStruct.DateStruct.Week = RTC_Weekday_Saturday;
RTC_InitStruct.TimeStruct.Hour = 0x17;
RTC_InitStruct.TimeStruct.Minute = 0x00;
RTC_InitStruct.TimeStruct.Second = 0x00;
RTC_InitStruct.TimeStruct.H24 = 1; /* 24 小时制 */
RTC_InitStruct.RTC_ClockSource = RTC_RTCCLK_FROM_LSE;
RTC_Init(&RTC_InitStruct);这里有一条特别重要、也最容易忽略的经验:初始化之前一定要先检查 RTC 是否已经在运行,以及本次复位是不是上电复位。手册给出的标准做法是检查 SYSCTRL_RESETFLAG:如果 RTC 已经运行,就不要再做初始化,避免打断正在走的时钟导致定时不准;如果本次不是上电复位(例如看门狗复位、软件复位、低电压复位),时间和日期寄存器的内容其实还在,不要重新写时间和日期,只需重新配置控制寄存器并重新启动 RTC,这样复位就不会丢时间。只有上电复位(POR)才需要完整设置一遍时间日期。这一点与主流 MCU 备份域的设计思路一致,不少初次使用 CW32 的同学不知道,每次复位都重写时间,结果时间总是不对。
时间日期全部是 BCD 格式,例如 2026 年 9 月 19 日 17:00:00,写入的是 0x26、0x09、0x19、0x17、0x00、0x00。硬件在写入时会自动做合法性检查,32 日、13 月、61 秒、2A 时这类非法值会被拒绝写入,所以不必自己写校验,但这也意味着一旦 BCD 转换代码写错,时间会“悄悄”写不进去。星期取值 0~6,其中 0 表示星期日。还要注意 12 小时制下 HOUR 最高位是 AM/PM 标志(0x21 表示 PM 1 点),配置时 H24、AMPM、HOUR 三者必须配套,否则显示时间会差 12 小时。
实际使用代码如下所示:
void ShowTime(void)
{
RTC_TimeTypeDef RTC_TimeStruct;
RTC_DateTypeDef RTC_DateStruct;
static uint8_t *WeekdayStr[7]={"SUN","MON","TUE","WED","THU","FRI","SAT"};
static uint8_t *H12AMPMStr[2][2]={{"AM","PM"},{"",""}};
RTC_GetDate(&RTC_DateStruct);
RTC_GetTime(&RTC_TimeStruct);
printf(".Date is 20%02x/%02x/%02x(%s).Time is %02x%s:%02x:%02x\r\n", RTC_DateStruct.Year, RTC_DateStruct.Month, RTC_DateStruct.Day, WeekdayStr[RTC_DateStruct.Week], RTC_TimeStruct.Hour, H12AMPMStr[RTC_TimeStruct.H24][RTC_TimeStruct.AMPM],RTC_TimeStruct.Minute, RTC_TimeStruct.Second);
}
void RTC_IRQHandlerCallBack(void)
{
if (RTC_GetITState(RTC_IT_ALARMA))
{
RTC_ClearITPendingBit(RTC_IT_ALARMA);
printf("*********Alarm!!!!\r\n");
}
if (RTC_GetITState(RTC_IT_INTERVAL))
{
RTC_ClearITPendingBit(RTC_IT_INTERVAL);
ShowTime();
}
}
我要赚赏金
