尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

低功耗RTC芯片DS1308U-33+T应用详解:I²C通信、NV RAM与硬件设计要点

低功耗RTC芯片DS1308U-33+T应用详解:I²C通信、NV RAM与硬件设计要点 各位做低功耗设备、电池供电产品、嵌入式数据记录仪的朋友肯定都体会过“选RTC”这件事有多纠结市面上的实时时钟芯片一抓一大把但真正到了项目里又是要求待机电流够低、又是要求停电时数据不能丢、又是要求接口简单不占用太多引脚找一圈下来能同时满足的其实没几个。今天要聊的这颗DS1308U-33T是 Maxim现在归入 ADI出品的一颗工业级实时时钟芯片最大特点就是 250nA 级别的超低功耗、标准的 I²C 接口并且芯片内部集成了 56 字节的 NV RAM掉电非易失存储区。这篇文章我会从这颗芯片的实际使用角度出发把硬件设计、I²C 通信细节、寄存器操作、驱动代码、低功耗实测以及我踩过的坑全部过一遍给正准备用它或正在调试它的朋友一份能直接上手的参考。1. DS1308U-33T 核心参数解读低功耗、NV RAM 和工业级到底意味着什么很多朋友第一次看到这颗芯片的型号容易把它和常见的 DS1307 弄混。实际上 DS1308 可以理解成 DS1307 的“加强版”依然是 8 引脚封装、依然是 I²C 接口、依然靠外部 32.768kHz 晶振工作但它把功耗做到了更低的量级同时把 RAM 空间从 DS1307 的 56 字节完整保留下来还提供了主备电源自动切换能力。后缀里的 U 代表 USOP 封装33 代表 3.3V 工作电压版本T 是卷带包装 表示无铅 Rohs 版本。1.1 250nA 功耗参数的正确理解方式数据手册上标注的 250nA 是“典型值”可不是所有条件下的保证值。这个数值对应的测试条件是VCC1 和 VCC2 都接在 3.3V振荡器正常起振I²C 总线处于空闲状态温度 25°C 左右没有对 RAM 进行读写操作。换句话说这是芯片在“纯走时”状态下的电流消耗。实际项目里如果你的系统一直对 RTC 进行频繁读取或者 I²C 总线上拉电阻太小导致静态电流偏大整机功耗肯定不止 250nA。我自己的实测中STM32F407 通过 I²C 每秒钟读一次时间RTC 这块的整体功耗大概会上升到 2~3uA 左右包含上拉电阻的贡献。所以做低功耗设计时要把上拉电阻、总线上其它器件的漏电流一起算进去而不是只盯着芯片手册上的典型值。1.2 56 字节 NV RAM 的定位它不是 EEPROM但比 EEPROM 好用NV RAM 是这颗芯片很有价值的部分。它的本质是芯片内部的 SRAM但通过 VCC2 备份电池供电在主电源掉电时自动切换到备份电源从而保证 RAM 内容不丢失。这里要解释一个容易混淆的点NV RAM 不需要像 EEPROM 那样按页擦除也没有 100 万次写入寿命的限制读写速度和普通 SRAM 一样快唯一的条件就是备份电池不能断。对于需要频繁记录的小数据量场景比如设备运行状态、累计计数、校准时标DS1308 的 NV RAM 比外挂一颗 AT24C02 要方便得多省了一颗芯片还少一路 I²C 地址占用。1.3 工业级温度范围的实际意义DS1308U-33T 的工业级版本工作温度范围是 -40°C 到 85°C。这个范围对于消费电子可能觉得无所谓但对于户外设备、车载电子、工业控制器来说就是硬指标。我曾在 -20°C 环境下跑过这颗芯片走时精度没有明显异常晶振的温漂反而成了主要误差来源。后面在硬件设计部分会详细说晶振选型的重要性因为芯片再稳外部晶振不行也是白搭。2. 硬件电路设计要点供电拓扑、晶振匹配和上拉电阻一个都不能省硬件设计这块是 DS1308 能否长期稳定运行的基础。我在第一版打样时就是因为在晶振匹配电容上偷了懒结果板子回来后有一半芯片走时偏快教训很深刻。2.1 VCC1 与 VCC2 双电源切换电路DS1308 的供电设计很有特点它有两个电源引脚VCC1 和 VCC2。芯片内部有一个电源切换电路规则很简单VCC1 电压高于 VCC2 时芯片由 VCC1 供电当 VCC1 掉到低于 VCC2 时自动切换到 VCC2。这个逻辑保证了主电源掉电瞬间RTC 走时和 NV RAM 数据不中断。典型的接线方式有两种方式一主电源接 VCC1电池接 VCC2这种方式适合系统常年有主电源、电池只是停电备份的场景。VCC1 接 3.3V 系统电源VCC2 接 3V 纽扣电池比如 CR2032 或者可充电的 LIR2032。正常工作时VCC13.3V 高于 VCC2≈3.0V芯片用系统电源停电后VCC1 掉电芯片自动切到电池。注意如果使用不可充电的 CR2032VCC2 引脚上最好串联一个 1kΩ 左右的限流电阻防止芯片内部寄生二极管在异常情况下对电池充电。方式二主电源和备份电源都接 VCC1VCC2 接地这种方式适用于系统本身有超级电容或者电池管理电路外部已经做了完善的电源切换芯片只需要单电源工作。VCC1 接电源VCC2 直接接地。这种情况下芯片不会自动切换电源全靠外部电路保证。我这里要强调一个细节VCC2 接可充电电池时数据手册建议串一个二极管和限流电阻目的就是限制涓流充电电流。很多工程师以为 DS1308 内部有充电管理电路其实它没有纯粹是靠外部阻容来限流的。2.2 32.768kHz 晶振选型与匹配电容计算DS1308 的时基来自外部 32.768kHz 晶振芯片内部集成了振荡电路但晶振的等效串联电阻ESR和负载电容CL会直接影响起振难度和走时精度。晶振选型建议选择 ESR 低于 50kΩ 的 32.768kHz 晶振最好是专门为 RTC 设计的型号比如 Epson MC-306 或 Seiko 系列。别用随手从拆机板上薅下来的晶振参数不匹配会带来很大的走时误差。负载电容匹配计算方式是 CL (C1 × C2) / (C1 C2) Cstray。其中 Cstray 是 PCB 走线和引脚寄生电容一般在 2~5pF 左右。如果晶振要求 CL12.5pFPCB 寄生电容估算 3pF那么 C1 和 C2 各取 20pF(20 × 20) / (20 20) 3 13pF已经很接近了。但我实际调试中发现DS1308 的振荡器增益比 DS1307 高通常 C1C215pF~22pF 之间都能正常起振。关于走时精度这里说个经验32.768kHz 晶振的频率精度和温度特性决定了 RTC 的精度芯片本身只是提供一个稳定的振荡环路。室温下常见的 32.768kHz 晶振精度在 ±20ppm折算下来一天误差约 1.7 秒。如果你需要更高精度要么选温补晶振TCXO要么通过软件对晶振频率偏差进行校准——DS1308 没有数字校准寄存器所以软件校准只能靠定期对时。2.3 I²C 上拉电阻的取值逻辑I²C 总线是开漏结构SDA 和 SCL 必须有上拉电阻才能工作。DS1308 的数据手册没有直接给出推荐的上拉电阻值只规定了总线电容和通信速率的关系。这里需要结合整个总线上的挂载情况来选标准模式100kHz上拉电阻取 4.7kΩ10kΩ快速模式400kHz上拉电阻取 2.2kΩ4.7kΩ总线电容较大或挂载设备较多时上拉电阻适当减小低功耗场景下上拉电阻不能取太小因为 I²C 空闲时 SDA 和 SCL 都是高电平上拉电阻直接形成对地的静态电流。以 3.3V 供电、上拉电阻 10kΩ 计算每个引脚的静态电流就是 3.3V / 10kΩ 330uA两个脚就是 660uA这个数值比 DS1308 本身高好几个数量级。所以做极低功耗产品时上拉电阻我会取 47kΩ100kΩ同时把 I²C 通信速率降到 100kHz 以下。实测下来100kΩ 上拉、100kHz 通信速率总线波形依然能正常翻转只是上升沿稍微缓一点。如果总线长度超过 10cm还是老老实实用 4.7kΩ别为了省电牺牲稳定性。2.4 USOP-8 封装引脚定义与 PCB 布线DS1308U-33T 是 USOP-8 封装引脚间距不大手工焊接有一定难度建议直接上回流焊或者用刀头烙铁拖焊。引脚定义如下引脚号名称功能说明1X132.768kHz 晶振输入2X232.768kHz 晶振输出3VBATVCC2备份电源输入4GND地5SDAI²C 数据线6SCLI²C 时钟线7SQW/INT方波/中断输出可配置8VCCVCC1主电源输入PCB 布线时晶振的两个引脚走线要尽量短并且晶振下方不要走其它信号线最好铺一块地铜皮做隔离。DS1308 的 VCC 引脚旁边放一个 0.1uF 的陶瓷电容备份电池的 VCC2 引脚也放一个 0.1uF这两个电容要尽量靠近芯片引脚。我见过有些板子为了省空间把去耦电容放得很远结果 RTC 在系统电流波动时容易复位。3. 寄存器地图与手册上没写透的细节DS1308 的内部寄存器空间一共 64 字节地址从 0x00 到 0x3F。其中 0x00~0x06 是时间和日期寄存器0x07 是控制寄存器0x08~0x3F 是那 56 字节的 NV RAM 区。下面逐一说明。3.1 时间寄存器BCD 格式与注意事项时间寄存器采用 BCD 码Binary-Coded Decimal存储也就是每个字节的高四位表示十位低四位表示个位。比如秒寄存器里存 0x45代表 45 秒。这一点和很多国产 RTC 芯片不同国产芯片有些直接存十六进制用起来很容易搞混。各寄存器的地址和范围如下地址寄存器位7位6~4位3~0取值范围BCD0x00秒CH十位秒个位秒00~590x01分0十位分个位分00~590x02时0十位时/24h模式个位时00~23 或 01~120x03星期00星期值01~070x04日0十位日个位日01~310x05月0十位月个位月01~120x06年十位年十位年个位年00~99操作时有两个关键点第一秒寄存器0x00的最高位是 CHClock Halt位写 0 才能启动振荡器写 1 会停止振荡芯片进入省电模式。很多初学者设置的寄存器都是对的时间就是不往前走十有八九就是 CH 位被写成了 1。第二小时寄存器0x02的第 6 位用来选择 12 小时制或 24 小时制12 小时制下第 5 位是 AM/PM 标志。我建议统一用 24 小时制省去 AM/PM 转换的麻烦。3.2 控制寄存器 0x07中断输出和方波配置控制寄存器 0x07 各位定义如下位7~4涓流充电选择位TS1~TS4。DS1308 支持给备份电池进行涓流充电这 4 个位必须写成 1010 才能启用充电功能其它值都会关闭充电。充电通路上的二极管数量和限流电阻阻值也是通过这几个位配置的。位3~1RS2、RS1、RS0方波输出频率选择可以输出 1Hz、4.096kHz、8.192kHz、32.768kHz。位0SQWE方波使能位。置 1 时SQW/INT 引脚按设定的频率输出方波置 0 时该引脚输出高阻态。这个寄存器有一点不能忽略如果你用 SQW/INT 引脚做中断输入配合闹钟功能那方波输出必须关闭SQWE0否则引脚上就一直是方波信号单片机的 EXTI 会被反复触发。我调这个的时候因为没关方波中断一下进一下出不间断差点以为是代码问题。3.3 NV RAM 区域可以当普通 SRAM 用但没有页写缓冲NV RAM 区地址 0x08~0x3F共 56 字节。读写方式和普通 I²C 从设备一样没有页写缓冲理论上可以按字节连续写。但注意DS1308 的 NV RAM 是通过内部备份电源供电保持的所以它的写入次数没有限制也不会像 EEPROM 那样需要等待写周期这对频繁记录数据的场景非常友好。配合 NV RAM还有一个很实用的技巧可以用 NV RAM 的前几个字节存一个“初始化标志”上电后主控读这个标志如果发现是乱码或者特定值不对就说明备用电池耗尽过、NV RAM 数据已经丢失这时需要重新初始化时间。这个标志机制在工控设备里非常常用可以避免设备因为电池失效而显示 2000 年 1 月 1 日这种诡异时间。4. I²C 通信全流程拆解从时序到命令字节DS1308 使用标准 I²C 协议7 位设备地址是 0x68写地址是 0xD0读地址是 0xD1。接下来把读时间、写时间、读写 NV RAM 三种操作的完整命令流程过一遍顺便讲一下时序参数上的注意事项。4.1 写时间寄存器流程写时间寄存器需要两步先指定起始寄存器地址然后连续写入数据。命令流程如下主机发送起始条件START主机发送写地址 0xD08 位从机应答 ACK主机发送寄存器起始地址比如 0x00 表示从秒开始写从机应答 ACK主机连续发送 7 个字节秒、分、时、星期、日、月、年从机每收一个字节回一个 ACK主机发送停止条件STOP要注意的是虽然时间寄存器按顺序排列芯片内部的地址指针在写入后会自动加 1但如果你只写了秒寄存器其它时间和日期寄存器不会被自动清零它们会保留原来的值所以写时间时最好把 7 个字节全部写一遍避免遗留错误数据。多字节连续写的另一个坑是I²C 地址指针在到达 0x3FNV RAM 最后一个字节后如果继续写指针会回绕到 0x00。如果写时间的时候数据长度超过 7 字节会直接覆盖到 NV RAM 区域。所以上位机和 MCU 驱动里一定要严格限制写长度。4.2 读时间寄存器流程读操作有两种方式推荐用“指定地址后读”的方式主机发送起始条件START主机发送写地址 0xD0从机应答 ACK主机发送寄存器起始地址 0x00从机应答 ACK主机重新发送起始条件即重复起始 Repeated START主机发送读地址 0xD1从机应答 ACK主机连续读取 7 个字节每字节后主机回 ACK最后一个字节回 NACK主机发送停止条件STOP记住一个关键点连续读多个字节时除了最后一个字节回 NACK前面的字节都必须回 ACK。如果某个字节回了 NACK从机会认为传输结束停止发送数据。很多初学 I²C 的朋友在这里翻车读出来全是 0xFF。4.3 NV RAM 读写与时间寄存器读写没有本质区别NV RAM 的读写流程和时间寄存器完全一致只是把起始地址换成 0x08~0x3F 之间的值。注意一点DS1308 的地址指针在读写过程中会自动递增所以可以一次把 56 字节全部读出命令流程和读时间寄存器一模一样只是读取长度变成 56。这里有个小优化建议如果系统只需要在掉电前保存几个关键参数可以只写 NV RAM 的前 16 字节剩下 40 字节留着扩展用。而且 NV RAM 的写入没有 EEPROM 那种“按页分布”的限制你可以任意字节写入不会影响相邻地址的数据。4.4 I²C 时序参数和时钟延展问题DS1308 的数据手册给出了 I²C 时序参数SCL 时钟频率最高 400kHzSCL 低电平时间、高电平时间、起始保持时间、数据建立时间等都有明确要求。标准 MCU 的硬件 I²C 外设基本都能满足。但有一个隐蔽的坑DS1308 内部有少量操作需要暂时拉低 SCL 总线时钟延展 Clock Stretching比如上电初始化时。大多数单片机硬件 I²C 外设支持时钟延展但 STM32F4 系列的硬件 I²C 在从设备拉低时钟时会报超时错误。如果你用 F407 的硬件 I²C 发现偶发通信失败又排除接线问题大概率就是时钟延展处理没配好。解决办法有两个一是把 I²C 超时时间设长一些二是直接用 GPIO 模拟 I²C彻底避开这个问题。5. 基于 STM32F407 的驱动实现HAL 库硬件 I²C 和 GPIO 模拟 I²C 双版本STM32F407 是很多工程师手头最常见的 MCU拿它举例最实用。这里给出两个版本的驱动核心代码一个用 HAL 库硬件 I²C一个用 GPIO 模拟 I²C。两种方式各有优劣后面会细说。5.1 HAL 库硬件 I²C 驱动初始化与读写封装先贴初始化代码。注意 F407 的 I²C 引脚大多数在快速模式需要配置为开漏输出并外加上拉电阻HAL 库初始化代码会自动设置复用开漏模式。void MX_I2C1_Init(void) { hi2c1.Instance I2C1; hi2c1.Init.ClockSpeed 100000; // 100kHz 标准模式 hi2c1.Init.DutyCycle I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 0; hi2c1.Init.AddressingMode I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 0; hi2c1.Init.GeneralCallMode I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode I2C_NOSTRETCH_DISABLE; // 必须允许时钟延展 HAL_I2C_Init(hi2c1); }读取时间寄存器的函数#define DS1308_WR_ADDR 0xD0 #define DS1308_RD_ADDR 0xD1 uint8_t ds1308_read_reg(uint8_t reg, uint8_t *buf, uint8_t len) { if (HAL_I2C_Master_Transmit(hi2c1, DS1308_WR_ADDR, reg, 1, 100) ! HAL_OK) return 1; if (HAL_I2C_Master_Receive(hi2c1, DS1308_RD_ADDR, buf, len, 100) ! HAL_OK) return 1; return 0; } uint8_t ds1308_write_reg(uint8_t reg, uint8_t *buf, uint8_t len) { uint8_t tmp[8]; tmp[0] reg; for (int i 0; i len; i) { tmp[i 1] buf[i]; } if (HAL_I2C_Master_Transmit(hi2c1, DS1308_WR_ADDR, tmp, len 1, 100) ! HAL_OK) return 1; return 0; }注意这里 HAL_I2C_Master_Transmit 的第一个参数是 7 位地址左移 1 位后的值所以直接填 0xD0 和 0xD1而不是 0x68。HAL 库内部会自动处理 MSB 位很多新手在这里填 0x68 导致通信失败。读取时间的完整示例typedef struct { uint8_t sec; uint8_t min; uint8_t hour; uint8_t week; uint8_t date; uint8_t month; uint8_t year; } rtc_time_t; uint8_t ds1308_get_time(rtc_time_t *t) { uint8_t buf[7]; if (ds1308_read_reg(0x00, buf, 7) ! 0) return 1; t-sec ((buf[0] 4) 0x07) * 10 (buf[0] 0x0F); // 秒去掉CH位 t-min ((buf[1] 4) 0x07) * 10 (buf[1] 0x0F); t-hour ((buf[2] 4) 0x03) * 10 (buf[2] 0x0F); // 24小时制 t-week buf[3] 0x07; t-date ((buf[4] 4) 0x03) * 10 (buf[4] 0x0F); t-month ((buf[5] 4) 0x01) * 10 (buf[5] 0x0F); t-year ((buf[6] 4) 0x0F) * 10 (buf[6] 0x0F); return 0; }BCD 转十进制时注意秒寄存器最高位是 CH 位必须先把最高位移出去再计算。上面代码通过(buf[0] 4) 0x07把 CH 位屏蔽掉这样即使 CH 位是 1 也不会影响结果。5.2 GPIO 模拟 I²C为什么在 DS1308 上我更推荐这种方式为什么推荐模拟 I²C因为 F4 系列硬件 I²C 有个著名的问题总线上有毛刺或者从机响应不及时硬件状态机就会卡死在 BUSY 状态必须复位外设才能恢复。这个问题在 DS1308 上我遇到的概率不低尤其是在带电插拔或者系统上电瞬间。模拟 I²C 的代码网上很多我这里给出一套精简可靠的实现重点在于起始/停止时序的延时控制。注意代码里的 delay_us 要保证在标准模式下 SCL 半周期至少 5us对应 100kHz。#define SDA_GPIO GPIOB #define SDA_PIN GPIO_PIN_7 #define SCL_GPIO GPIOB #define SCL_PIN GPIO_PIN_6 void i2c_start(void) { SDA_HIGH(); SCL_HIGH(); delay_us(5); SDA_LOW(); delay_us(5); SCL_LOW(); } void i2c_stop(void) { SDA_LOW(); SCL_HIGH(); delay_us(5); SDA_HIGH(); delay_us(5); } uint8_t i2c_write_byte(uint8_t data) { for (int i 0; i 8; i) { if (data 0x80) SDA_HIGH(); else SDA_LOW(); data 1; SCL_HIGH(); delay_us(3); SCL_LOW(); delay_us(3); } // 接收 ACK SDA_INPUT(); SCL_HIGH(); delay_us(3); uint8_t ack (SDA_READ() 0) ? 0 : 1; SCL_LOW(); SDA_OUTPUT(); return ack; } uint8_t i2c_read_byte(uint8_t ack) { uint8_t data 0; SDA_INPUT(); for (int i 0; i 8; i) { data 1; SCL_HIGH(); delay_us(3); if (SDA_READ()) data | 0x01; SCL_LOW(); delay_us(3); } // 发送 ACK 或 NACK SDA_OUTPUT(); if (ack) { SDA_HIGH(); // NACK } else { SDA_LOW(); // ACK } SCL_HIGH(); delay_us(3); SCL_LOW(); SDA_LOW(); return data; }模拟 I²C 的好处不仅是绕开 F4 的硬件 bug还在于你可以随时调整时序来适配不同的从设备甚至可以在系统休眠时完全释放 SDA 和 SCL 引脚减少漏电流。5.3 动态地址扫描快速确认芯片是否在线调试 I²C 设备时第一步永远是确认芯片挂在总线上并且地址正确。写一个简单的扫描函数遍历 0x08~0x77 的所有 7 位地址发送写地址看有没有 ACK 回应void i2c_scan(void) { for (uint8_t addr 0x08; addr 0x78; addr) { uint8_t tmp 0x00; i2c_start(); uint8_t ack i2c_write_byte((addr 1) | 0x00); i2c_stop(); if (ack 0) { printf(Found device at 0x%02X\r\n, addr); } } }DS1308 正常工作时扫描结果会显示 0x68。如果扫描不到优先检查电源、地、SCL/SDA 有没有接反、上拉电阻是否焊接再用示波器看 SCL 波形。我见过不少“芯片坏了”的案例最后发现是 SDA 和 SCL 在 PCB 上画反了。6. 一个完整应用场景掉电事件记录器的软硬件实现说了这么多原理和代码用一个实际案例把这些串起来。假设我们要做一个工控设备的事件记录模块要求在系统断电瞬间把当前时间和运行状态保存到 NV RAM下次上电后主控可以读出上次断电的时间和状态。6.1 硬件部分电源监测与掉电检测实现这个功能除了 DS1308还需要一个掉电检测电路。最简单的方案是用一个电压比较器或者直接用 STM32F407 的内部 ADC 检测 3.3V 电源电压。更可靠的方案用一颗电压监测芯片比如 TPS3839当电源电压跌到阈值比如 2.93V时立即输出低电平触发 STM32 的外部中断。在中断服务函数里主控需要在电容放电完毕前的几个毫秒内完成时间读取和 NV RAM 写入。这里要注意DS1308 的 NV RAM 写入是即时生效的只要 VCC1 还在芯片工作电压以上写入就没有问题。实测在 3.3V 掉电到 2.5V 的过程大约有 5ms足够写完 16 字节数据了。6.2 软件部分断电中断里读时间和写 NV RAM中断服务函数的核心逻辑void EXTI0_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin GPIO_PIN_0) { // 电源掉电进入紧急保存流程 rtc_time_t t; uint8_t buf[16]; ds1308_get_time(t); // 读出当前时间 buf[0] 0xA5; // 标志字节固定值 buf[1] t.sec; buf[2] t.min; buf[3] t.hour; buf[4] t.date; buf[5] t.month; buf[6] t.year; buf[7] get_system_status(); // 保存设备状态 // 剩余字节保存其它运行参数 ds1308_write_reg(0x08, buf, 8); // 写入 NV RAM } }上电复位后主控读取 NV RAM 的 0x08 字节检查标志是否等于 0xA5。如果等于说明上次掉电保存有效然后把时间提取出来记录到日志中如果标志不对说明掉电太快、NV RAM 没来得及写入或者电池耗尽导致数据丢失。这个场景充分发挥了 DS1308 的两个核心特性I²C 接口写入速度快不用像 I²C EEPROM 那样等写入周期NV RAM 数据由备份电池保持系统完全断电也不丢。如果换成普通 RTC 芯片加外部 EEPROM 的方案掉电瞬间那几毫秒根本不够完成 EEPROM 的页写入。7. 实测经验与常见问题排查链路实战中总会碰到各种奇奇怪怪的问题这里按排查顺序整理我在 DS1308 上遇到过的几个经典问题每个都值得记录下来。7.1 问题一I²C 通信偶尔失败复位后恢复现象系统长时间运行后读取 DS1308 时间偶尔返回错误重新初始化 I²C 外设后恢复正常。排查链路第一步用示波器抓取 SDA 和 SCL 波形重点看通信失败瞬间是否有总线冲突或毛刺。第二步检查 I²C 外设的忙标志位发现失败时 I2C1-SR2 的 BUSY 位置 1。第三步怀疑时钟延展问题因为 DS1308 上电内部初始化时可能短暂拉低 SCL。结论硬件 I²C 在从设备时钟延展期间如果超时设置太短HAL 库会直接返回超时错误而外设状态机没复位导致后续通信一直失败。解决办法就是初始化时把 NoStretchMode 设为 DISABLE同时把超时时间从 100ms 加大到 200ms。额外发现还有一个隐藏诱因——SDA 和 SCL 走线太长而且没有包地总线上耦合了电源纹波。PCB 改版后把 I²C 走线缩短并加了串联电阻22Ω问题几乎不再出现。7.2 问题二走时偏差特别大一天能差好几分钟现象同一批板子一部分走时准确一部分每天快 3~5 分钟。排查链路先用频率计测量晶振引脚发现芯片实际输出的时钟频率是 32.785kHz 而不是标准的 32.768kHz。检查晶振匹配电容发现这批板子贴错电容贴了 33pF 导致振荡频率偏高。用公式估算负载电容从 12.5pF 变成 33pF频偏约为 1050ppm对应每天快约 90 秒。但实测快 3 分钟说明还有其它因素。继续查发现晶振下方的 PCB 走线有高频数字信号穿过时钟信号被干扰。改版后把晶振下方的走线清空同时把匹配电容换成 15pF走时偏差降到每天 2 秒以内。结论DS1308 的走时精度瓶颈在外部晶振不在芯片本身。晶振选型、匹配电容、PCB 布线三者同等重要。批量生产时每一片晶振的实际频率都有离散性有必要的话可以在产线做整机校准。7.3 问题三断电后时间归零NV RAM 数据全部丢失现象设备断电重启时间变成 2000 年 1 月 1 日NV RAM 里的标志字节也变成乱码。排查链路先确认备份电池是否接好万用表量 VCC2 引脚电压发现是 0V。检查电池座发现电池弹片氧化导致接触不良。换一个新电池座后正常。但这个问题的深层原因是DS1308 的 VCC2 引脚有最小电压要求低于这个值 NV RAM 数据无法保持。如果 VCC2 完全没电走时和 RAM 数据都会丢。结论硬件设计时VCC2 引脚最好加一个 1~10uF 的电容一方面过滤电池接触瞬间的抖动另一方面在更换电池时提供短暂的保持时间。此外上电初始化时一定要做“标志字节判断”确保电池失效后设备能自动恢复到默认状态而不是带着脏数据运行。7.4 问题四低功耗模式下整机电流比预期大了很多现象系统休眠时整机电流实测 20uA但设计目标是 5uA。排查链路断开传感器、关闭 LED电流没变化怀疑是 DS1308 或 I²C 上拉电阻的问题。试着把 I²C 上拉电阻拆掉一个电流立刻降了 10uA。再量 SDA 电平发现 STM32F407 的 IO 在休眠时输出低电平导致上拉电阻和 IO 内部寄生二极管形成通路。结论低功耗设计里STM32 和 DS1308 共用 I²C 总线时MCU 休眠前必须把 SDA 和 SCL 引脚配置为模拟输入或者高阻态避免电平冲突。同时上拉电阻接在 VCC 和总线之间如果总线被拉低就会有持续电流流过上拉电阻。解决办法是在每个 I²C 引脚到 MCU 之间串联一个 1kΩ 电阻MCU 休眠时把引脚设为高阻总线上拉电阻会把 SDA/SCL 拉到高电平两边都不会产生漏电。DS1308U-33T 这个方案我已经在几个正式项目里跑了快两年整体评价是功能不大不小刚刚好I²C 接口成熟稳定56 字节 NV RAM 解决了很多实时存储需求超低功耗在电池产品里很亮眼。唯一需要多花心思的还是晶振选型和 PCB 布局这也是所有外置晶振 RTC 的共性课题。如果你正在评估这颗芯片建议画板前先按我上面说的供电方案和晶振匹配做一轮计算可以省掉不少后续调试的时间。
返回列表