news 2026/10/14 1:26:04

STM32驱动DS1302实战:从三线时序到掉电不丢时间的完整踩坑记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32驱动DS1302实战:从三线时序到掉电不丢时间的完整踩坑记录

把模拟项目X里那颗 DS1302 重新折腾了一遍,我才发现很多教程讲得都太“顺利”了。芯片本身确实简单:三线接口、几个寄存器、一块纽扣电池,可真正把它接到 STM32 上,读回 0xFF、时间不走、掉电丢时间这些经典问题,我几乎是一个不落地全碰了一遍。这篇笔记想把 STM32 驱动 DS1302 的完整链路好好顺一遍——从为什么选这颗芯片、三线时序和寄存器机制,到代码怎么写,再到我实测踩过的坑和工程化建议。如果你是刚开始接触外部 RTC,或者已经写通了但总觉得哪里没吃透,这篇应该能帮你省下不少折腾时间。

1. 为什么用 DS1302:这颗“老芯片”在项目里的定位

1.1 它解决的是“单片机没有时间”的问题

STM32 内部其实是有 RTC 外设的,但很多人忽略一个前提:内部 RTC 的时间基准维持依赖 VBAT 引脚供电,而且一旦 MCU 本身复位、固件重新下载、电池没接,时间就归零了。更麻烦的是,换一颗 MCU 型号,内部 RTC 的寄存器配置基本要重写一遍。对于需要记录“事件在什么时间发生”的产品,比如数据采集器、电子日历、定时控制,外部 RTC 几乎是绕不开的方案。

DS1302 在这类方案里属于经典款。它用三根线(CE、SCLK、I/O)和主控通信,占用引脚非常少;外部接一颗 32.768kHz 晶振,走时功耗极低;主电源断电后还能靠备用电池继续跑时间。价格在今天看来也相当便宜,所以很多成本敏感的小产品依然在用。这颗芯片虽然老,但在“要一个掉电不丢的时间”这个需求面前,依然很能打。

1.2 选型对比:DS1302、内部 RTC 与 DS3231

我接触过的项目里,最常被放在一起比较的三类时间方案是:DS1302、MCU 内部 RTC、DS3231。它们的定位差异很明显。

方案接口掉电保持精度成本适合场景
DS1302三线 GPIO 模拟外接电池,VCC1 引脚中,取决于外部 32.768kHz 晶振低电子钟、低成本记录仪、定时产品
MCU 内部 RTC内部外设需 VBAT 引脚外接电池中无额外芯片成本不想加外部器件、对精度要求不高的产品
DS3231I2C外接电池高,内置温补晶振高计费设备、对时间精度有硬要求的场景

这里有个容易忽略的点:DS1302 的价格优势建立在“精度够用就行”的前提下。它的走时精度主要由外部晶振决定,普通 32.768kHz 晶振在温度变化下一天差个两三秒很常见。如果产品要长时间累计计时,比如打卡考勤、计时收费,那 DS3231 的温补晶振更合适。DS1302 更适合“一天手动校准一次也能接受”的使用场景。

1.3 适合的场景和不适合的场景

从我自己的实践看,DS1302 适合做这几类事情:带时间戳的环境温湿度记录仪、桌面电子钟、定时器开关控制、低成本打卡设备。这些场景的共同点是:不需要毫秒级精度,不需要频繁对时,但要求掉电后时间不丢。

不太适合的场景也很明确:需要高精度时间同步的设备,直接上 DS3231 或者带网络对时的方案;需要毫秒级甚至微秒级时间戳的采集系统,RTC 本身只精确到秒,这点硬伤换任何外部 RTC 都一样;还有就是项目团队里如果已经有成熟的内部 RTC 封装,且电池供电设计完善,也不一定非要外挂 DS1302。选型这事,够用和好用之间要拎得清。

2. 驱动前的两个基本功:三线时序与寄存器地图

2.1 三线接口:看着像 SPI,但别当 SPI 用

DS1302 的三线接口由 CE、SCLK、I/O 组成。CE 是使能脚,只有在传输期间保持高电平,读写操作结束后拉低;SCLK 是主机产生的串行时钟;I/O 是真正的双向数据线,写数据时由主机驱动,读数据时由 DS1302 驱动。这和 SPI 那种 MOSI/MISO 分开的双向方案完全不同,I/O 这根线需要在读写过程中切换方向。

我第一次写驱动时,很自然想用 STM32 的硬件 SPI 去驱动它,结果发现并不顺手。因为 DS1302 的 CE 时序和 SPI 片选不完全一致,I/O 又要切方向,用硬件 SPI 反而要在中断里反复折腾。实际做下来,GPIO 模拟三线时序最省心,速度也完全够——DS1302 的 SCLK 上限大约是 2MHz,而 GPIO 翻转跑个几百千赫兹毫无压力。所以不用纠结“模拟是不是太低效”,这个场景下稳定和简单比速度重要。

还有一个细节:DS1302 的数据是 LSB first,也就是低位先传。这个和很多人的习惯正好相反,写代码时特别容易把字节搞反。我当时就是没注意这一点,命令字发得对,但内部数据位反了,调试了很久。记住:第一个 bit 是 bit0,不是 bit7。

2.2 命令字节是怎么算出来的

DS1302 的所有操作都从主机发送一个 8 位命令字节开始。这个字节的结构,网上不少资料写得含含糊糊,我按实际验证过的结果重新整理一下。

命令字节从高位到低位依次是:

bit7bit6bit5bit4bit3bit2bit1bit0
固定为 1写保护位 WP寄存器地址 A4A3A2A1A0读写选择 R/W

bit7 固定为 1,表示这是一个有效命令;bit6 是写保护位,如果置 1,DS1302 会拒绝任何写操作;bit5 到 bit1 是寄存器地址;bit0 是读写选择,1 表示读,0 表示写。

常见寄存器的命令字可以直接推导出来。比如秒寄存器的地址是 0x00,那么写秒命令是 0x80(bit7=1,bit6=0,地址 00000,bit0=0),读秒命令是 0x81。分寄存器地址 0x01,写分是 0x82,读分是 0x83。依此类推,写保护控制寄存器的地址是 0x07,所以写控制寄存器用 0x8E,读控制寄存器用 0x8F。

这里特别注意:命令字节里的 bit6 写保护位,和控制寄存器里的 bit7 写保护位,是两个不同层级的东西。命令字节里的 WP 位如果为 1,这次命令直接被拒绝;而控制寄存器里的 WP 位(0x8E 写入的 bit7)是持久保护状态,置 1 后后续所有写操作都被禁止,除非先向 0x8E 写入 0x00。简单说,每次写时间前,先向 0x8E 写 0x00 解除保护;写完再写 0x80 恢复保护。这是驱动里必须有的步骤。

2.3 BCD 码:读回 0x59 不是十进制的 59

DS1302 内部时间寄存器用 BCD 码存储,也就是一个字节的高四位表示十位,低四位表示个位。秒寄存器读回 0x59,代表的是十进制的 59 秒,而不是十六进制的 0x59 直接转十进制变成 89。这个如果不处理,显示会非常诡异。

我用到的转换方式很常规:

static uint8_t BCD_TO_DEC(uint8_t bcd) { return (bcd >> 4) * 10 + (bcd & 0x0F); } static uint8_t DEC_TO_BCD(uint8_t dec) { return ((dec / 10) << 4) + (dec % 10); }

所有读写时间的函数,都必须过一遍这两个转换,只对寄存器原始值操作是行不通的。另外要注意,秒寄存器最高位是 CH 位,读时间时要屏蔽掉;时寄存器也有 12/24 小时制标志位,读的时候同样要按位处理。这些细节我在下面的代码里会具体说明。

3. 从零写驱动:GPIO 模拟三线时序

3.1 硬件连接与 CubeMX 配置

驱动代码之前,硬件先接对。DS1302 常见封装有 8 个引脚,我这里列一个我验证过的连接方式:

DS1302 引脚连接到说明
VCC23.3V 或 5V主电源
VCC1备用电池正极主电源断电后给芯片供电,不需要掉电保持可不接
GNDGND共地
CESTM32 GPIO(如 PA1)使能脚,传输期间拉高
SCLKSTM32 GPIO(如 PA2)串行时钟
I/OSTM32 GPIO(如 PA3)双向数据线,建议加 10kΩ 上拉到 VCC
X1、X232.768kHz 晶振两端,各对地接一个 6pF 电容晶振尽量靠近芯片

在 CubeMX 里,CE、SCLK、I/O 三个引脚可以都配成 GPIO 输出,初始状态拉低。I/O 引脚比较特殊,它需要在输出和输入之间动态切换。我实际用的是推挽输出模式,配合外部 10kΩ 上拉,读数据时把引脚重新初始化成输入模式。这样做的理由是:DS1302 读数据时 I/O 由芯片驱动,如果主机引脚还保持在推挽输出状态,两个输出源会打架,轻则读不到数据,重则损伤引脚。方向切换必须在时序里控制好时机。

3.2 底层时序代码:先把一个字节读明白

先看 GPIO 操作的宏定义和延时函数:

#define DS1302_CE_H() HAL_GPIO_WritePin(DS1302_CE_GPIO_Port, DS1302_CE_Pin, GPIO_PIN_SET) #define DS1302_CE_L() HAL_GPIO_WritePin(DS1302_CE_GPIO_Port, DS1302_CE_Pin, GPIO_PIN_RESET) #define DS1302_SCLK_H() HAL_GPIO_WritePin(DS1302_SCLK_GPIO_Port, DS1302_SCLK_Pin, GPIO_PIN_SET) #define DS1302_SCLK_L() HAL_GPIO_WritePin(DS1302_SCLK_GPIO_Port, DS1302_SCLK_Pin, GPIO_PIN_RESET) #define DS1302_IO_H() HAL_GPIO_WritePin(DS1302_IO_GPIO_Port, DS1302_IO_Pin, GPIO_PIN_SET) #define DS1302_IO_L() HAL_GPIO_WritePin(DS1302_IO_GPIO_Port, DS1302_IO_Pin, GPIO_PIN_RESET) #define DS1302_IO_READ() HAL_GPIO_ReadPin(DS1302_IO_GPIO_Port, DS1302_IO_Pin)
static void DS1302_Delay(void) { for (volatile uint8_t i = 0; i < 10; i++); }

这个延时是给 SCLK 高电平和低电平留出最小保持时间。DS1302 对 SCLK 的脉宽有要求,虽然留几十纳秒可能也能跑,但我会故意把延时调大一点,保证在任何编译优化等级下都不会被优化掉。volatile 关键字在这里很关键,别省。

写一个字节到 DS1302:

static void DS1302_WriteByte(uint8_t dat) { DS1302_IO_Output(); for (uint8_t i = 0; i < 8; i++) { if (dat & 0x01) DS1302_IO_H(); else DS1302_IO_L(); dat >>= 1; DS1302_SCLK_H(); DS1302_Delay(); DS1302_SCLK_L(); DS1302_Delay(); } }

核心逻辑是:先把数据放到 I/O 上,再拉高 SCLK,让 DS1302 在上升沿采样。数据是 LSB first,所以每次判断最低位,然后右移。这里不需要切输入模式,因为整个字节都是在写。

从 DS1302 读一个字节:

static uint8_t DS1302_ReadByte(void) { uint8_t dat = 0; DS1302_IO_Input(); for (uint8_t i = 0; i < 8; i++) { dat >>= 1; DS1302_SCLK_H(); DS1302_Delay(); DS1302_SCLK_L(); DS1302_Delay(); if (DS1302_IO_READ()) dat |= 0x80; } return dat; }

读数据的时序和写不一样。主机产生 SCLK 脉冲,DS1302 在 SCLK 下降沿把数据放到 I/O 线上,所以主机在下降沿之后读取引脚。代码里是先拉高再拉低,产生一个下降沿,延时稳定后读引脚。dat 的处理方式有点 tricky:每读到一个 bit 先放在高位,然后下一次循环右移,等到 8 次循环结束,最早读到的 bit0 恰好落在最低位,最后一个 bit 落在最高位。这个写法我在初学时很容易绕晕,如果你不喜欢,也可以改用 dat |= (DS1302_IO_READ() << i) 的方式,逻辑更直观。

完整的寄存器读写封装:

static void DS1302_WriteReg(uint8_t cmd, uint8_t dat) { DS1302_CE_H(); DS1302_WriteByte(cmd); DS1302_WriteByte(dat); DS1302_CE_L(); } static uint8_t DS1302_ReadReg(uint8_t cmd) { uint8_t dat; DS1302_CE_H(); DS1302_WriteByte(cmd); dat = DS1302_ReadByte(); DS1302_CE_L(); return dat; }

每次操作开始前把 CE 拉高,结束后拉低。注意 CE 在整套传输过程中必须保持高电平,不能中途抖动,否则 DS1302 会认为这次传输无效。SCLK 在 CE 拉低之前也要处于低电平,这是数据手册时序图里的要求。

3.3 时间结构体与读写函数

时间数据用一个结构体管理,年份只保留两位,00 到 99,世纪问题交给应用层处理:

typedef struct { uint8_t year; /* 00-99 */ uint8_t month; /* 01-12 */ uint8_t date; /* 01-31 */ uint8_t week; /* 01-07 */ uint8_t hour; /* 00-23,使用24小时制 */ uint8_t minute; /* 00-59 */ uint8_t second; /* 00-59 */ } DS1302_Time_t;

读取时间:

void DS1302_GetTime(DS1302_Time_t *tm) { tm->second = BCD_TO_DEC(DS1302_ReadReg(0x81) & 0x7F); tm->minute = BCD_TO_DEC(DS1302_ReadReg(0x83) & 0x7F); tm->hour = BCD_TO_DEC(DS1302_ReadReg(0x85) & 0x3F); tm->date = BCD_TO_DEC(DS1302_ReadReg(0x87) & 0x3F); tm->month = BCD_TO_DEC(DS1302_ReadReg(0x89) & 0x1F); tm->week = BCD_TO_DEC(DS1302_ReadReg(0x8B) & 0x07); tm->year = BCD_TO_DEC(DS1302_ReadReg(0x8D)); }

读秒寄存器时用 0x7F 把 CH 位去掉,读小时寄存器时用 0x3F 屏蔽 12/24 小时制标志位,日期、月份、星期也都按数据手册的位定义做了掩码。虽然正常情况下我们自己写入的数据是干净的,但一颗被别人动过的芯片或模块,寄存器里可能有意外标志位,掩码是低成本保险。

写入时间:

void DS1302_SetTime(DS1302_Time_t *tm) { DS1302_WriteReg(0x8E, 0x00); /* 关闭写保护 */ DS1302_WriteReg(0x80, DEC_TO_BCD(tm->second)); /* 写入秒,同时清CH位启动振荡 */ DS1302_WriteReg(0x82, DEC_TO_BCD(tm->minute)); DS1302_WriteReg(0x84, DEC_TO_BCD(tm->hour)); DS1302_WriteReg(0x86, DEC_TO_BCD(tm->date)); DS1302_WriteReg(0x88, DEC_TO_BCD(tm->month)); DS1302_WriteReg(0x8A, DEC_TO_BCD(tm->week)); DS1302_WriteReg(0x8C, DEC_TO_BCD(tm->year)); DS1302_WriteReg(0x8E, 0x80); /* 开启写保护 */ }

写秒寄存器时传入的 BCD 值最高位是 0,正好把 CH 位清掉,芯片启动振荡。所以不需要额外单独清 CH 位。写小时寄存器时,由于我们统一用 24 小时制,DEC_TO_BCD(23) 之类的结果高位 bit6 一定是 0,不会误触发 12 小时制模式。

初始化函数:

void DS1302_Init(void) { DS1302_CE_L(); DS1302_SCLK_L(); DS1302_IO_Output(); /* 解除写保护,避免后续操作被拒 */ DS1302_WriteReg(0x8E, 0x00); /* 读取秒寄存器,如果CH位为1,说明振荡停止,需要清除CH位 */ uint8_t sec = DS1302_ReadReg(0x81); if (sec & 0x80) { DS1302_WriteReg(0x80, sec & 0x7F); } /* 恢复写保护 */ DS1302_WriteReg(0x8E, 0x80); }

这颗芯片的特点是没有复位引脚,上电后寄存器内容随机,而且控制寄存器默认写保护生效,秒寄存器默认停振。初始化时做一次“解除保护、检查 CH 位、恢复保护”相当必要。如果 CH 位为 1 而不处理,读出的时间有可能停留在上电随机值,看起来像是在走,实际上根本没更新。

3.4 突发模式要不要用

DS1302 还支持突发模式,也就是一次性连续读写 8 个时间寄存器。写突发命令是 0xBE,读突发命令是 0xBF。突发模式看起来省事,但我实际用下来发现它有两个问题:一次传输时间更长,容易在读时间的临界点遇到秒进位导致数据不一致;多出来的控制寄存器字节还要多余处理。如果不是对连续传输有特殊需求,我更推荐逐寄存器读写,代码清晰,排查也容易。

如果确实要用突发读取,一个实用技巧是连续读两次,比较两次读到的秒值是否一致。如果秒值不同,说明读取过程跨越了临界点,丢弃重读一次。这个方法成本很低,但能显著减少脏数据出现的概率。

4. 上电实测与调试中踩过的坑

4.1 第一次读全 0xFF:IO 方向切换是头号凶手

我在第一次上电调试时,读秒寄存器得到 0xFF,整套代码看起来没问题,时间就是读不出来。这里把排查过程记一下,你遇到同样现象可以照着走。

先说最容易忽视的一步:I/O 引脚上拉电阻。DS1302 读数据时是开漏输出风格,如果 I/O 没有上拉,读到的电平会飘,结果大概率是 0xFF。所以硬件上先确认 I/O 有没有 10kΩ 左右的上拉到 VCC。

然后是 IO 方向切换的时机。读寄存器时,命令字节发送完成后、读数据字节之前,必须把 I/O 切到输入模式。如果切换太滞后,或者切之前还往 I/O 上写过高电平,都可能造成读数据失败。我在 ReadReg 函数里是先发命令,然后立刻调 DS1302_IO_Input(),中间不加多余操作,避免电平没稳定就开时钟。

还有一个隐蔽问题:命令字节本身发错了。很多人写完命令字节后不检查,比如读秒寄存器明明该发 0x81,发成了 0x80,DS1302 会认为这是写操作,I/O 方向自然不会切换,读回来自然全是 0xFF。排除方法很简单,用逻辑分析仪抓 CE、SCLK、I/O 三根线,对照数据手册的时序图看命令字节和数据字节。没有逻辑分析仪的话,也可以用示波器看 I/O 线上的电平变化,命令字节阶段和数据字节阶段的电平方向应该明显不同。

4.2 时间不走:秒寄存器的 CH 位和写保护

第二个高频问题是时间不走或时间乱跳。这时候排查思路分两条线:第一看写保护有没有正确关闭,第二看秒寄存器的 CH 位是否被清掉。

CH 是秒寄存器最高位,置 1 时振荡器停止,时钟完全不走。很多模块到手后默认 CH 位是 1,必须通过写秒寄存器清除。我在初始化函数里已经写了这个处理。调试时如果发现秒值一直不变,读一下原始秒寄存器(没做 BCD 转换和掩码之前)看看最高位是不是 1,如果是,说明问题就在这。

写保护方面,DS1302 上电后默认禁止写。如果代码里直接写时间,没有先向 0x8E 写 0x00,那写操作会被静默拒绝,表面上程序跑得正常,实际寄存器一个字节都没改。这种问题最坑,因为读出来的时间看似有点规律,其实是芯片里残留的随机值。判断方法也简单:先读一次时间,手动改掉其中一个字段,比如把月份改成 9 月写回去,再读出来看有没有变化。没变化就是写保护没处理好或者写命令根本没生效。

4.3 晶振不起振和布局建议

DS1302 的时间基准来自 X1、X2 引脚之间的 32.768kHz 晶振。这颗晶振一定要选对,32.768kHz 是音叉晶振,外形像细圆柱或者贴片小封装,别拿处理器主板上那种 8MHz 的无源晶振凑数。晶振要尽量贴着芯片放,走线短一点,两边各接一个负载电容到地。DS1302 数据手册里的典型值是 6pF,我实际在模块上用 6pF 到 12pF 都遇到过,都能起振,但电容太大会导致频偏变大,一天差好几秒。

晶振不起振的排查,示波器探头可以直接点在 X1 引脚上看有没有振荡波形。如果波形幅度很小,可能是负载电容偏大或者晶振本身质量问题;如果完全没有波形,先检查焊接,尤其是手工贴片时虚焊很常见。另外,如果代码里 CH 位一直是 1,晶振就算正常也不会工作,所以这个坑要排在晶振检查前面。

4.4 备用电池和涓流充电:一个决定时间能不能保住的细节

DS1302 有 VCC1 和 VCC2 两个电源引脚。VCC2 接主电源,VCC1 接备用电池。主电源正常时芯片由 VCC2 供电,主电源掉电后自动切到 VCC1 电池供电。如果你只需要芯片上电时工作,不要求掉电保持,VCC1 可以悬空或者直接接到 VCC2,但这样做上电后时间必然丢失。

电池选择上有个很容易踩的坑:DS1302 支持涓流充电,也就是可以通过 VCC1 给可充电电池充电。但很多开发板默认带的是 CR2032 这种一次性锂电池,绝对不能开涓流充电。CR2032 不可充电,强制充电轻则电池鼓包漏液,重则有安全隐患。如果你确实想用可充电电池,比如 LIR2032,才需要通过涓流充电寄存器(命令 0x90 写配置)开启充电功能。配置值要谨慎,0xA5 之类的开启代码网上很多,实际用的时候我会把电阻档位选大一点,限制充电电流,宁可充慢一点也求安全。

4.5 上电复位后时间被覆盖:初始化逻辑要想清楚

还有一个特别容易让初学者困惑的现象:代码每次上电都执行 SetTime,结果时间永远停在程序烧录那一刻。这不是 DS1302 的问题,是主控代码的问题。正确做法是:上电后先尝试读时间,判断数据是否有效。如果年月日读出来全是 0xFF,说明芯片从未初始化或电池彻底没电,这时候才提示用户设置时间;如果读出来是有效值,直接使用就是。可以用一个标志位,比如在某个不再使用的寄存器位置写入特定值来做判断,也可以更简单地检查“年”和“月”字段的合法范围。项目里我用的是年字段加一个上电标志位存放在 DS1302 的涓流充电寄存器高位,每次读的时候校验标志,简单有效。

5. 开源工程化建议:把学习笔记变成能用的驱动

5.1 驱动分层:硬件依赖和逻辑分开

如果你的项目只是点个灯,驱动放一个文件里无所谓。但 DS1302 这种驱动代码往往要复用到多个项目里,我建议分成两层。最底层是引脚映射和 GPIO 操作,专门放一个 bsp 文件,里面管 CubeMX 生成的引脚、IO 方向切换、底层时序;上层是 DS1302 纯逻辑驱动,接收一个回调或者直接调用底层函数,完成命令、寄存器操作、BCD 转换、时间封装。这样换一块板子,只需要改底层引脚映射,驱动逻辑一个字节都不用动。

文件结构大致是:bsp_ds1302.c/h 管引脚和底层延时,ds1302.c/h 管寄存器读写和时间结构体,app_rtc.c 管业务逻辑,比如上电是否需要校准、时间如何显示等。层次关系清晰之后,调试时也能快速定位问题在硬件层还是逻辑层。

5.2 代码健壮性:校验、容错与掉电恢复

DS1302 毕竟是外部芯片,任何读写都可能受干扰,所以健壮性处理值得多花点心思。我实际项目里做了这几件事:

读时间后做 BCD 范围校验。如果月份读出来是 13 月、日期是 40 号这种非法值,直接判定这次读取无效,按错误处理,而不是把乱数显示给用户。

写时间前校验用户传入的时间范围。秒、分不能超过 59,时不能超过 23,月份 1 到 12,日期按当月天数判断。这种保护能避免脏数据写进 DS1302,毕竟芯片内部的处理能力很有限。

主控上电后读一次时间,如果发现芯片数据无效,可以用默认时间初始化一次,同时在日志里打一条记录说明时间已被重置。这样设备出问题的时候,运维能快速判断是电池耗尽还是首次上电。

读时间的一致性处理在前文提过,对精度要求不高的场景可以忽略,但对于要打时间戳的记录仪,最好连续读两遍,秒值一致才采用。

5.3 我实测后最想提醒的三件事

代码怎么组织是个人风格,但下面这三件事是我踩过坑之后觉得最值得记下来的。

第一,初始化顺序别乱。先置 CE、SCLK 为低,再解除写保护,然后检查 CH 位和写时间,最后恢复写保护。如果把恢复写保护放在了时间写入之前,后面所有写操作都会被拒绝,整个初始化等于白做。

第二,清 CH 位时不要直接把秒寄存器写成 0。用 sec & 0x7F 保留当前秒值,只清最高位,这样不会把已经跑起来的时间瞬间清零。虽然秒的精度没有太高要求,但保留现场总归是好习惯。

第三,GPIO 模式选择上,我最终用了推挽输出加外部上拉的组合,配合 IO 方向切换函数。也有人用开漏输出加外部上拉,开漏模式在切输入时更省事,但推挽输出写数据时电平更干脆。两种都能用,关键是方向切换的时机要严格,写命令阶段和读数据阶段之间不要夹带无关代码,避免电平不稳定。

DS1302 驱动这件事,看起来只是“会读写寄存器”就够了,但实际上时序、位序、BCD、写保护、电源设计环环相扣。把这一套理顺之后,再去看其他三线接口或者类似的 SPI 器件,很多原理是相通的。这篇笔记算是把学习过程里的总结完整沉淀了一遍,里面每个坑都是真金白银换来的经验,希望能帮你少走点弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 1:25:59

Jedis、Lettuce、Redisson怎么选?Redis客户端选型避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/14 1:25:53

嵌入式启动流程深度解析:从复位向量到main函数的底层原理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/14 1:25:50

最大熵Patch采样器:少样本图像分类的关键区域选择方法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/14 1:25:48

C# Windows高精度毫秒定时器实现与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/14 1:25:08

发动机5大系统一次讲透!

常听人说发动机是汽车的心脏&#xff0c;这话没错。但你琢磨琢磨&#xff0c;光一颗心脏自己转得起来吗&#xff1f;转不起来。它得有一整套周边系统伺候着&#xff0c;供油、供气、降温、排毒&#xff0c;少一样都不行。好多车主跟我聊天&#xff0c;说保养不就是换机油嘛&…

作者头像 李华