MicroPython pyb.RTC 实时时钟深入指南:日期时间读写、唤醒定时器与平滑校准
【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址: https://gitcode.com/gh_mirrors/mi/micropython
pyb.RTC是 MicroPython 在 STM32 系列开发板(pyboard)上提供的实时时钟(Real Time Clock)接口,它由硬件 RTC 外设驱动,与主系统时钟相互独立,能够在主 CPU 停机甚至整板待机(standby)期间持续记录日期与时间。本文以 docs/library/pyb.RTC.rst 为骨架,结合 ports/stm32/rtc.c 的实现细节,系统讲解 RTC 的构造、datetime()日期时间读写、wakeup()周期唤醒、info()启动信息以及calibration()频率校准,并给出可在 pyboard 上直接运行的可验证示例。读完本文,你将能够用几行 Python 代码完成时间戳管理、低功耗定时唤醒和时钟漂移校正。
RTC 是什么
RTC 是一个独立于主 CPU 的时钟外设,专门负责跟踪日期与时间。在 STM32 平台上,RTC 通常由以下两种时钟源之一驱动:
- LSE(外部低速晶振,典型 32768 Hz):精度高,断电后由 VBAT 备份电池维持走时,适合长期计时;
- LSI(内部低速 RC 振荡器,标称 32000 Hz):无需外部晶振,但精度略低。
从源码结构看,ports/stm32/rtc.c 中通过RTC_ASYNCH_PREDIV/RTC_SYNCH_PREDIV(及其_LSE、_LSI变体)配置分频系数,把时钟源分频到 1 Hz 的秒脉冲(ck_spre),这些值可以在各开发板的mpconfigport.h中按需调整。RTC 一旦运行便持续计数,即使进入pyb.stop()(Stop 模式)或pyb.standby()(Standby 模式)也不会停止。
快速上手
文档给出的最小示例非常简洁:
import pyb rtc = pyb.RTC() rtc.datetime((2014, 5, 1, 4, 13, 0, 0, 0)) # 设置时间 print(rtc.datetime()) # 读取时间pyb.RTC()构造器不接收任何参数,它返回一个代表板载 RTC 外设的单例对象(源码中由pyb_rtc_obj常量对象实现,见 ports/stm32/rtc.c)。同一时刻多次调用pyb.RTC()得到的都是同一个底层硬件。
datetime():日期与时间的读写
RTC.datetime([datetimetuple])是 RTC 最核心的接口,兼具读取与设置两种用途:
- 无参数调用:返回当前日期与时间的 8 元组;
- 传入 1 个 8 元组:设置日期与时间,同时将
subseconds重置为 255。
8 元组的完整格式为:
(year, month, day, weekday, hours, minutes, seconds, subseconds)字段说明:
| 字段 | 取值范围 | 说明 |
|---|---|---|
year | 2000–2099 | 完整年份 |
month | 1–12 | 月份 |
day | 1–31 | 日 |
weekday | 1–7 | 1 为星期一,7 为星期日 |
hours | 0–23 | 24 小时制 |
minutes | 0–59 | 分 |
seconds | 0–59 | 秒 |
subseconds | 255→0 倒数 | 亚秒计数,设置时间后被重置为 255 |
关于weekday需要特别留意:它遵循1=Monday, 7=Sunday的约定,与 Python 标准库datetime.weekday()(0=Monday)不同,跨库换算时要小心偏移。
读写实现原理
在 ports/stm32/rtc.c 的pyb_rtc_datetime中可以看到:
- 读取时先调用
HAL_RTC_GetTime再调用HAL_RTC_GetDate,源码注释明确说明必须按此顺序访问寄存器才能正确读取影子寄存器中的一致值; - 年份在内部以"相对于 2000 的偏移量"存储(
date.Year = mp_obj_get_int(items[0]) - 2000),读出时再加回 2000,因此有效年份范围为 2000–2099; subseconds来自 RTC 的同步分频计数寄存器,读出后经rtc_subsec_to_us换算;而设置时间时该值被硬件复位逻辑重置。
实用的读写模式
from pyb import RTC rtc = RTC() # 设置时间:2024 年 6 月 15 日,星期六,10:30:45 rtc.datetime((2024, 6, 15, 6, 10, 30, 45, 0)) # 读取并解包 year, month, day, weekday, hh, mm, ss, subsec = rtc.datetime() print(f"{year}-{month:02d}-{day:02d} {hh:02d}:{mm:02d}:{ss:02d}") # 只需 1 秒精度的格式化时间 now = rtc.datetime()[:7]在 tests/ports/stm32/rtc.py 中,MicroPython 官方测试覆盖了从(2000,1,1)到(2099,12,31)的多种日期边界(月末、年末、闰年等),并验证了设置后 1.05 秒再读取、前 7 个字段准确递增 1 秒的行为,说明datetime()具备完整的日历计算能力。
wakeup():周期唤醒定时器
RTC.wakeup(timeout, callback=None)用于设置一个周期性触发的唤醒定时器,每经过timeout毫秒触发一次。该触发既可以唤醒pyb.stop()暂停的 CPU,也可以唤醒pyb.standby()待机的整板——这是构建低功耗定时采样系统的关键能力。
调用规则:
timeout=None:禁用唤醒定时器;- 提供
callback时,每次触发都会执行该回调,回调必须恰好接收 1 个参数(MicroPython 会传入内部唤醒源编号)。
from pyb import RTC, LED rtc = RTC() def tick(n): LED(1).toggle() # 每次唤醒翻转一次 LED,示意"被唤醒了" # 每 2 秒唤醒一次(配合 pyb.stop() 使用可大幅省电) rtc.wakeup(2000, tick) while True: pyb.stop() # 进入 Stop 模式,等待 RTC 唤醒毫秒到硬件寄存器的换算
唤醒超时最终要落到 STM32 RTC 的 WUTR(Wakeup Timer Register)计数器和 WUCKSEL 时钟选择位。在 ports/stm32/rtc.c 的pyb_rtc_wakeup中,毫秒值被自动换算:
- 小超时:使用 RTC 时钟分频(32768 Hz 的 1/16、1/8、1/4、1/2),
wucksel依次选择更粗的分频,得到接近毫秒级的触发间隔; - 大超时:切换到 1 Hz 秒时钟,
wut = ms / 1000; - 若
wut超过 16 位寄存器上限(0x10000),会尝试用wucksel=6的偏移技巧扩容; - 仍超出范围则抛出
ValueError(源码中的"wakeup value too large",对应测试tests/ports/stm32/rtc.py中set_and_print_wakeup(0x20001 * 1000) # exception一行)。
测试文件同时用stm.mem32[stm.RTC + stm.RTC_CR]直读寄存器,验证了 0/1/4000/8000/16000/32000 ms 等边界值下 WUCKSEL 与 WUT 的组合,说明换算逻辑覆盖了从毫秒级到数万秒的宽范围。
与 machine 模块的联动
wakeup()并不只属于pyb模块。在 ports/stm32/modmachine.c 中,machine.lightsleep(ms)与machine.deepsleep(ms)在收到毫秒参数时都会内部调用pyb_rtc_wakeup来配置 RTC 唤醒,随后分别进入 Stop 模式与 Standby 模式;而 ports/stm32/modpyb.c 将pyb.stop映射到machine_lightsleep、pyb.standby映射到machine_deepsleep。因此以下写法在 STM32 上等价:
# 方式一:pyb 风格 pyb.RTC().wakeup(5000) pyb.stop() # 5 秒后被唤醒 # 方式二:machine 风格 import machine machine.lightsleep(5000) # 内部自动配置 RTC 唤醒info():启动耗时与复位来源
RTC.info()返回一个整数,用于诊断 RTC 的启动过程与系统复位来源,其位定义如下:
| 位域 | 含义 |
|---|---|
低 16 位(0xffff) | RTC 启动耗时,单位毫秒 |
0x10000 | 置位表示发生过上电复位(power-on reset) |
0x20000 | 置位表示发生过外部复位(external reset) |
from pyb import RTC info = RTC().info() startup_ms = info & 0xffff # 启动花了多少毫秒 was_power_on_reset = bool(info & 0x10000) was_external_reset = bool(info & 0x20000) print("startup:", startup_ms, "ms")这些信息来自 ports/stm32/rtc.c 中直接返回的内部变量rtc_info。该变量在 RTC 初始化阶段被逐步填充:rtc_init_finalise中通过__HAL_RCC_GET_FLAG(RCC_FLAG_PORRST)与RCC_FLAG_PINRST检查复位标志并置位对应比特(ports/stm32/rtc.c),同时记录HAL_GetTick() - rtc_startup_tick作为启动毫秒数。此外源码中还包含 LSE 启动失败回退 LSI、LSEBYP 回退等内部位(如0x01000000、0x02000000、0x100000、0x20000000等),可用于更深入的硬件诊断。
calibration():平滑校准时钟精度
晶振频率存在温漂与个体差异,长时间运行后 RTC 会累积可观的走时误差。RTC.calibration(cal)提供平滑校准(Smooth Calibration)机制:
- 无参数:返回当前校准值,范围[-511, 512]的整数;
- 带 1 个参数:写入校准值,超出范围抛出
ValueError。
校准的物理含义(源自文档,可由 ports/stm32/rtc.c 的HAL_RTCEx_SetSmoothCalib调用印证):STM32 在32 秒(2^20 个 32768 Hz 时钟节拍)周期内,按校准值增删时钟节拍。每增加 1 个节拍,时钟加快约 1/2^20,即0.954 ppm;负值则减慢。因此整个可用校准范围约为:
- 最快:
512 × 0.954 ≈ +488.5 ppm - 最慢:
-511 × 0.954 ≈ -487.5 ppm
1 ppm 相当于每天约 0.0864 秒,因此 ±488 ppm 大约能补偿每天 ±42 秒级别的漂移,足以应对绝大多数晶振误差。
from pyb import RTC rtc = RTC() print(rtc.calibration()) # 读取当前校准值,例如 0 # 假设实测时钟每天慢 3 秒,则需要正向补偿: # 3 秒/天 ≈ 34.7 ppm ≈ 36.4 个节拍 rtc.calibration(36) # 反向(时钟偏快)则用负值 rtc.calibration(-20)几点使用注意:
- 校准值写入后立即生效,且写入新值会覆盖旧值;
- tests/ports/stm32/rtc.py 依次测试了 512、511、345、1、0、-1、-123、-510、-511 等边界值,并先保存再恢复原校准值,验证了全量程的读写往返一致性;
- 部分开发板若定义了
MICROPY_HW_RTC_USE_CALOUT,源码还支持用0x0ffe/0x0fff特殊值开关 PC13 上的 512 Hz 校准方波输出(用于配合外部频率计测量),普通用户无需关注。
综合实战:低功耗定时记录器
将上述接口组合起来,即可实现一个典型的低功耗应用——每 10 秒唤醒一次,记录当前时间戳:
from pyb import RTC, LED import pyb rtc = RTC() log = [] def on_wakeup(n): t = rtc.datetime() log.append(t[:7]) # (y, m, d, wd, h, m, s) LED(1).toggle() rtc.datetime((2024, 1, 1, 1, 0, 0, 0, 0)) # 初始化基准时间 rtc.wakeup(10000, on_wakeup) print("entering stop mode; will wake every 10s") while len(log) < 3: pyb.stop() # Stop 模式下 RTC 继续走时 print(log) rtc.wakeup(None) # 停止唤醒要点回顾:
wakeup(None)关闭定时器,避免进入 Stop 后反复被唤醒;pyb.stop()期间 RTC 保持计时,唤醒后datetime()读数连续;- 若需跨掉电保持时间,请确保开发板使用 LSE + VBAT 备份电源配置(见各板
mpconfigport.h中的MICROPY_HW_RTC_USE_LSE)。
小结与延伸阅读
pyb.RTC在四个方法内覆盖了实时时钟的全部核心需求:datetime()负责日历时间的读写,wakeup()将低功耗与定时唤醒结合,info()提供启动与复位诊断,calibration()则以 ppm 级精度校正长期漂移。由于 STM32 的machine模块复用同一实现,pyb与machine两种风格可以无缝混用。
如需进一步深入,建议阅读:
- 模块文档:docs/library/pyb.RTC.rst
- STM32 端口实现:ports/stm32/rtc.c、ports/stm32/rtc.h
- 官方回归测试:tests/ports/stm32/rtc.py(含边界日期、校准全量程、唤醒寄存器换算)
- 通用
machine.RTC行为测试:tests/extmod/machine_rtc.py - 睡眠模式入口:ports/stm32/modmachine.c 与
pyb.stop/pyb.standby映射 ports/stm32/modpyb.c - 其他端口的 RTC 用法可参考 tests/ports/renesas-ra/rtc_init.py、tests/ports/cc3200/rtc.py
【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址: https://gitcode.com/gh_mirrors/mi/micropython
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考