ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

STM32不掉电时钟方案:DS1302+纽扣电池实战记录

STM32不掉电时钟方案:DS1302+纽扣电池实战记录 STM32 项目掉电后时间归零这件事我印象太深了。之前做一台带记录功能的小设备客户说“我明明断电重启了两天时间怎么还在 1 月 1 号”当时排查一圈发现是内部 RTC 的 VBAT 引脚没接好备用电池电压根本进不去。后来重新设计时我把方案改成了外部 DS1302 纽扣电池彻底解决了“掉电时间丢失”这个老大难顺便还把整机的静态功耗降了下来。这篇文章就把这次改造的完整过程记录下来从硬件供电拓扑到软件驱动调试再到换电池保证时钟不乱的细节一次性讲透。1. 先认清一个现实STM32 内部 RTC 并不是“万能的”很多人拿到 STM32 第一反应就是“芯片内部有 RTC直接用不就行了”。没错STM32F103/F407 这类芯片确实自带 RTC 模块甚至专门留了 VBAT 引脚来接备用电池。但实际产品里“直接用内部 RTC”往往会踩到几个不容易发现的坑。1.1 内部 RTC 的“掉电”并不等于彻底断电STM32 内部 RTC 的正常工作条件很苛刻主电源 VDD 掉电后必须由 VBAT 引脚持续供电。很多人画原理图时确实把 VBAT 接到了纽扣电池正极但忽略了 VBAT 在芯片内部会连接到 RTC 和备份寄存器。如果电路板在 VBAT 路径上串了电阻、接了二极管或者阻容滤波参数太大电池电压在低功耗模式下可能被拉低到 RTC 复位的边缘。还有一点STM32 的 VBAT 引脚如果悬空内部 RTC 在 VDD 还在时能正常计时但一旦主电源断开RTC 直接丢失全部数据。更麻烦的是很多 STM32 型号的 VBAT 不能给外部纽扣电池充电只能单向供电。如果你在 VBAT 上接的是可充电电池就需要额外设计充电电路复杂度一下就上去了。1.2 内部 RTC 时钟源的精度和稳定性STM32 内部 RTC 可以使用 LSI低速内部 RC 振荡器或 LSE外部 32.768kHz 晶振。LSI 本身是一颗 RC 振荡器精度在不同温度、不同电压下会明显变化。ST 数据手册标称 LSI 在 25 度时典型值是 40kHz 左右但实际上误差可能到百分之几换算成一天的时间偏差直接就是几十秒甚至几分钟。这对需要精确计时的设备来说是致命的。LSE 晶振方案精度会好很多但 STM32 的 LSE 起振电路对晶振的负载电容、PCB 布线、两端电容虚焊都很敏感。很多新手画板时把 32.768kHz 晶振放在 MCU 引脚很远的地方或者用了匹配电容不对的晶振导致要么不起振要么起振后频率偏差大。调试起来要动用示波器抓波形对量产来说是不小的成本。单纯从“供电独立性”和“时钟精度”两个角度看外部 DS1302 方案的优势非常明显它自带电源切换逻辑只要纽扣电池有电哪怕 MCU 整板断电DS1302 也照样走时而且它使用的是外部 32.768kHz 晶振可以通过挑选高精度晶振来控制系统误差。2. 硬件供电方案设计一颗纽扣电池是怎么做到“不掉电”的既然要用外部 RTC那供电拓扑就是整个方案的核心。DS1302 有 VCC2 和 VCC1 两个电源引脚官方手册里的切换逻辑非常简单VCC2 接主电源VCC1 接纽扣电池芯片内部会自动比较两个电源的电压优先使用电压较高的一侧。这个特性让“主电掉电后无缝切换电池供电”变成了芯片自动完成的事不需要外部 PMOS 切换电路也不用担心切换瞬间时钟数据冲突。2.1 DS1302 关键引脚与内部电源切换逻辑DS1302 是 Maxim 的一颗老牌 RTC 芯片封装有 DIP-8 和 SOIC-8引脚数量少非常适合手工焊接。它的引脚功能如下引脚名称功能说明1VCC2主电源输入典型 3.3V/5V2X132.768kHz 晶振输入3X232.768kHz 晶振输出4GND地5CE/RST片选/复位高电平有效6I/O数据输入输出三态接口7SCLK串行时钟输入下降沿采样数据8VCC1备用电池正极接纽扣电池或超级电容内部电源切换的判定条件看官方手册和实际测量结果大概是当 VCC2 电压比 VCC1 高 0.2V 以上时芯片内部由 VCC2 供电一旦 VCC2 跌落到接近或低于 VCC1就自动切换到 VCC1 供电。这个过程是纯硬件行为DS1302 的时间寄存器和 RAM 数据不会因为切换而丢失所以在 MCU 主电源断电时芯片只是换了个“供电源”继续跑。需要注意DS1302 的 VCC1 最低工作电压一般在 2.0V 左右如果纽扣电池电压掉到 2.0V 以下芯片会停止计时并可能丢失数据。所以设计时不能在 VCC1 到电池之间加降压二极管正极直接连电池即可。市面上有些模块会把 VCC1 串联一个 1N4148看似防反接但 0.7V 的压降会直接让电池可用电压缩水很多非常不推荐。2.2 主电源、备用电源和上拉电阻的“电流回路”问题很多 DS1302 模块的原理图里VCC2 和 VCC1 之间还连着一条跳线或者直接通过一个二极管相连。这样做对普通使用没问题但如果你想追求低功耗就要特别小心了。DS1302 的 I/O 和 SCLK、CE 引脚在模块上一般都有上拉电阻到 VCC2。VCC2 正常时上拉电阻正常工作但 VCC2 掉电后如果 VCC1 的电池仍然通过内部电路和外部上拉电阻形成回路电流就会从 VCC1 漏到 I/O 引脚上。更糟糕的是I/O 引脚还连着 STM32 的 GPIOGPIO 内部可能也有上下拉电阻或者处于输入模式时的高阻态。这样整条漏电通路会把纽扣电池的电白白消耗掉导致时钟模块“明明只有电池供电却几天就没电”。我第一次画板时用的成品模块就是这种漏电设计。后来实际测下来电池电量在正常使用中大概三到四个月就掉到 2.5V 以下和理论寿命差了几十倍。解决方法是使用 DS1302 时模块上只保留 SCLK、I/O、CE 到 MCU 的连接不接任何到 VCC2 的上拉电阻如果 MCU 的 GPIO 内部可以配置上下拉也建议把这三个引脚配置为复用推挽输出或浮空输入不要开内部上拉。2.3 纽扣电池选型和寿命算账纽扣电池选型最常用的肯定是 CR2032。单颗标称容量约 220mAh 到 240mAh具体看品牌和批次。DS1302 在掉电模式下也就是 VCC1 单独供电时典型电流是 300nA 左右最坏情况会到 1µA。按 220mAh、300nA 算理论寿命高达 80 多年。但实际还要考虑电池自放电CR2032 年自放电率大概在 1%~2%、PCB 漏电流、上拉电阻漏电以及温度影响。保守估计在常温环境下能撑 5 到 8 年已经远超很多产品的生命周期。如果你对体积有要求可以选 CR1220容量大概 40mAh寿命会缩短到两年左右。用在消费类小设备上结合换电池维护周期来选就够。还有一种方案是使用超级电容比如 1F 或者 3F 的DS1302 的 VCC1 可以接超级电容然后通过 VCC2 对超级电容充电。超级电容支持反复充放电适合那些需要短时间断电保持时钟但又不希望换电池的场景。3. STM32 与 DS1302 的完整接线和实物连接细节DS1302 和 STM32 的通信只需要三根线再加上电源和地总共五根线。三根线分别是 SCLK时钟、I/O数据、CE片选。由于 DS1302 的通信协议是类 SPI 但又不完全兼容所以直接拿硬件 SPI 外设去驱动反而不方便最通用的就是用 GPIO 模拟时序。3.1 我用的引脚分配STM32F103C8T6 为例当时手里的主控是 STM32F103C8T6引脚不多我选了 PC13、PC14、PC15 三个引脚来连接 DS1302STM32 引脚连接目标说明PC13DS1302 CE输出模式控制片选PC14DS1302 SCLK输出模式产生时钟脉冲PC15DS1302 I/O双向数据线需配置开漏/推挽切换3.3VDS1302 VCC2主电源GNDDS1302 GND共地电池正极DS1302 VCC1备用电源这里有个细节PC14 和 PC15 在 STM32 上默认对应 OSC32_IN 和 OSC32_OUT只有当你不需要使用 LSE 外部低速晶振时它们才能做普通 GPIO。我这次项目本来就用内部 HSI不需要 LSE所以直接复用。如果你在同一个项目里用了 LSE 晶振就别把这两个引脚拿来接 DS1302 了否则引脚冲突会导致 LSE 无法起振。PC13 这个引脚还兼顾 RTC 校准功能在不使用 RTC 校准输出时也可以当普通 GPIO 用。不过要注意PC13 最大输出电流较小驱动能力有限但控制 DS1302 的 CE 信号完全没问题。3.2 成品模块的“坑”跳线、电容和晶振负载市面上常见的 DS1302 模块通常是一块小板上面集成 DS1302、32.768kHz 晶振、电池座、几个上拉电阻和滤波电容。多数模块默认配置是兼容 Arduino 的会通过跳线把 VCC2 和 VCC1 连在一起方便没有独立电池时也能用。这种接法导致的结果是主电源掉电后电池会直接给 VCC2 上的其他器件供电。如果模块上还带有电源指示灯有些模块会焊一个 LED这个 LED 就会一直亮着把电池电量快速耗尽。焊接或插接模块时建议先仔细看一下丝印和原理图。如果模块上有 “VBAT” 和 “VCC” 跳线通常默认是连在一起的。要改成独立供电得把跳线断开让 VCC2 只接主电源VCC1 只接电池。如果模块上没有跳线只是把 VCC1 和 VCC2 通过一根 PCB 铜线短接你甚至需要用小刀割断铜箔。操作前一定要拿万用表量一下 VCC1 和 VCC2 之间是否导通免得改完以后电池路径根本没断。另外DS1302 内部的晶振电路对负载电容极其敏感。官方手册要求 32.768kHz 晶振的负载电容在 6pF 左右这也是很多高品质 RTC 晶振的常见标称值。如果模块上焊的晶振是 12.5pF 负载电容的实际频率会偏离额定值每天可能差好几秒。质检严格的话应该在焊接后实测一下频率用频率计或示波器看 X1/X2 引脚波形误差控制在 ±20ppm 以内比较理想。3.3 DIY 电路板时的布局建议如果是自己画板DS1302 的布局核心是晶振。晶振要尽量靠近 X1/X2 引脚走线短且等长不要在晶振下面穿其他高速信号线。晶振两端的负载电容大小按晶振规格书选取很多 6pF 负载晶振在 DS1302 上甚至不需要外加电容但要预留焊盘位置方便调试时调整。VCC2 和 VCC1 的滤波电容建议各放一个 0.1µF靠近电源引脚。VCC1 到电池座的线路上不需要串联电阻但可以加一个反接保护二极管吗我的建议是不要加。二极管压降会吃掉电池电压导致电池电压低于 2.0V 时 DS1302 无法工作。正确做法是电池座的正负极丝印标注清楚焊接时确认方向。CR2032 如果是平装电池座一般有防呆设计但如果是弹簧式装反的概率还是不低的。4. 软件驱动实现和时序设计手写一套 DS1302 驱动DS1302 的数据手册上写得很清楚它的通信协议是类似 SPI 的三线制但每次读写都要先发一个命令字节然后才能读写数据。命令字节最低位是读/写控制位如果是 0 表示写1 表示读高 5 位是寄存器地址最高位固定为 1。整个过程每发一个字节都是低位在前LSB first这是它在软件上和普通 SPI 最大的区别。4.1 为什么不用硬件 SPI而是用 GPIO 模拟DS1302 的操作是由 MCU 产生 SCLK然后 I/O 在 SCLK 的下降沿输出或采样数据。这其实是 SPI 模式 0 的变体但有两个关键点导致硬件 SPI 不好用一是命令字节和后续数据字节之间SCLK 需要连续产生且不能违反 DS1302 的时序约束二是 I/O 方向需要频繁切换写完命令字节后要立刻把 I/O 从输出模式切到输入模式硬件 SPI 做这种半双工切换比较麻烦GPIO 模拟反而直观可控。用 GPIO 模拟的好处还有一点不占用固定的 SPI 外设引脚可以根据实际 PCB 布线任意分配。对于低速 RTC 芯片来说GPIO 翻转速度完全够用只要控制好延时时序稳定度没有问题。4.2 DS1302 读写一个字节的完整代码以 STM32 标准外设库为例下面这段代码实现了一个最基本的字节写入和字节读取。写入时在 SCLK 上升沿前把数据放到 I/O 上读取时在 SCLK 下降沿后读取 I/O 的电平。#define DS1302_CE_HIGH GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET) #define DS1302_CE_LOW GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET) #define DS1302_CLK_HIGH GPIO_WriteBit(GPIOC, GPIO_Pin_14, Bit_SET) #define DS1302_CLK_LOW GPIO_WriteBit(GPIOC, GPIO_Pin_14, Bit_RESET) #define DS1302_IO_HIGH GPIO_WriteBit(GPIOC, GPIO_Pin_15, Bit_SET) #define DS1302_IO_LOW GPIO_WriteBit(GPIOC, GPIO_Pin_15, Bit_RESET) /* 微秒级延时根据主频调整 */ static void delay_us(uint32_t us) { uint32_t i; for (i 0; i us * 8; i) { __NOP(); } } void DS1302_WriteByte(uint8_t dat) { uint8_t i; GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; // 推挽输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOC, GPIO_InitStructure); for (i 0; i 8; i) { DS1302_CLK_LOW; if (dat 0x01) { DS1302_IO_HIGH; } else { DS1302_IO_LOW; } delay_us(1); DS1302_CLK_HIGH; delay_us(1); dat 1; } } uint8_t DS1302_ReadByte(void) { uint8_t i, dat 0; GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; // 浮空输入外部上拉 GPIO_Init(GPIOC, GPIO_InitStructure); for (i 0; i 8; i) { dat 1; DS1302_CLK_HIGH; delay_us(1); DS1302_CLK_LOW; delay_us(1); if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_15)) { dat | 0x80; } } return dat; }需要注意写字节之前要把 I/O 配置为推挽输出读字节之前要切换成输入模式。如果没有做模式切换I/O 引脚一直保持输出状态去读数据读回来的会一直是自己输出的电平永远读不到 DS1302 里存储的正确时间。4.3 突发模式读写时间和 BCD 码转换DS1302 的时间寄存器地址从秒到年分别是 0x80、0x82、0x84、0x86、0x88、0x8A、0x8C。可以一个寄存器一个寄存器地读写也可以使用突发模式Burst Mode一次性连续读写 7 个字节。写突发命令字节是 0xBE读突发命令字节是 0xBF。我习惯使用突发模式因为代码更简洁而且一次性把时间载入结构体不容易出现读取到一半发生进位导致时间不连贯的问题。当然如果对时序要求极高可以在读秒寄存器的值时把 CE 拉低重读直到连续两次读到的秒值一致这样可以避免在秒进位瞬间读到不一致的时间。时间数据在 DS1302 内部是以 BCD 码二进制编码的十进制存储的。比如秒寄存器里存 0x35表示的其实是 35 秒而不是十六进制的 0x3553 秒。所以写入前要把整数时间转换为 BCD 码读取后要把 BCD 码还原成正常的十进制数。最简单的方法是使用除法和取余uint8_t dec_to_bcd(uint8_t dec) { return ((dec / 10) 4) | (dec % 10); } uint8_t bcd_to_dec(uint8_t bcd) { return ((bcd 4) * 10) (bcd 0x0F); }下面是设置和读取时间的完整例程包含了写保护位处理typedef struct { uint8_t year; // 00~99 uint8_t month; // 01~12 uint8_t date; // 01~31 uint8_t hour; // 00~23 uint8_t minute; // 00~59 uint8_t second; // 00~59 uint8_t week; // 01~07 } RTC_TimeTypeDef; void DS1302_SetTime(RTC_TimeTypeDef *tm) { DS1302_CE_HIGH; // 关闭写保护 DS1302_WriteByte(0x8E); DS1302_WriteByte(0x00); DS1302_CE_LOW; // 进入突发写模式 DS1302_CE_HIGH; DS1302_WriteByte(0xBE); DS1302_WriteByte(dec_to_bcd(tm-second)); DS1302_WriteByte(dec_to_bcd(tm-minute)); DS1302_WriteByte(dec_to_bcd(tm-hour)); DS1302_WriteByte(dec_to_bcd(tm-date)); DS1302_WriteByte(dec_to_bcd(tm-month)); DS1302_WriteByte(dec_to_bcd(tm-week)); DS1302_WriteByte(dec_to_bcd(tm-year)); DS1302_CE_LOW; } void DS1302_GetTime(RTC_TimeTypeDef *tm) { uint8_t buf[7]; uint8_t i; DS1302_CE_HIGH; DS1302_WriteByte(0xBF); // 读突发模式 for (i 0; i 7; i) { buf[i] DS1302_ReadByte(); } DS1302_CE_LOW; tm-second bcd_to_dec(buf[0]); tm-minute bcd_to_dec(buf[1]); tm-hour bcd_to_dec(buf[2]); tm-date bcd_to_dec(buf[3]); tm-month bcd_to_dec(buf[4]); tm-week bcd_to_dec(buf[5]); tm-year bcd_to_dec(buf[6]); }初始化时把 DS1302 的 CE 引脚拉低然后发送 0x8E 写保护关闭命令。DS1302 的写保护寄存器默认值是 0x80即写保护开启如果不先关闭任何对时间寄存器的写入都会被忽略这是很多新手最容易忽略的问题。设置完时间后可以再写一次 0x8E 和 0x80 重新开启写保护防止后续程序误改时间。4.4 “频率计”调试技巧与精度校正DS1302 的时间精度完全取决于外部 32.768kHz 晶振。晶振本身有频率偏差温度变化还会引起漂移所以整机走时不可能做到绝对精准。在实际调试时我会在 DS1302 的 X1 或 X2 引脚上引出一根测试线用频率计测量晶振频率。严格来说X1/X2 引脚直接测量会引入探头电容导致频率轻微改变但用来判断晶振是否起振、频率是否严重偏出范围已经足够。如果实测频率偏低或者偏高且偏差固定可以在软件里做校准。DS1302 虽然有数字校准寄存器但最常用的做法是每秒对晶振计数然后软件补偿。比如实测频率是 32767.5Hz一天就差大约 1.3 秒可以在驱动里每隔一段时间统一修正一次。另一种思路是用外部高精度时间源比如通过串口或者 WiFi 拿到网络时间在设备空闲时定期同步 DS1302这样即使单颗晶振有误差长期来看时间也能保持准确。这里再聊一下“时钟抖动、频偏和漂移”这几个词。抖动是时钟沿在短时间内的随机偏移主要影响高速信号时序对 RTC 这种低速设备影响不大频偏是晶振实际频率和标称值的固定偏差导致每天都有稳定的误差漂移则是频率随温度、电压、老化等原因发生的缓慢变化。做产品时如果 DS1302 放在温度变化剧烈的环境里就要用更高精度的温补晶振或者定期接收外部时间同步。5. 常见问题速查与实战避坑DS1302 虽然简单但实际项目中还是有不少经典问题。我根据实际调试中踩过的坑整理了一个排查顺序你可以按这个顺序一步步查。5.1 时间不走、乱码、写不进去三个经典问题第一个问题是时间完全不走读取出来一直是 00:00:00 或者不变。这种大概率是 32.768kHz 晶振没有起振。用示波器或频率计测 X1 引脚如果没有波形先检查晶振是否虚焊、负载电容是否匹配、DS1302 供电是否正常。有些模块的晶振外壳是金属的焊接时焊锡把外壳和邻近引脚连在一起也会导致停振。第二个问题是时间能读出来但设置进去以后不生效。检查写保护是否关闭命令字节是否正确。DS1302 的写保护寄存器是 0x8E写入 0x00 才能解除保护。每次上电时芯片默认写保护开启所以初始化函数里必须先发送关闭写保护命令。第三个问题是数据乱码比如读出来的月份是 19、秒是 78。这种情况通常是 BCD 码转换函数写反了或者读取时 I/O 引脚没有正确切换为输入模式。还有一种可能是读取过程中 SCLK 的上升沿和下降沿延时太短总线数据还没稳定就被采样导致误码。我给读写函数加的 1µs 延时实际在 72MHz 主频下跑完全没问题但如果你用的主频更高可以适当延长到 2µs。5.2 电池装反、漏电和“涓流充电”的正确姿势DS1302 支持涓流充电功能寄存器地址是 0x90。当 VCC2 主电源存在时可以按设定好的电阻/二极管参数给 VCC1 上的充电电池供电。注意这个功能默认是关闭的只有你往 0x90 寄存器写入特定值才会启用。寄存器数据结构是高 4 位写 1010 使能充电第 3、2 位选择串联二极管数量第 1、0 位选择限流电阻阻值。千万不要给普通 CR2032 纽扣电池开启涓流充电。CR2032 是一次性锂锰电池不可充电强行充电会导致电池内部压力升高、漏液甚至鼓包反过来损坏 PCB 和芯片。如果你要用可充电电池就选 LIR2032这是专门的可充电纽扣电池或者用超级电容。在实际项目里我一般选择直接禁止涓流充电把 0x90 寄存器写入 0x00让 VCC1 只作为纯备用电源。关于漏电前面提到过成品模块的上拉电阻会造成电池额外消耗。最稳妥的做法是把模块上接在 SCLK/I/O/CE 三个引脚到 VCC2 的上拉电阻全部拆掉。如果你的板子是高速信号或者长走线确实需要上拉来保证信号完整性那可以把上拉电阻改接到 VCC2 的“受控电源”上也就是掉电后跟着主电源一起断开的电源轨这样就不会消耗电池电量。5.3 低功耗整机设计中DS1302 怎么和 STM32 深度睡眠共存很多手持设备在待机时需要进入 STOP 模式甚至待机模式把整机电流降到微安级别。DS1302 在这种情况下有两个作用一是作为实时时钟维持当前时间二是作为闹钟在特定时间通过中断唤醒 MCU。但要注意DS1302 的闹钟功能需要 CE/SCLK/I/O 之外的中断输出引脚而很多 DS1302 模块并没有引出这个引脚甚至有些芯片型号根本没有闹钟输出功能。用之前要仔细看有没有 INT 或者 /INT 引脚不要想当然。如果 MCU 进入 STOP 模式GPIO 保持之前配置的输出状态。为了让 DS1302 在不工作时尽量少耗电可以在 MCU 睡眠前把 CE 引脚拉低SCLK 和 I/O 引脚都配置为浮空输入或者输出低电平避免在总线上产生电平冲突。实测这样配置后DS1302 的静态电流很小整机待机电流基本能控制在 10µA 以下。还有一点STM32 进入 STOP 模式后如果 DS1302 和 MCU 共用同一个 3.3V LDO而 LDO 的静态电流比较大那整机功耗未必能降下来。想要极低待机可以考虑用一颗带使能引脚的 LDO由 MCU 的某个 GPIO 控制在进入 STOP 前关闭主电源只让 DS1302 和电池回路继续工作。6. 我踩过的坑和最后的几条经验最后说点实在的。DS1302 这个芯片看起来非常简单但真正做好“不掉电”却需要仔细处理电源路径、晶振选型和软件时序。我最初用模块时遇到过电池几个月就没电的问题后来发现是模块上 VCC2 和 VCC1 的跳线没有断开LED 指示灯一直在漏电。还有一次写代码忘了关闭写保护结果调了一整天所有寄存器写入都无效最后翻数据手册才发现这个问题。从产品化角度看如果你想把这套方案用到量产设备里我建议做这几件事第一在 PCB 上预留 VCC1 电池电压检测点出厂前用自动化测试设备量一下电池电压防止装到电池虚接或漏电的板子第二在固件里加一个“RTC 初始化标志”在 DS1302 备份寄存器或外部 EEPROM 里写入一个魔术字每次上电读取时间时顺便校验一下如果时间年份小于 2020 年就认为 RTC 已经失效主动进入待设置状态而不是让系统带着错误时间运行第三设置时间时最好通过串口或上位机写入不要每次都走默认编译时间因为固件编译时间和实际出厂时间往往有几天甚至几个月的偏差。这套 DS1302 纽扣电池的组合优点是成本低、电路简单、开发周期短。缺点是芯片本身比较老没有内置温度补偿精度上限确实不如高端的 RX8025 或甚至内部 RTC 搭配温补晶振。如果你需要更高精度可以换成 RX8025T 这类带温度补偿的 RTC但供电切换逻辑和软件驱动思路是相通的。把 DS1302 的底层吃透再迁移到其他 RTC 上基本就是改个寄存器映射的事。希望这份“加不掉电时钟”的实战记录能帮到正在折腾 STM32 外部 RTC 的朋友。如果你也遇到类似问题欢迎交流尤其是晶振选型和低功耗漏电这两块很多时候细节决定了产品最终体验。
返回列表