资讯动态

STM32 RTC例程全解析:7z解压、备份域与走时校准

发布时间:2026/9/16 14:06:04 来源:尧图企业网站定制
简介面向STM32开发者的RTC实时时钟示例工程包适用于正在学习HAL库或标准外设库的入门与进阶开发者。压缩包共54个文件含15个头文件、14个C源文件、14个中间目标文件并附hex、elf、bin等烧录与调试产物整体仅187KB目录结构清晰明确便于按模块查阅和移植。示例围绕STM32的RTC外设展开详细演示了从RTC初始化、LSE与LSI时钟源选择到日历时间读写、闹钟中断、唤醒定时器、备份寄存器保存关键数据以及低功耗STOP/STANDBY模式下RTC继续计时并唤醒系统的完整流程。代码中区分了主程序、延时函数与HAL库调用方便理解实时时钟的配置顺序和回调机制从外设配置到底层调用均有迹可循。目前已有249人学习下载适合需要快速掌握STM32时间基准设计、做低功耗产品开发或在现有项目中集成RTC功能的开发者参考该工程可少走弯路直接复用核心代码。1. 拿到 Example_RTC.7z 时先想清楚这包东西要解决什么一个名叫 Example_RTC.7z 的压缩包在 STM32 开发里太常见了。官方固件包或者开发板厂商在分发例程时为了少传几个来回经常把整个 HAL 库、中间件、工程文件甚至文档一起打成 7z。RTC 例程之所以值得单独拿出来跑一遍是因为它同时踩到几块硬骨头32.768kHz 晶振电路能不能起振、备份域寄存器的读写是否正常、系统从 STOP 模式被闹钟唤醒时中断能不能可靠触发。这些能力不只在“做个电子钟”时才用得上产品里的定时上报、日志时间戳、低功耗设备的周期唤醒全都要依赖同一套 RTC 机制。这篇文章按一个例程包应该被对待的顺序来写先解决怎么把它安全解压出来再讲例程背后的 RTC 原理和参数然后落到代码怎么读、怎么改最后给出一个用串口脚本验证走时精度的具体做法。适合刚搭好 Keil 或 STM32CubeIDE 环境、想用官方例程验证板子 RTC 的开发者也适合想把 RTC 唤醒功耗做进量产产品的老手对照边界参数。2. 解压与校验用 7z 命令行打开 RTC 例程的最小环境2.1 为什么嵌入式例程包偏爱 7z 而不是 zip7z 的压缩率通常比 zip 高 10% 到 20%一个包含完整 HAL 库的 RT-Thread 或者 STM32Cube 工程解压后可能超过 500MB压成 7z 能省不少分发带宽。另一个原因是 7z 支持 AES-256 加密部分板卡商会给未公开的例程包加口令而 zip 的传统加密算法在安全性上已经不够看了。所以第一个结论是不要直接把 .7z 当成 zip 去解压。Windows 资源管理器自带的 zip 支持处理不了 7zLinux 发行版默认也不带 7z 命令。你需要 p7zip 或者 7-Zip。在 Ubuntu/Debian 上安装sudo apt install p7zip-fullmacOS 上如果装了 Homebrew用brew install sevenzip安装后命令是7zz。Windows 上可以用官方 7-Zip 的安装包装完命令行的7z.exe位于安装目录下例如C:\Program Files\7-Zip\7z.exe。这里建议把它加入系统 PATH后面所有操作都直接在终端里做比图形界面容易复现。2.2 解压前先算哈希7z 文件完整性检查嵌入式例程的坑很多时候不是代码写错而是下载过程把压缩包弄坏了。7z 文件自带 CRC 校验解压时损坏文件会直接报错但与其解压到一半才失败不如先校验整个文件的哈希值。往下发例程的页面通常会在旁边标注 SHA256你拿到文件后第一时间算出来比对# Linux / macOS sha256sum Example_RTC.7z # Windows PowerShell Get-FileHash .\Example_RTC.7z -Algorithm SHA256如果发布页没有给出参考哈希就把下载好的文件解压两次分别用7z t做测试7z t Example_RTC.7z7z t会逐个检查压缩包内文件的 CRC 和长度输出Everything is Ok才代表文件完整。这一步不能省尤其是走网盘转存、邮件附件这些渠道拿到的例程包来源不可控时更要先过一遍。2.3 用 7z 命令行解压出 STM32 例程工程校验通过后执行解压命令如下7z x Example_RTC.7z -o~/rtc_lab -y参数含义逐一说清x表示解压并保留压缩包内的目录结构这对例程工程很重要因为 Keil/MDK 工程文件里记录的相对路径依赖原本的目录层级-o后直接跟输出目录注意-o和路径之间不要加空格这是 7z 命令行最容易踩的坑-y表示所有覆盖确认直接回答 Yes避免解压中途卡住。如果这个 7z 包被加密过比如某个开发板厂家在百度网盘分享里把例程包设置了口令在x后面加-p口令7z x Example_RTC.7z -pSTM32_RTC_2024 -o~/rtc_lab -y口令错误的典型表现是 7z 直接报Wrong password而不是解压失败。还有一种情况是压缩包用了 Unicode 文件名加密解压时要额外加-mheon才能看到文件名遇到再说即可。解压过程中如果看到文件名乱码通常是压缩包在 Windows 上用 GBK 编码打包而当前系统是 UTF-8 环境。解法是在 Windows 上用 7-Zip 图形界面解压一次它默认走系统代码页或者命令行加-scsGBK指定源码字符集7z x Example_RTC.7z -scsGBK -o~/rtc_lab -y2.4 解压后的目录结构怎么认ST 官方例程的目录结构一般分为三层Projects下按开发板型号分目录每个板子目录里是具体外设例程Drivers下是 CMSIS 和 HAL 驱动Middlewares放 FreeRTOS、FATFS 这类第三方组件。RTC 例程通常出现在类似Projects/STM32F4-Discovery/Examples/RTC/的位置里面是 Keil 的.uvprojx工程文件。如果你拿到的不是官方包而是正点原子或野火这类开发板例程路径可能变成实验X RTC实时时钟实验\这种中文目录。中文路径在 Keil 里偶尔会触发编码问题我的习惯是解压后马上把项目复制到纯英文路径下比如D:\stm32_proj\rtc_lab再打开工程。这一步能省掉后面cant open file一类莫名其妙的编译报错。3. RTC 例程背后的原理备份域、LSE 与晶振电容计算3.1 为什么 RTC 寄存器要放在备份域里读 RTC 例程的代码时第一件让人困惑的事是为什么操作 RTC 之前要先打开 PWR 时钟还要调用HAL_PWR_EnableBkUpAccess()。这是 STM32 的系统架构决定的RTC 的日历寄存器、备份寄存器、以及一部分唤醒逻辑都放在备份域里。备份域由VBAT引脚单独供电。系统主电源掉电后只要 VBAT 上还挂着纽扣电池或超级电容RTC 就能继续走时备份寄存器也能保存关键数据。为此芯片设计上做了隔离主电源域的程序不能直接访问备份域必须先把电源接口里的备份域访问使能打开。例程里如果漏掉这一步读出来的时间永远是复位值而且写不进去。这段初始化顺序在官方例程里通常是这样的__HAL_RCC_PWR_CLK_ENABLE(); HAL_PWR_EnableBkUpAccess();__HAL_RCC_PWR_CLK_ENABLE()打开 PWR 模块时钟HAL_PWR_EnableBkUpAccess()解除备份域写保护。这两行必须是任何 RTC 操作之前的固定动作。在电池供电的产品里VBAT 引脚不能悬空否则 RTC 一掉电就归零例程跑得通也没用。3.2 LSE 与 LSI例程默认走哪条时钟源RTC 的时钟有两个来源LSE 和 LSI。LSE 是外部低速晶振典型频率是 32.768kHz走时精度取决于晶振本身通常能做到 ±20ppm 以内LSI 是芯片内部的低速 RC 振荡器无需外部器件但精度差一到两个数量级不同温度下的频率漂移明显。官方 RTC 例程几乎都是基于 LSE 写的因为只有 LSE 才能保证闹钟按“实时时间”触发。两者对比特性LSE外部晶振LSI内部 RC频率32.768kHz典型 32kHz系列间有差异精度依赖晶振常见 ±20ppm全温区 ±5% 量级功耗低极低外部器件晶振 两颗负载电容无适合场景日历、闹钟、低功耗定时不需要精确时间的唤醒间隔如果例程初始化配置里把 LSE 改成 LSI日误差会达到秒级。这适合“每 10 分钟唤醒一次睡 10 分钟”这种对时间点不敏感的场景但不适合做 RTC 日历。例程代码定位到HAL_RTC_MspInit()看 RCC 配置结构体里的时钟源选择就能判断板子当前用哪种。3.3 32.768kHz 晶振的负载电容计算晶振电路不起振是 RTC 例程跑不通的第一大原因超过一半是负载电容配错了。MCU 的OSC32_IN和OSC32_OUT两脚之间跨接晶振每个引脚对地接一颗电容两颗电容串联再与引脚寄生电容并联构成晶振的负载电容。负载电容的计算式是CL (C1 * C2) / (C1 C2) Cstray其中Cstray是 PCB 走线和芯片引脚引入的寄生电容取值一般在 2pF 到 5pF 之间。以一颗CL 12.5pF的 32.768kHz 晶振为例假设寄生电容取 3pF则( C * C ) / ( 2C ) 3 12.5 C 2 * ( 12.5 - 3 ) 19pF取标准系列值 20pF。如果晶振规格书写的是CL 6pF同样算出来每边大约 6pF工厂样品阶段用两颗 6.8pF 电容验证也能起振但频偏会向正方向跑长期定时误差增大。例程配套的原理图里电容值可以直接抄自绘板子时要按晶振数据手册的CL重新算不要照搬其他板的 22pF。另外晶振负载电容对走时的影响比代码里任何校准寄存器都更基础电容错 10pF日误差可能多出几十秒。3.4 例程里 RTC 初始化代码的执行顺序看一遍官方例程的main.cRTC 初始化固定分四步。第一步使能 PWR 时钟并打开备份域访问第二步判断备份域是否已经初始化过第三步如果是首次上电就配置 RTC 时间第四步启动闹钟或者周期唤醒。第二步很关键HAL 库的HAL_RTC_Init()每次调用都会执行一遍复位 RTC 的流程如果 main 函数里每次上电都盲目调用会导致 RTC 时间被清掉再重设。官方例程用一个备份寄存器做标志if (HAL_RTCEx_BKUPRead(hrtc, RTC_BKP_DR0) ! RTC_BKP_MAGIC) { RTC_TimeRegulate(hrtc); HAL_RTCEx_BKUPWrite(hrtc, RTC_BKP_DR0, RTC_BKP_MAGIC); }这段代码的含义是备份寄存器 DR0 里的值不是预设的魔数RTC_BKP_MAGIC就认为 RTC 是首次使用重新设置时间否则直接跳过时间设定。这样系统复位不会重置时钟但掉电池后再上电又能正确重新校时。这个模式在写自己的 RTC 驱动时可以原样保留。4. 例程代码怎么读、怎么改时间设定与 RTC 唤醒4.1 例程代码的主干结构打开 RTC 例程的 Keil 工程后不要急着编译先按调用关系看三个函数。main()里先做时钟树配置然后调RTC_Init()或HAL_RTC_Init()完成 RTC 初始化最后进入while(1)主循环。如果例程用了闹钟中断主循环里通常是个空循环或者WFI指令等待唤醒。RTC 例程分两类。一类是“电子钟”型主循环里每秒调用一次时间读取通过串口打印当前日期时间另一类是“闹钟唤醒”型RTC 秒中断或闹钟中断里翻转 LED或者直接进入低功耗模式等待 RTC 闹钟唤醒。拿到例程先看stm32f4xx_hal_msp.c里的HAL_RTC_MspInit()这里能看到 RTC 时钟源是 LSE 还是 LSI也能看到中断优先级怎么配的。void HAL_RTC_MspInit(RTC_HandleTypeDef* hrtc) { RCC_OscInitTypeDef rtc_osc {0}; __HAL_RCC_PWR_CLK_ENABLE(); HAL_PWR_EnableBkUpAccess(); rtc_osc.OscillatorType RCC_OSCILLATORTYPE_LSE; rtc_osc.LSEState RCC_LSE_ON; rtc_osc.PLL.PLLState RCC_PLL_NONE; if (HAL_RCC_OscConfig(rtc_osc) ! HAL_OK) { Error_Handler(); } __HAL_RCC_RTC_ENABLE(); __HAL_RCC_RTC_CLKSOURCE_CONFIG(RCC_RTCCLKSOURCE_LSE); }4.2 从串口读改时间RTC_TimeRegulate 与时间格式转换例程里设置和读取时间都用 HAL 结构体小时、分钟、秒通过RTC_TimeTypeDef传入日期通过RTC_DateTypeDef传入。官方例程普遍提供一个RTC_TimeRegulate函数里面用HAL_RTC_SetTime()设置时间。读时间则用下面的写法RTC_TimeTypeDef sTime {0}; RTC_DateTypeDef sDate {0}; HAL_RTC_GetTime(hrtc, sTime, RTC_FORMAT_BIN); HAL_RTC_GetDate(hrtc, sDate, RTC_FORMAT_BIN); printf(日期: %04d-%02d-%02d 时间: %02d:%02d:%02d\r\n, sDate.Year 2000, sDate.Month, sDate.Date, sTime.Hours, sTime.Minutes, sTime.Seconds);这里的RTC_FORMAT_BIN表示直接用二进制数值读取RTC_FORMAT_BCD则返回 BCD 编码需要手动转换。例程里如果用串口助手看到时间是一串十六进制乱码多半是格式用错了。设置时间的代码里最容易被忽略的是同步预分频和异步预分频。HAL 的RTC_TimeTypeDef里有TimeFormat字段12 小时制下还要区分上午下午例程默认 24 小时制这个字段保持RTC_HOURFORMAT_24即可。4.3 编译前的芯片型号与头文件修改例程包是从官方仓库同步下来的工程配置针对特定芯片型号。比如Example_RTC.7z里的工程是STM32F407VG而你手头的板子是STM32F103C8T6直接打开工程编译会报一大堆undefined symbol核心原因是器件宏定义不匹配。在 Keil MDK 里修改型号要注意两处。第一处是魔术棒Options for Target里的 Device 标签页重新选择芯片型号第二处是 C/C 标签页里的 Define 宏比如原来写的是STM32F407xx切换到 F1 系列要改成STM32F103xE或STM32F103xB这个宏决定 HAL 库编译哪一套寄存器定义。两处要同步修改只改一处会在链接阶段报错。如果工程是用 STM32CubeMX 生成的改型号的正规做法是在 CubeMX 的Device选择框里重新选中目标芯片重新生成代码。手动改工程文件容易漏掉引脚重映射CubeMX 会帮你检查哪些外设引脚在新型号上不存在。这里还常遇到“Keil5 兼容 C51 和 STM32”的问题。很多工程师装过 Keil C51 后又装 MDK结果打开 STM32 工程时 Device 列表里只看到 8051。这是因为两个工具链虽然共用同一个 Keil 界面但工程类型不同安装顺序或安装路径冲突时会互相覆盖。解决方法是确认启动的是UV4.exeMDK 的入口在 Pack Installer 里确认已经安装对应的 STM32 器件包比如Keil::STM32F1xx_DFP。器件包缺失时打开工程会提示Device not found。安装器件包的命令行方式是UV4.exe --update-packs也可以从 Pack Installer 图形界面里勾选需要的 pack联网自动下载。这个步骤做完再回到 Device 选择界面型号列表才会出现完整的 STM32 系列。4.4 用 RTC 闹钟唤醒代替 delay 的工程改造如果例程只是循环打印时间对产品没什么直接用处。实际项目里更常见的是把 RTC 闹钟改造成系统唤醒源替代HAL_Delay()这种忙等。改造方式是在 RTC 初始化后配置闹钟 A并使能它的 EXTI 中断HAL_RTC_SetAlarm_IT(hrtc, sAlarm, RTC_ALARM_A); void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) { // 记录唤醒时间置位唤醒标志 wake_flag 1; }进入低功耗前把 EXTI 线配置到 RTC 闹钟事件上。不同类型的芯片引脚定义的 EXTI 线不一样以 F4 为例闹钟 A 对应 EXTI 17 线需要在stm32f4xx_hal_msp.c里配置HAL_RTC_AlarmA_EXTI_IRQHandler()的中断入口。改造时注意不要在中断回调里做耗时操作正确姿势是只置标志位回到主循环后再处理数据上报、传感器读取这些逻辑。配合HAL_PWR_EnterSTOPMode()RTC 闹钟唤醒的功耗可以压到微安级别这是低功耗产品常用的套路。前提是例程里 RTC 时钟源选 LSELSI 的功耗虽然更低但定时不准不适合“凌晨 3 点上报数据”这类业务。5. 验证 RTC 走时精度的串口测量脚本与降漂移技巧5.1 用 Python 脚本统计串口时间戳漂移例程跑起来后板子每秒通过串口输出一行格式化的 RTC 时间。光用肉眼看串口助手很难判断一天慢几秒更好的办法是让 PC 端脚本记录每一帧的到达时间和板子自报的时间做差值自动算出漂移率。先在例程的串口打印处改成固定机器可读格式printf(RTC_TIMESTAMP,%lu,%04d-%02d-%02d %02d:%02d:%02d,%lu\r\n, (unsigned long)rtc_epoch_sec, sDate.Year 2000, sDate.Month, sDate.Date, sTime.Hours, sTime.Minutes, sTime.Seconds, uptime_sec);然后 PC 端用 pyserial 采集import serial import time import re ser serial.Serial(COM22, 115200, timeout0.5) pattern re.compile(rbRTC_TIMESTAMP,(\d),(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}),(\d)) samples 0 drift_sum 0.0 while samples 1800: # 采集约30分钟 line ser.readline() m pattern.search(line) if not m: continue board_sec int(m.group(1)) # 板子上报的 UTC 秒 host_sec time.time() # PC 收到帧时的 UTC 秒 offset host_sec - board_sec # 正数表示板子走慢了 drift offset * 1e6 / (int(m.group(3)) or 1) drift_sum drift samples 1 print(foffset{offset:.3f}s drift{drift:.1f}ppm) print(faverage_drift{drift_sum / samples:.1f} ppm)脚本逻辑说明board_sec是 RTC 从校时时刻起累计的 UTC 秒host_sec是 PC 收到串口帧瞬间的时间戳两者相减得到累计偏移。第三次字段uptime_sec是板子运行的秒数用于把偏移换算成 ppm。启动测试前先让板子和 PC 对齐一次 UTC 基准过程是把电脑的当前时间写到 RTC 或通过网络校时一次然后开始统计。串口波特率 115200 的传输延迟在毫秒级对秒级漂移统计可以在几分钟内显示出趋势但完整判断日误差还是至少测 6 到 12 小时。5.2 三个降低 RTC 走时误差的工程化调整第一个调整是优先选优质 32.768kHz 晶振而不是依赖软件校准。工业级 ±20ppm 的无源晶振价格比消费级只贵两三毛钱日误差可以从 ±5 秒压到 ±1.7 秒以内。PCB 布局上晶振靠近 MCU避开大电流走线两侧电容接地尽量短线。第二个调整是用 RTC 校准寄存器。STM32F4 以上的系列带有RTC_CALR校准寄存器通过CALP和CALM字段对 RTC 输出频率做数字微调补偿范围约 -487ppm 到 488ppm分辨率约为 0.95ppm。实测一段时间后把漂移量换算成寄存器值写回走时精度可以提升一个数量级。具体计算公式以对应芯片参考手册的 RTC 校准章节为准不要跨系列套用。F1 系列没有这个寄存器只能用合适的负载电容完成微调。第三个调整是区分“走时精度”和“唤醒精度”。要求时间基准准的用 LSE只要求几秒内醒来的用 LSI 反而更省电。例程默认 LSE如果产品对定时唤醒的时间点不敏感可以把时钟源切到 LSI实测功耗更低系统复杂度也更小。对于量产产品最后一道工序通常是在 25℃ 恒温环境下用频率计测 MCO 引脚输出的 1Hz 信号频率把校准值写进 Flash出厂时每台设备单独补偿温度和晶振离散带来的偏差。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价