树莓派 Pico 搭配 MicroPython 做开发,是我这几年来玩嵌入式最顺手的一套组合。不过有个问题几乎每个项目都会撞上——时间。传感器采回来的数据没有时间戳,日志不知道哪条在前哪条在后,定时任务更是无从谈起。单靠 RP2040 内部的 RTC,断电就丢时间,加上 Pico 板上连个 32.768kHz 的晶振都没焊,运行时的走时精度也谈不上好。于是我把目光转向网络——用 Pico W 连上 WiFi,通过 NTP 协议从公共时间服务器把标准时间拉下来,再写入芯片内部的 RTC,彻底解决嵌入式设备的时间焦虑。
这篇文章会从方案选型、硬件准备、核心代码到常见坑点,把我从零实现“RTC 控制 + NTP 时间同步”的完整过程讲清楚。不管你是想给数据采集仪加时间戳,还是想做一个日出日落控制开关,这篇内容都能直接落地。文中涉及 RP2040 内部 RTC 的读写、Pico W 的网络连接、NTP 服务器选择、时区偏移处理,以及外接 DS3231 高精度 RTC 模块的进阶玩法,适合正在用 MicroPython 做物联网设备的开发者和硬件爱好者抄作业。
1. 为什么设备必须解决“时间”问题
1.1 没有可靠时间戳的项目有多难熬
我在早期一个温湿度采集项目里,直接把传感器数据打在串口上,不记时间。一开始觉得省事,反正数据是连续发的,回头看一眼顺序就行。可数据量一多、中间断过几次电、又加了几路传感器之后,整个日志就乱成了一锅粥,哪条数据是几点采的完全对不上。后来不得已把主机时间带进来写文件,但主机断电后时间照样错。这让我明白一个道理:嵌入式设备一旦脱离了时间戳,采集、日志、上报都等于半瘫。
时间同步这件事表面上是个“有没有时间可用”的问题,实际牵扯到三件事:一是设备上电后 RTC 能不能给出正确时间;二是断电重启后时间会不会丢掉;三是长时间运行之后时间漂移能不能接受。很多刚入门的开发者只盯着第一点,以为rtc.datetime()能读能写就够了,结果项目上线后几天,时间就偏了几十秒,或者一断电全乱套。这些问题如果在一开始就规划好,后面几乎不用返工。
1.2 RP2040 内部 RTC 的硬伤:断电即失忆
树莓派 Pico 用的这颗 RP2040 芯片,内部确实有一个 RTC 模块,MicroPython 的machine.RTC可以直接访问。但是——它跟我那套“单片机应该有纽扣电池保住时间”的惯用认知差距很大。RP2040 没有独立的 RTC 电源域,板上也没有焊 32.768kHz 低速晶振,RTC 的时钟源只能依赖系统主时钟分频。这就带来两个实际后果:掉电之后时间立刻归零;运行时走时精度受系统时钟影响,不能跟专业 RTC 芯片比。
我一开始没仔细看数据手册,直接用machine.RTC设置时间,然后拔掉电源再上电,发现时间直接回到 2021 年,瞬间傻眼。后来查资料才确认,RP2040 的 RTC 就是一个“运行期日历”,不是一个带后备电池的实时时钟。理解了这一点,你才会明白为什么 NTP 同步和内外部 RTC 协同的方案,几乎成了 Pico 项目时间处理的必答题,而不是可选题。
2. 方案选型:内部 RTC + NTP,还是外接 RTC 模块
2.1 三种主流方案横向对比
在动手写代码之前,我花了不少时间权衡方案。市面常见的做法大致有三类:
| 方案 | 时间保持能力 | 精度表现 | 成本与复杂度 | 适用场景 |
|---|---|---|---|---|
| 只用 RP2040 内部 RTC | 断电即丢 | 受系统时钟影响,有漂移 | 最低,零硬件成本 | 临时测试、始终在线且每次联网的设备 |
| 内部 RTC + NTP 定期同步 | 断电后需重新同步 | 取决于校准间隔和 RTC 漂移 | 需网络能力,软件实现简单 | Pico W 联网设备,大多数 IoT 项目 |
| 外接 DS3231 等外部 RTC | 电池供电,断电续走 | 内部温补晶振,年误差约 1 分钟 | 模块约 10 元左右,接线 4 根 | 离线设备、对断电续走有要求的项目 |
从表格能看出来,内部 RTC 最大的问题不是精度,而是“断电即失忆”。如果你的设备长期通电且每次开机都能联网,那“内部 RTC + NTP”是最省钱的方案。如果设备需要频繁断电、离线运行,或者对时间连续性要求较高,DS3231 这类带电池的外部 RTC 基本是必选项。
这里多说一句,为什么选择 DS3231 而不是更便宜的 DS1302。DS1302 模块当年流行,但走时需要外挂晶振,精度受温度和晶振频率偏差影响很明显,一个星期的误差可能就有几分钟。DS3231 内置了温度补偿晶振(TCXO),把精度控制在 ±2ppm 左右,换算下来一年也就偏差一分钟上下。对于大多数 IoT 应用来说,这个精度已经非常够了,差价的几块钱完全值得。
2.2 我为什么最终选了“内部 RTC + NTP + 定期校准”
我的主力项目是一个番茄大棚环境监测节点,用 Pico W 每 10 秒采一次数据,通过 WiFi 上报到本地服务器。这个设备常年通电,断电也不敢太久,但它不可能每次断电后都靠手工设置时间。仔细一看,这正好是“内部 RTC + NTP”方案的典型场景。
我的落地策略分三层:设备上电后,先尝试 NTP 同步,成功后把 UTC 时间加 8 小时写入内部 RTC;如果启动时网络不可用,就先用 RTC 里的旧时间顶着,等网络恢复后自动补一次校准;正常运行期间每 30 分钟自动校准一次,把内部 RTC 的漂移问题控制在可接受范围。这套组合没有增加任何硬件成本,代码量也不大,却解决了时间戳可靠性的大问题。
如果你做的是完全离线的项目,比如一个手持的记录仪,那我会直接把方案换成 DS3231,毕竟电池保时间这条路,RP2040 内部 RTC 天生就不支持。
3. 环境准备:硬件选择与 MicroPython 固件烧录
3.1 Pico 还是 Pico W:网络能力是关键分水岭
NTP 时间同步的前提是设备能联网。原版树莓派 Pico 用的是 RP2040 芯片,板上没有 WiFi 模块,MicroPython 固件里连network模块都找不到,根本走不了 NTP。Pico W 则在板载了 CYW43439 无线芯片,可以像 ESP32 那样直接连接 WiFi 网络,network和socket模块齐全,是跑 NTP 的最低门槛。
所以这一节你必须先想清楚自己手上是哪种板子。如果手里只有原版 Pico 又想用 NTP,两条路可选:换一块 Pico W,或者给 Pico 外接一个 ESP-01S、ESP8266 之类的串口 WiFi 模块,通过 AT 指令间接联网。我建议新手直接上 Pico W,现在价格也就三四十块,省去串口调试和协议对接的麻烦,代码一次写通。
另外提一句,树莓派官方还发布了 Pico 2、Pico 2 W,芯片升级到了 RP2350,但 MicroPython 层面的 RTC 接口和 WiFi 用法保持一致,下面的代码在这些新板子上同样适用,不需要单独改动。
3.2 烧录 MicroPython 固件详细步骤
给 Pico W 烧录固件不需要专门的烧录器,整个过程就是按住 BOOTSEL 键拖一个文件。具体操作是这样:
- 先到树莓派官方下载页面找对应 Pico W 的 MicroPython 固件,文件名一般是
RPI_PICO_W-202xxxxx-unstable-v1.xx.x.uf2这种格式,别下成不带W的版本。 - 用 USB 线连接 Pico W 和电脑,连接之前按住板子上的 BOOTSEL 按钮不放,插入后再松开。
- 电脑会出现一个名为
RPI-RP2的 U 盘,直接把.uf2文件拖进去。复制完成后,板子会自动重启进入 MicroPython 环境。 - 打开 Thonny 或 mu 编辑器,右下角选择“MicroPython (Raspberry Pi Pico)”解释器,在 Shell 里输入
print('hello'),能回显就说明固件烧录成功。
烧录过程我踩过一次坑:一开始下载了不带W的原版 Pico 固件,烧完以后import network直接报错,折腾半天才反应过来是固件选错了。所以下载时一定看好设备型号,这一步错了后面全白搭。
4. 核心代码实现:从 NTP 同步到 RTC 读写
4.1 先让 Pico W 联网:STA 模式连接 WiFi
NTP 的活要干,网络得先通。Pico W 的 MicroPython 里,连接 WiFi 的代码跟 ESP32 很相似,用network模块把网卡设为 STA 模式,再调connect()连路由器。我一般会写一个带超时重试的连接函数,避免某次网络闪断导致程序直接卡死。
import network import time SSID = "你的WiFi名称" PASSWORD = "你的WiFi密码" def connect_wifi(): wlan = network.WLAN(network.STA_IF) wlan.active(True) if not wlan.isconnected(): wlan.connect(SSID, PASSWORD) for _ in range(30): if wlan.isconnected(): break time.sleep(1) if not wlan.isconnected(): raise RuntimeError("WiFi连接失败,请检查SSID和密码") print("网络已连接,IP地址:", wlan.ifconfig()[0]) return wlan连接超时我设了 30 秒,家里路由器响应慢的话也基本够了。还有一个细节:重复调用wlan.active(True)不会把已经连上的连接断开,所以这个函数可以在重试逻辑里反复执行。如果连接失败,建议先看打印出的 IP 是否为0.0.0.0,是的话多半是密码错误或路由器拒绝接入,而不是代码本身的问题。
4.2 NTP 同步:用 ntptime 模块一步到位
MicroPython 官方在固件里标配了ntptime模块,它把 SNTP 客户端的逻辑封装好了,默认请求pool.ntp.org,你只需要调一下ntptime.settime()就能把网络时间同步进 RTC。
NTP 的底层原理其实不复杂:客户端向服务器 123 端口发送一个 UDP 数据包,服务器收到后把携带时间戳的响应包发回来,客户端解出其中的时间字段,再根据 1900 到 1970 之间的偏移量转换成 Unix 时间戳。ntptime模块内部就用了一段精简的逻辑来做这件事,最后通过 RTC 接口把时间写到系统里。懂了这个原理,你就能理解为什么 NTP 同步依赖网络,以及为什么同步出的时间是 UTC 而不是本地时间。
ntptime模块允许修改服务器地址,我强烈建议把它改成国内访问稳定的 NTP 服务器。默认的pool.ntp.org在海外的解析结果不稳定,偶尔会出现超时。
import ntptime # 将NTP服务器改为国内公共NTP ntptime.host = "ntp.aliyun.com"我实测下来,阿里云的ntp.aliyun.com和腾讯云的ntp.tencent.com都很稳。如果你所在机构有内网 NTP 服务器,改这里也能走内网同步,响应速度会更快。
4.3 时区转换:同步到的是 UTC,不是北京时间
这是最坑的一步。ntptime.settime()同步完以后,设备时区仍然是 UTC 零时区。如果你在串口直接打印localtime(),会看到比北京时间慢了 8 个小时。很多初学者在这个地方反复排查,以为 NTP 同步失败了,其实时间已经同步成功了,只是没做时区偏移。
MicroPython 的time.timezone变量在不同移植上行为不统一,为了避免踩雷,我从不依赖它,而是自己在同步完成后加一个固定偏移量再写回 RTC。中国标准时间是 UTC+8,所以加8 * 3600秒。
import machine import time UTC_OFFSET = 8 * 3600 # 东八区 def sync_time_and_set_rtc(): ntptime.settime() rtc = machine.RTC() t = time.time() + UTC_OFFSET tm = time.localtime(t) # tm 结构: (年, 月, 日, 时, 分, 秒, 星期几, 年内第几天) rtc.datetime((tm[0], tm[1], tm[2], tm[6], tm[3], tm[4], tm[5], 0))这里要注意time.localtime()返回的第七个元素tm_wday范围是 0 到 6,0 表示周一。而 MicroPython 的rtc.datetime()在写入时同样要求 weekday 为 0 到 6,两者正好对应,直接塞进去就行。如果你交换了顺序或者传了 1 到 7 的值,读出来的星期就会错位。
4.4 手动读写 RP2040 内部 RTC:掌握底层接口
NTP 自动同步之外,手动读写 RTC 也是基本功。MicroPython 统一用machine.RTC操作,这个类不受 “RTC” 英文字面含义的局限,实际上就是一个可以读写的系统日历。
设置时间用rtc.datetime(tuple),元组长度 8,顺序是(年, 月, 日, 星期, 时, 分, 秒, 子秒)。子秒参数在大部分场景直接填 0 即可。读取时间用rtc.datetime(),返回同样结构的元组:
from machine import RTC rtc = RTC() # 设置时间:2024年6月1日 星期六 12:30:00 rtc.datetime((2024, 6, 1, 5, 12, 30, 0, 0)) # 读取当前时间 now = rtc.datetime() print("当前时间: {}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format( now[0], now[1], now[2], now[4], now[5], now[6]))关于 weekday 的索引,不同平台的 MicroPython 文档写法略有出入,有的写成 Monday=0,有的写成 Monday=1。我始终以time.localtime()的tm_wday为准,毕竟它和 NTP 时区转换逻辑能完全对上。如果你设置后打印出来的星期数不直观,就打印tm_wday对应的中文名称来验证。
还有一点要牢记:RP2040 内部 RTC 没有后备电池,所有通过machine.RTC设置的时间在断电后会丢失。代码逻辑里不要假设“我上电设过一次,下次上电还能读出来”,一定要在启动流程里重新同步或恢复。
4.5 完整示例代码:开机自动同步并定时校准
把上面的模块拼起来,就是一个完整的开机自动校时程序。我实际项目里用的是这个精简版本:
import network import time import machine import ntptime SSID = "你的WiFi名称" PASSWORD = "你的WiFi密码" NTP_HOST = "ntp.aliyun.com" UTC_OFFSET = 8 * 3600 SYNC_INTERVAL = 1800 # 每30分钟同步一次 def connect_wifi(): wlan = network.WLAN(network.STA_IF) wlan.active(True) if not wlan.isconnected(): wlan.connect(SSID, PASSWORD) for _ in range(30): if wlan.isconnected(): break time.sleep(1) if not wlan.isconnected(): print("WiFi连接失败") return None return wlan def sync_ntp(): try: ntptime.host = NTP_HOST ntptime.settime() rtc = machine.RTC() t = time.time() + UTC_OFFSET tm = time.localtime(t) rtc.datetime((tm[0], tm[1], tm[2], tm[6], tm[3], tm[4], tm[5], 0)) print("时间同步完成: {}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format( tm[0], tm[1], tm[2], tm[3], tm[4], tm[5])) return True except Exception as e: print("NTP同步失败:", e) return False def main(): rtc = machine.RTC() wlan = connect_wifi() if wlan: sync_ntp() last_sync = time.time() while True: # 你的主业务逻辑写在这里,比如读取传感器、上报数据 now = rtc.datetime() print("当前时间: {}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format( now[0], now[1], now[2], now[4], now[5], now[6])) # 定期校准 if time.time() - last_sync >= SYNC_INTERVAL: if wlan and wlan.isconnected(): sync_ntp() last_sync = time.time() time.sleep(1) main()这个程序里启动后立刻同步一次,进入主循环后每 30 分钟检查一次是否需要校准。如果启动时网络不好,它也不会阻止主逻辑运行,只是暂时用上一次保存在 RTC 里的旧时间顶着。这是一个很实用的小技巧:让网络同步成为后台任务,而不是启动的前置阻塞条件。
关于SYNC_INTERVAL的取值,我建议根据项目对时间精度的要求来定。普通的环境监测 30 分钟到 1 小时校准一次绰绰有余;如果只是打个日志时间戳,一天校准一次都行。校准太频繁会给 NTP 服务器造成不必要的压力,也不见得能把精度提升多少,因为同步误差本身就受网络延迟波动影响。
5. 进阶方案:外接 DS3231 高精度 RTC 模块
5.1 硬件接线与模块说明
如果你的项目是离线的,或者经常断电,那就得考虑外接带电池的 RTC 芯片。我用的是 DS3231 模块,它除了芯片本身,板上还带一个 CR2032 纽扣电池座,断电后由电池维持走时。模块通过 I2C 总线通讯,地址固定为0x68。
接线非常简单,用 Pico 的默认 I2C0 接口:
| DS3231 引脚 | 接到 Pico |
|---|---|
| VCC | 3V3(OUT) 引脚 |
| GND | GND |
| SDA | GP0 |
| SCL | GP1 |
接好以后,用machine.I2C(0, scl=Pin(1), sda=Pin(0))初始化总线,就能开始读取模块里的时间了。DS3231 模块上的 SQW 引脚和 32K 引脚一般用不到,如果你没做闹钟或没接外部时钟源,这两根线可以悬空不接。
我第一次接的时候把 SDA 和 SCL 接反了,导致 I2C 扫描不到设备,折腾了半小时。后来养成一个习惯:每次接线后用i2c.scan()确认设备地址,看到[104](也就是十进制 104,十六进制 0x68)再往下走。
5.2 纯 Python 驱动 DS3231:读写时间
MicroPython 没有内置 DS3231 驱动,我直接照芯片手册写了一个简化版,核心就是把寄存器里的 BCD 编码转换成十进制,以及反向操作。
DS3231 内部从地址0x00到0x06依次存放秒、分、时、星期、日、月、年。这些字节都是 BCD 编码,比如十进制 42 秒存的是0x42,而不是普通的0x2A。所以读写都要做编码转换。
from machine import Pin, I2C DS3231_ADDR = 0x68 class DS3231: def __init__(self, i2c): self.i2c = i2c self.addr = DS3231_ADDR def _bcd2dec(self, v): return (v >> 4) * 10 + (v & 0x0F) def _dec2bcd(self, v): return ((v // 10) << 4) | (v % 10) def read_time(self): data = self.i2c.readfrom_mem(self.addr, 0x00, 7) sec = self._bcd2dec(data[0] & 0x7F) minute = self._bcd2dec(data[1]) hour = self._bcd2dec(data[2] & 0x3F) weekday = self._bcd2dec(data[3]) day = self._bcd2dec(data[4]) month = self._bcd2dec(data[5] & 0x1F) year = self._bcd2dec(data[6]) + 2000 return (year, month, day, weekday, hour, minute, sec, 0) def write_time(self, dt): year, month, day, weekday, hour, minute, second = dt[0], dt[1], dt[2], dt[3], dt[4], dt[5], dt[6] buf = bytearray([ self._dec2bcd(second), self._dec2bcd(minute), self._dec2bcd(hour), self._dec2bcd(weekday), self._dec2bcd(day), self._dec2bcd(month), self._dec2bcd(year % 100) ]) self.i2c.writeto_mem(self.addr, 0x00, buf) i2c = I2C(0, scl=Pin(1), sda=Pin(0)) ds = DS3231(i2c) # 写入当前时间 now = (2024, 6, 1, 5, 12, 30, 0, 0) ds.write_time(now) # 读取时间 current = ds.read_time() print(current)写入时需要注意data[0]的秒寄存器最高位是时钟停止位,读取时我做了& 0x7F处理,避免把停止位当作秒数。如果你读到秒数永远在 60 以上,就是少了这一步。
5.3 内部 RTC 与外部 RTC 的协同策略
外接了 DS3231 之后,一个最佳实践是:每次上电先把 DS3231 的时间读取出来,写入 RP2040 内部 RTC,这样你的业务代码里始终用machine.RTC就行,不用到处传 DS3231 对象。等到能联网时,再用 NTP 校一次外部模块,把两者统一到标准时间上。
这套“双 RTC”策略的好处是分层清晰:外部 RTC 负责断电续走,充当“时间保险箱”;内部 RTC 负责给业务代码提供快速访问;NTP 负责定期纠偏。就算设备离线几个月,只要纽扣电池还有电,DS3231 就能维持走时,不需要人工干预。
我目前的实际组合是:DS3231 作为主时间源,NTP 作为校准源。每天凌晨 3 点连一次 WiFi,同步成功后把新时间写入 DS3231,同时刷内部 RTC。这样既避免了频繁联网,又保证了离线期间的时间稳定,两全其美。
6. 常见问题与排查技巧实录
6.1 问题速查表
实战中我遇到并解决了下面这些典型问题,先给你一张速查表,遇到类似情况可以直接按表排查:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
import ntptime报错 | 固件不是网络版/选错设备固件 | 换成 Pico W 的 MicroPython 固件重烧 |
ntptime.settime()超时 | NTP 服务器不可达或网络未连接 | 先确认wlan.isconnected(),再换国内 NTP 服务器 |
| 同步后时间慢 8 小时 | 未做时区偏移 | 在 RTC 写入前加time.time() + 8 * 3600 |
| 断电重启后时间重置 | RP2040 内部 RTC 无后备电池 | 外接 DS3231 或每次上电重新 NTP 同步 |
| 星期显示不对 | weekday 索引不一致 | 用time.localtime()的tm_wday作为唯一来源 |
| 时间越走越偏 | 内部 RTC 缺少 32.768kHz 晶振 | 缩短 NTP 校准间隔,或换 DS3231 |
i2c.scan()返回空 | 接线错误或模块供电不足 | 检查 SDA/SCL 是否接反,确认 VCC 接到 3V3 |
这七条几乎覆盖了新手阶段的全部高频问题。尤其是“时间慢 8 小时”和“断电重置”,我每次换板子、换模块都会碰到,现在已经形成条件反射了,遇到时间不对第一个看时区,遇到重启乱时间第一个想到没电保。
6.2 独家避坑经验分享
第一,NTP 服务器尽量不要用默认值。pool.ntp.org是好,但解析结果在国外,有时延迟高或者被运营商 QoS,偶尔会超时。我改用了ntp.ntsc.ac.cn(国家授时中心)和ntp.aliyun.com之后,成功率明显提高。如果你在内网防火墙后面,建议先让 IT 放行 UDP 123 端口,并提供一个内网 NTP 地址,这样在工业现场最稳。
第二,MicroPython 的time.timezone不要依赖。不同板子对这个变量的处理不一致,在 ESP32 上改它有用,在 Pico W 上改了可能没用,甚至引发奇怪行为。我统一用“手动加偏移量再localtime()”的方式,跨板移植时零差异。
第三,校准间隔别设太短。NTP 同步本身有网络波动,如果每分钟都同步一次,时间反而可能因为网络延迟误差而跳来跳去。我建议最少 10 分钟一次,常规项目 30 分钟到 1 小时就够。如果对时间精度要求极高,就不该用内部 RTC 加网络同步这种低成本方式,直接上带温补晶振的 DS3231 更靠谱。
第四,machine.RTC和time.localtime返回的字段索引容易搞混。rtc.datetime()的顺序是年月日星期时分秒子秒,time.localtime()的顺序是年月日时分秒星期几天数,两者星期字段的位置不一样。我已经不止一次因为搞错位置把时间写坏了,现在都封装成get_now_string()这样的统一函数,避免业务代码直接跟元组索引打交道。
7. 基于可靠时间的三个实战扩展
7.1 带时间戳的环境数据记录仪
把温湿度传感器和 RTC 结合,就是常见的数据记录仪。每次采集时,把rtc.datetime()格式化成长字符串,和传感器数据一起拼成 CSV 行,写到 SD 卡或通过 MQTT 上报。这样后续做数据分析、曲线绘制、异常回溯都有据可查,不用再靠“我记得那条数据是早上采的”来猜。
我实际做过的项目里,记录格式是这样的:2024-06-01 12:30:05,25.6,68.3。一行数据里时间、温度、湿度全都有了。如果哪天发现某段数据缺失,也能根据时间戳定位到当时设备是否在线,排查效率翻倍。
7.2 日出日落定时控制开关
有了准确时间,就能做真正的天文钟,而不是简单设个“早上 6 点开灯,晚上 18 点关灯”。你可以根据经纬度计算当天的日出日落时刻,让设备控制继电器实现灯光或水泵的自动启停。时间同步的作用在这里体现得最直接:如果设备时间偏了几分钟,日出日落的控制就会跟着偏,久而久之用户就会抱怨“这灯怎么越开越不准”。
用 NTP 同步以后,设备的时间源是全球标准时间,日出日落计算只需要用本地日期和经纬度作为输入,任何时间的偏差都来自算法本身,而不再来自系统时钟漂移。这个方案还能自然适配冬夏令时地区,因为计算完全基于太阳位置,跟人为时间调整无关。
7.3 低功耗场景下的时间保持策略
Pico 在lightsleep模式下内部 RTC 的走时表现并不理想,社区里有人反馈唤醒后时间会跳变甚至停滞。所以如果做电池供电的低功耗设备,我建议别依赖内部 RTC,直接用外部 DS3231 做定时唤醒。
DS3231 的 SQW 引脚可以输出方波或闹钟中断,接在 Pico 的 GP 引脚上,触发后唤醒设备。这样 MCU 大部分时间处于睡眠状态,只有到点才醒来处理任务,时间始终由 DS3231 维护,既省电又准确。我做过一个电池版气象站,就是靠这个方案实现了 3 个月不充电稳定运行,数据日志的时间戳一点没乱。
最后再分享一个实用小技巧
如果你用的是 Pico W 且经常调试时间相关代码,可以在main.py里做一个判断:如果按住板载 BOOTSEL 键的时间超过 3 秒,就跳过 NTP 同步,直接进手动模式,方便在没网的环境下测试 RTC 读写功能。这个判断不需要额外按键,复用 BOOTSEL 的 GPIO 状态就行。我实际项目里一直保留这个小开关,省去了反复插拔网线的麻烦。时间同步这件事,做一次不难,做得可靠、能长期稳定运行,才真正考验对方案细节的把控。