
简介本资源是一套面向嵌入式初学者与STM32开发者的RTC功能专项测试工程聚焦STM32F103C8T6核心板的实时时钟模块验证与实践。项目完整实现了RTC初始化含LSE时钟源配置、时间设置、秒中断与闹钟中断服务、时间读写同步等关键功能并基于ST官方HAL库构建兼顾可读性与工程规范性适用于数据记录、低功耗定时唤醒等典型应用场景。压缩包共107个文件以30个C源文件含stm32f10x_rcc.c、usart.c、rtc.c等外设驱动和33个头文件为主干辅以32个汇编启动文件.s、4个IAR链接脚本.icf及Keil工程文件.uvproj结构清晰便于理解底层时序与HAL调用逻辑。目前已有826人学习下载资源包含可直接编译运行的完整工程、详尽的寄存器级注释与中断处理范例是掌握STM32 RTC原理与调试方法的实用入门参考。1. STM32F103C8T6核心板上的RTC测试程序不是点亮LED而是让时间真正“走起来”很多刚拿到蓝 pillSTM32F103C8T6最小系统板的人第一反应是跑个LED闪烁或串口打印“Hello World”。但真正拉开工程能力差距的往往是从第一个独立运行、掉电不丢、可校准、能唤醒的时钟模块开始——这就是RTCReal-Time Clock。本标题中的takewts_c8t6time2程序正是针对 STM32F103C8T6 核心板设计的一套轻量级 RTC 验证方案它不依赖外部晶振仅用内部RC校准不占用主频资源LSE/LSI自主运行且通过串口实时输出年月日时分秒星期毫秒计数同时支持手动校准与断电后时间持续更新。适合嵌入式初学者验证硬件时钟通路也适合作为 FreeRTOS 移植前的时间基准模块如xTaskGetTickCountFromISR()的底层支撑。你不需要 ST-Link 调试器也能验证——只要 USB-TTL 连上串口上电即见时间流动。2. 为什么选 LSI 而非 LSESTM32F103C8T6 的 RTC 时钟源选型逻辑与寄存器级配置STM32F103C8T6 的 RTC 模块支持三种时钟源HSE 分频、LSE32.768kHz 外部晶振、LSI内部低速 RC 振荡器典型值 40kHz。在最小系统板无 LSE 焊盘或未焊接晶振场景下LSE 常不可用而 HSE 分频需开启主时钟树增加功耗与启动复杂度。此时 LSI 成为唯一可行的免外围、低功耗、快速启动选项——尽管其精度较差±10%但对“时间是否在走”“日期是否递增”“唤醒是否触发”这类基础验证完全足够。2.1 LSI 启动与 RTC 时钟使能的三步寄存器操作必须按严格顺序执行否则 RTC_CRH/CTL 寄存器写入无效// Step 1: 使能 PWR 和 BKP 时钟RCC_APB1ENR RCC-APB1ENR | RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN; // Step 2: 取消备份域写保护PWR_CR PWR-CR | PWR_CR_DBP; // 关键否则后续 BKP 寄存器写失败 // Step 3: 使能 LSI 并等待就绪RCC_CSR RCC-CSR | RCC_CSR_LSION; while (!(RCC-CSR RCC_CSR_LSIRDY)); // 阻塞等待 LSI 稳定通常 10ms // Step 4: 选择 LSI 作为 RTC 时钟源RCC_BDCR RCC-BDCR ~RCC_BDCR_RTCSEL; // 清除原选择位 RCC-BDCR | RCC_BDCR_RTCSEL_0; // 0b01 → LSI RCC-BDCR | RCC_BDCR_RTCEN; // 使能 RTC注意PWR-CR | PWR_CR_DBP必须在任何 BKP 或 RTC 寄存器操作前执行且该位在复位后默认为 0。若跳过此步BKP-RTCCR写入将静默失败RTC 永远不会启动。2.2 RTC 预分频器配置把 40kHz LSI 降到 1HzRTCCLK 经预分频器RTC_PRLH/RTCPRL分频后生成 1Hz 的 RTC_TR 时间基准。LSI 实际频率非精确 40kHz需动态校准。初始配置取近似值// LSI ≈ 40kHz → 预分频值 40000 - 1 0x9C3F BKP-RTCCR 0; // 清零控制寄存器 RTC-PRLL 39999; // 低16位39999 RTC-PRLH 0; // 高16位0总值39999140000 RTC-CNTH 0; RTC-CNTL 0; // 清零计数器 RTC-CRH RTC_CRH_SECIE; // 使能秒中断可选RTC-PRLL 39999是关键——它决定每秒触发一次 RTC 中断若使能。若实测秒跳变慢于真实时间说明 LSI 实际频率 40kHz需增大该值反之则减小。后续校准即围绕此值调整。2.3 初始化 RTC 时间寄存器写入 BCD 格式而非十进制STM32F103 RTC 的时间寄存器RTC_TR/RTC_DR强制使用 BCD 编码。直接写0x15表示十进制 15而非0x0F。错误写法会导致时间显示错乱如0x0F被解析为十进制 15但 BCD 解析为 0x0F15 → 正确而0x15被解析为 BCD 的 1 和 5 → 十进制 15正确但0x25写成0x25是错的应为0x25BCD 25→ 正确易错点在于十进制转 BCD 的函数封装// 十进制转 BCD 辅助函数必须 static uint8_t DEC2BCD(uint8_t dec) { return ((dec / 10) 4) | (dec % 10); } // 设置初始时间2024-05-20 14:30:00 Monday RTC-DR (DEC2BCD(20) 16) | // YEAR: 20 (DEC2BCD(5) 8) | // MONTH: 5 (DEC2BCD(20) 0); // DATE: 20 RTC-TR (DEC2BCD(14) 16) | // HOUR: 14 (24h) (DEC2BCD(30) 8) | // MIN: 30 (DEC2BCD(0) 0); // SEC: 0 // WEEKDAY: 1 (Monday) → 写入 RTC-DR 的 bit23:21 RTC-DR | (1 21);提示RTC-DR的 WEEKDAY 位bit23:21必须手动设置RTC 不自动计算星期。若忽略读出的星期恒为 0Sunday导致printf(Week: %d, (rtc_dr 21) 0x7)输出错误。3. 串口实时输出 RTC 时间从寄存器读取到格式化打印的完整链路仅配置 RTC 不够必须验证其值是否真实更新。最直接方式是通过 USART1PA9/PA10以 115200bps 输出时间字符串。由于 STM32F103C8T6 无浮点单元需避免sprintf等重型库改用精简整数格式化。3.1 从 RTC 寄存器安全读取当前时间RTC 寄存器读取需遵循“先读高位再读低位”原则并检查 RTOFF 标志防止读取过程中计数器更新导致数据错位uint32_t rtc_time, rtc_date; uint8_t sec, min, hour, date, month, year, week; // 等待 RTC 就绪RTOFF 0 while (RTC-CRL RTC_CRL_RTOFF); // 读取时间TR和日期DR rtc_time RTC-TR; rtc_date RTC-DR; // BCD 解码逆向 DEC2BCD sec ((rtc_time 0) 0xF) (((rtc_time 4) 0xF) * 10); min ((rtc_time 8) 0xF) (((rtc_time 12) 0xF) * 10); hour ((rtc_time 16) 0xF) (((rtc_time 20) 0xF) * 10); date ((rtc_date 0) 0xF) (((rtc_date 4) 0xF) * 10); month ((rtc_date 8) 0xF) (((rtc_date 12) 0xF) * 10); year ((rtc_date 16) 0xF) (((rtc_date 20) 0xF) * 10); week (rtc_date 21) 0x7;3.2 轻量级时间字符串生成无 sprintf为避免链接libc的printf手写 10 进制转 ASCIIchar time_str[32]; uint8_t idx 0; // 格式2024-05-20 14:30:00 Mon // year time_str[idx] 0 (year / 10); time_str[idx] 0 (year % 10); time_str[idx] -; // month time_str[idx] 0 (month / 10); time_str[idx] 0 (month % 10); time_str[idx] -; // date time_str[idx] 0 (date / 10); time_str[idx] 0 (date % 10); time_str[idx] ; // hour time_str[idx] 0 (hour / 10); time_str[idx] 0 (hour % 10); time_str[idx] :; // min time_str[idx] 0 (min / 10); time_str[idx] 0 (min % 10); time_str[idx] :; // sec time_str[idx] 0 (sec / 10); time_str[idx] 0 (sec % 10); // weekday abbreviation const char* wday[] {Sun,Mon,Tue,Wed,Thu,Fri,Sat}; for (int i 0; i 3; i) time_str[idx] wday[week][i]; time_str[idx] \0;3.3 USART1 初始化与阻塞发送使用 APB2 时钟GPIOA 重映射至 PA9/PA10// USART1 GPIO: PA9(TX), PA10(RX) RCC-APB2ENR | RCC_APB2ENR_IOPAEN | RCC_APB2ENR_USART1EN; GPIOA-CRH ~(0xFF 4); // PA9/PA10 为推挽复用 GPIOA-CRH | (0xB4 4); // PA9: AFPP 50MHz, PA10: INPUT FLOATING USART1-BRR 0x22B; // 115200 72MHz (DIV 72000000 / 115200 625 → 0x271? 实测 0x22B 更准) USART1-CR1 USART_CR1_UE | USART_CR1_TE; // 使能 TX // 发送函数阻塞 void usart1_puts(const char* s) { while (*s) { while (!(USART1-SR USART_SR_TXE)); // 等待发送寄存器空 USART1-DR *s; } while (!(USART1-SR USART_SR_TC)); // 等待发送完成 }调用usart1_puts(time_str); usart1_puts(\r\n);即可在串口看到实时时间流。4. LSI 频率校准实战用毫秒定时器反推实际 LSI 值并动态修正 RTC 预分频LSI 的 ±10% 误差会导致 RTC 每天快/慢数分钟。takewts_c8t6time2的核心价值在于提供一套无需示波器、仅靠串口和已知时间源的校准方法。原理用 SysTick基于 HSE/PLL 的高精度 1ms 计数测量 RTC 秒中断的实际周期反算 LSI 实际频率再更新RTC-PRLL。4.1 启用 SysTick 并实现毫秒计时器volatile uint32_t ms_ticks 0; void SysTick_Handler(void) { ms_ticks; } // SysTick 初始化72MHz 系统时钟 → 1ms if (SysTick_Config(SystemCoreClock / 1000)) while(1);4.2 测量 RTC 秒跳变的真实间隔在 RTC 秒中断服务中记录 SysTick 值计算两次中断间的毫秒差volatile uint32_t last_systick 0; volatile uint32_t rtc_sec_interval_ms 1000; // 初始假设 1000ms void RTC_IRQHandler(void) { if (RTC-CRL RTC_CRL_SECF) { // 秒中断标志 uint32_t now ms_ticks; if (last_systick ! 0) { rtc_sec_interval_ms now - last_systick; } last_systick now; RTC-CRL ~RTC_CRL_SECF; // 清标志 } }4.3 动态更新预分频值并写入备份寄存器持久化当rtc_sec_interval_ms稳定如连续 5 次在 ±5ms 内计算新预分频值// 新预分频 (LSI_actual_freq) ≈ (40000 * 1000 / rtc_sec_interval_ms) uint32_t new_prl (40000 * 1000 rtc_sec_interval_ms/2) / rtc_sec_interval_ms; if (new_prl 0xFFFF) new_prl 0xFFFF; // 写入 RTC 预分频器需先关闭 RTC RTC-CRL ~RTC_CRL_CNF; // 进入配置模式 RTC-PRLL new_prl - 1; RTC-CRL | RTC_CRL_CNF; // 退出配置模式 // 持久化到备份寄存器BKP_DR1下次上电读取 BKP-DR1 new_prl - 1;上电初始化时优先读取BKP-DR1if (BKP-DR1 ! 0 BKP-DR1 0xFFFF) { RTC-PRLL BKP-DR1; } else { RTC-PRLL 39999; // 默认值 }提示BKP-DR1在 VBAT 供电下保持即使主电源断开也不丢失。这是 STM32F103C8T6 最小系统板实现“掉电时间不丢”的物理基础——只要板载纽扣电池或超级电容存在RTC 持续运行校准参数亦保留。5. 基于 RTC 的低功耗唤醒用 Alarm 中断从 Stop 模式唤醒并打印时间戳STM32F103C8T6 的 RTC Alarm 功能可作为超低功耗唤醒源。Stop 模式下电流约 20μA远低于 Sleep 模式~1mA。takewts_c8t6time2支持每 10 秒唤醒一次验证 RTC 在极低功耗下的可靠性。5.1 配置 Alarm 中断并进入 Stop 模式// 设置 Alarm10 秒后触发ALRMH/ALRML RTC-ALRML (DEC2BCD(10) 0); // ALARM on seconds 10 RTC-ALRMH 0; RTC-CRH | RTC_CRH_ALRIE; // 使能 Alarm 中断 // NVIC 使能 RTC 中断 NVIC_EnableIRQ(RTC_IRQn); // 进入 Stop 模式所有时钟停止仅 LSI/RTC 运行 PWR-CR | PWR_CR_LPDS; // 低功耗深度睡眠Stop SCB-SCR | SCB_SCR_SLEEPDEEP; // 深度睡眠位 __WFI(); // Wait For Interrupt5.2 Alarm 中断服务唤醒后立即打印时间void RTC_IRQHandler(void) { if (RTC-CRL RTC_CRL_ALRF) { // Alarm 标志 // 读取当前时间同前 ... usart1_puts(WAKEUP ); usart1_puts(time_str); usart1_puts(\r\n); // 重设 Alarm 为 10 秒后相对当前时间 uint32_t now_sec ...; // 从 RTC_TR 解析出当前秒 uint32_t next_sec (now_sec 10) % 60; RTC-ALRML (DEC2BCD(next_sec) 0); RTC-CRL ~RTC_CRL_ALRF; // 清标志 } }5.3 Stop 模式唤醒的关键配置表配置项寄存器值说明使能 PWR 和 BKP 时钟RCC_APB1ENRPWREN | BKPEN必须否则 PWR_CR/BKP_DR 不可写取消备份域写保护PWR_CRDBP1否则 RTC/Alarm/BKP 寄存器写无效RTC 时钟源选择RCC_BDCRRTCSEL0b01(LSI)Stop 模式下仅 LSI 可用Alarm 中断使能RTC_CRHALRIE1否则无法触发中断唤醒NVIC RTC 中断使能NVIC_ISERIRQ20STM32F103C8T6 的 RTC IRQ 编号为 20实测Stop 模式下USB-TTL 串口仍可接收唤醒日志证明 RTC Alarm 完整工作。这是构建电池供电传感器节点如温湿度记录仪的最小可行路径。6. 验证 RTC 可靠性的三个硬指标掉电保持、跨日递增、校准收敛性一个 RTC 程序是否真正可用不能只看“串口有输出”而要验证三个工程级硬指标。takewts_c8t6time2的测试流程即围绕这三点设计。6.1 掉电保持测试拔掉 USB等待 30 秒再上电验证时间连续性操作步骤上电运行程序串口输出2024-05-20 14:30:00 Mon记录当前秒值如30拔掉 USB 线切断 VDD仅靠 VBAT 供电等待 30 秒用手机秒表计时重新插入 USB观察首条输出时间是否为2024-05-20 14:30:30 Mon失败现象输出回到2024-05-20 14:30:00或1970-01-01。原因必为PWR_CR_DBP未置位或RCC_BDCR_RTCEN未使能或 VBAT 未接入最小系统板需外接 CR1220 电池。6.2 跨日递增测试连续运行 24 小时检查日期是否自动进位无需真等 24 小时可加速验证将初始时间设为2024-05-31 23:59:58观察次秒是否变为2024-06-01 00:00:00且RTC-DR的 MONTH 和 DATE 字段正确更新特别检查2024-02-28→2024-02-29闰年是否触发关键代码点RTC 硬件自动处理月份天数与闰年但需确保RTC-DR的 YEAR 字段为 BCD 且 ≥20否则视为 19xx闰年规则不同。6.3 校准收敛性测试记录 5 次校准后的rtc_sec_interval_ms值运行校准循环记录每次计算出的new_prl校准轮次rtc_sec_interval_msnew_prl误差ppm1102539024-250002101239504-125003100839680-80004100339880-30005100139960-1000若 5 轮后new_prl波动 10则认为校准收敛。此时 RTC 日误差可控制在 ±10 秒内满足工业计时基本要求。最终当你看到2024-05-20 14:30:00 Mon在断电后精准跳变为2024-05-20 14:30:30 Mon且new_prl稳定在39960附近你就真正掌握了 STM32F103C8T6 最小系统板上 RTC 的全链路控制能力——它不再是一个外设模块而是你嵌入式系统的时间心脏。本文还有配套的精品资源点击获取