ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

单总线协议与DS2431 EEPROM:从时序原理到驱动实战

2026/10/3 1:34:46 拓冰建站 浏览量
单总线协议与DS2431 EEPROM:从时序原理到驱动实战 做过硬件的人都懂板子上为了存几个字节参数专门拉四条线出来伺候一颗SPI EEPROM属实有点亏I2C虽然省到两条但在一些接口资源紧张的场景里还是嫌多。我第一次在项目里看到DS2431这颗芯片时就觉得单总线协议1-Wire bus这名字起得实在——它真的只用一根数据线就能把读、写、供电、设备识别这些事全包了。这篇文章就拿DS2431做例子把单总线协议的时序原理、驱动代码、实操流程以及我踩过的坑一次性讲透。不管你是刚接触1-Wire的新手还是已经在用DS2431但总被奇怪故障折磨的老手都能在这里找到能直接拿去用的东西。单总线协议一根数据线如何完成双向通信1.1 为什么会有单总线这种“抠门”协议单总线协议是达拉斯半导体现在并到Maxim搞出来的一套低速通信方案核心思路就一句话用一根开漏的数据线完成主机和从机之间的双向数据传输。这根线平时被上拉电阻拉到高电平所有设备都是开漏输出谁想发信号就把线拉低不拉就是高。听着有点像I2C但I2C至少还要一根时钟线单总线连时钟都省了时钟信息靠的是拉低持续的时间长短来体现。这种设计自然牺牲了速度常规速率也就15.4kbps左右过驱动模式能到大概125kbps。说实话用它传大量数据肯定不现实但用来传几个字节的配置信息、序列号、校准参数那是绰绰有余。单总线最大的优势是节省引脚和走线在传感器探头、防伪标签、电池组这类空间受限、连线越少越好的场合一根线就能把活干完线路可靠性也更高。代价就是时序要求严格主机侧的程序必须精确卡好时间不像UART那样有硬件模块帮你处理帧格式。1.2 复位、存在脉冲与时隙三张“暗号表”单总线所有的通信都由主机发起从机只能被动响应。整个协议的基础是三个动作复位脉冲、存在脉冲以及读写时隙。主机先把总线拉低480us以上然后释放这就是复位脉冲。从机检测到这个低电平后等待一小段时间会自己把总线拉低60到240us这就是存在脉冲相当于从机跟主机打招呼说“我在呢”。主机在释放总线后的大约60us到240us窗口期内去采样总线电平读到一个被拉低的状态就知道总线上有设备在线。复位之后就是时隙通信。每个时隙至少60us用来传一个bit。写1时隙主机拉低总线1到15us然后释放让上拉电阻把总线拉回高电平持续到整个时隙结束。写0时隙主机直接拉低总线持续整个时隙一般要超过60us。读时隙稍微复杂一点主机先拉低总线1到15us然后立即释放接着在时隙的前半段采样总线电平。如果从机想传0它会主动把总线拉低想传1它就让总线保持高电平。主机必须在从机驱动总线的有效窗口内完成采样所以采样点通常放在拉低释放后的10us左右。这套机制本质上就是靠“低电平持续时间”来编码类似两个人用一根手电筒在黑暗里打暗号短闪是1长按是0而且每次必须由发起方先问一句“在不在”对方回应了再开始。这里有个特别需要注意的点所有时序都以主机为基准从机的时钟是从总线上的边沿提取的所以主机侧的延时精度直接决定了通信稳定性。用delay函数的时候编译器优化级别、MCU主频配置、中断响应都可能让延时偏差到完全无法通信的程度。1.3 总线上的设备寻址64位ROM码一条单总线总线上可以挂多个从机因为每个从机芯片出厂时都烧录了一个唯一的64位ROM码。这64位里低8位是家族码DS2431的家族码是0x2D中间48位是芯片唯一序列号高8位是CRC校验。主机通过ROM命令来选择跟哪个设备通信常用命令有Skip ROM0xCC、Read ROM0x33、Match ROM0x55和Search ROM0xF0。如果总线上只挂一个DS2431直接发Skip ROM最省事表示“不管你是谁后面的命令都给你”。如果挂了多个设备主机就得用Search ROM命令配合搜索算法逐个找出总线上所有设备的ROM码。这个搜索算法本质上是在64位ROM码的位树上做深度优先遍历主机每发一位总线上所有设备会同时回复两个bit一个是自己ROM码当前位的值另一个是它的反码。如果多个设备在这一位上的值不同就会出现“冲突”主机根据冲突情况决定走哪条分支就这样逐位确定出一台设备的完整ROM码然后把它隔离再继续搜下一个。我之前手写过一次这个算法第一次跑通的时候感觉确实巧妙但平时真没必要重复造轮子Maxim官方应用笔记里就有现成实现直接参考移植就行。DS24311024位的“小铁片”能干什么2.1 DS2431的存储结构与核心特性DS2431是一颗1024位128字节的1-Wire EEPROM存储阵列分为16页每页8字节地址范围0x00到0x7F。这颗芯片最吸引人的地方是接口简单TO-92封装只有三根引脚一根地线、一根数据线、一根电源线甚至在某些场景下还能用寄生供电两根线就能工作。工作电压范围2.8V到5.25V工业级版本支持-40°C到85°C在很多环境恶劣的现场设备里都能稳定运行。它的擦写寿命标称在10万次左右数据保持时间在工业级温度条件下也能满足常规产品需求。跟普通Flash比DS2431虽然容量小但胜在字节访问灵活、接口极其简单、封装小。我在一个仪表项目里用它存传感器出厂校准系数和累计运行时间一颗芯片加一个电阻就解决了所有数据存储问题PCB上几乎不占地方。这里有个容易忽略的点DS2431虽然叫“EEPROM”但它的操作流程和普通I2C EEPROM不太一样。直接写存储区的命令是没有的所有写入必须先写到一个暂存器Scratchpad里校验无误后再复制到EEPROM阵列。这个设计其实是为了保证写入可靠性防止在写EEPROM过程中掉电导致存储区数据半新半旧。所以对DS2431的编程本质上是在操作“暂存器EEPROM”两套存储空间。2.2 用在哪校准数据、身份识别、耗材防伪DS2431在工业现场最常见的用途是存校准数据和身份信息。设备出厂前把传感器零点、增益系数、量程范围写进EEPROM之后主控每次上电读出来做补偿这样即使换了传感器探头只要探头里带一颗DS2431主机插上就能自动识别并加载对应参数。这比把参数存在主机Flash里要灵活得多也方便售后更换配件。另一个典型场景是耗材防伪和配件管理。打印机的墨盒、医疗设备的试剂包、电池组的身份模块都可以嵌入一颗DS2431。主机通过单总线协议读取芯片ROM码和内部加密数据就能判断耗材是否原装、剩余寿命多少。因为ROM码是全球唯一、不可更改的每颗DS2431天生就是一个不可复制的“身份证”。我之前做过一个电池组项目每节电池模组里放一颗DS2431记录电芯编号、出厂日期、循环次数整组电池的履历清清楚楚。还有一类应用是利用DS2431的页写保护功能做一次性配置。设备量产时写入序列号和关键配置然后把对应页保护起来防止现场被意外改写。这样既能保证数据安全又不需要增加额外成本。2.3 页写保护与EPROM结构的坑DS2431每个8字节页都有一个对应的页保护位可以独立设置。保护操作通过向保护位写入特定的命令序列完成底层是EPROM结构。这里必须提醒一句EPROM结构的保护位和普通Flash的写保护不一样一旦置位想再改回来非常困难不是软件里清一个寄存器就能搞定的。很多工程师第一次用这个功能时习惯性地以为写保护就像I2C EEPROM里的状态寄存器一样随时可以解锁结果一锁就把自己锁死了只能换芯片。所以我的建议是在产品开发阶段尽量不要过早启用页保护真要测试保护功能也要专门留一个测试命令入口并且批量生产前反复确认保护位设置流程无误。实际项目里最稳妥的做法是把需要频繁更新的数据比如运行时长和只能写一次的数据比如序列号分页存放序列号那页设置保护运行时长那页保持可写。这个规划在软件设计初期就要定好别等写固件写到最后才想起保护位的事。从零写驱动MCU读写DS2431全流程3.1 硬件连接开漏、上拉和供电方式硬件电路非常简单主机任意一个GPIO接到DS2431的DQ引脚DQ引脚通过一颗上拉电阻接到电源典型阻值是4.7kΩ。DS2431的VCC引脚可以直接接电源GND与主机共地。数据线距离短、走线干净的时候上拉电阻用4.7k到10k都能正常工作如果总线比较长或者挂了多个从机寄生电容会变大上拉电阻就要适当减小我一般控制在1k到4.7k之间。这里有个关键点必须强调主机的GPIO必须配置成开漏输出模式或者至少工作在“输出低电平和输入采样”来回切换的模式。绝对不能用推挽输出高电平去驱动DQ线。原因是单总线是线与逻辑如果主机强推高电平而从机想拉低总线两个输出就会“打架”轻则通信失败重则把芯片搞坏。用开漏输出配合上拉电阻主机不拉低时总线自然回到高从机才有机会把总线拉低来回应主机的读时隙。如果DS2431采用寄生供电模式VCC引脚直接接地或者悬空接个小电容芯片正常工作时的能量主要靠DQ线的高电平通过内部二极管给电容充电。这种模式确实能省掉一根电源线但写入EEPROM时需要的电流会明显增大普通上拉电阻根本供不过来必须由主机在复制暂存器阶段提供强上拉比如把GPIO临时切成推挽输出高电平持续驱动总线。我做项目时只要空间允许都会把VCC老老实实接上电源省这点线换来的是整个通讯链路的稳定性。3.2 底层时隙函数实现写驱动之前先把基础函数准备好。这里用C语言实现GPIO操作封装成宏方便移植到不同MCU。#define DQ_PORT GPIOA #define DQ_PIN GPIO_PIN_0 #define DQ_LOW() HAL_GPIO_WritePin(DQ_PORT, DQ_PIN, GPIO_PIN_RESET) #define DQ_HIGH() HAL_GPIO_WritePin(DQ_PORT, DQ_PIN, GPIO_PIN_SET) #define DQ_READ() HAL_GPIO_ReadPin(DQ_PORT, DQ_PIN)先说明一点用HAL库虽然方便但函数调用开销大延时精度很难稳定。真正产品级的驱动我建议直接操作寄存器这里为方便阅读还是用宏封装。对外层的delay_us不同平台实现方式不一样比如STM32可以用DWT计数器或者简单用SysTick。复位和存在脉冲检测函数如下uint8_t ow_reset(void) { uint8_t presence; DQ_LOW(); // 主机拉低总线 delay_us(500); // 复位脉冲500us DQ_HIGH(); // 释放总线 delay_us(60); // 等待从机响应 presence DQ_READ(); // 采样存在脉冲低电平表示有设备 delay_us(400); // 等待复位时隙结束 return presence; // 返回0表示检测到设备1表示无设备 }然后是写一位和读一位void ow_write_bit(uint8_t bit) { if (bit) { DQ_LOW(); delay_us(6); // 短拉低 DQ_HIGH(); // 释放开始写1时隙 delay_us(64); // 补足时隙长度 } else { DQ_LOW(); delay_us(60); // 持续拉低整个时隙写0 DQ_HIGH(); delay_us(10); // 恢复时间 } } uint8_t ow_read_bit(void) { uint8_t val; DQ_LOW(); delay_us(6); // 读时隙起始拉低 DQ_HIGH(); // 释放总线让从机驱动 delay_us(9); // 在有效窗口内采样 val DQ_READ(); delay_us(55); // 补足时隙 return val; }读写一个字节就是按位循环void ow_write_byte(uint8_t data) { for (uint8_t i 0; i 8; i) { ow_write_bit(data 0x01); data 1; } } uint8_t ow_read_byte(void) { uint8_t data 0; for (uint8_t i 0; i 8; i) { data 1; if (ow_read_bit()) { data | 0x80; } } return data; }底层函数的核心就是时序窗口。写1时隙拉低时间控制在6us左右别太长太长就会被从机误判成写0采样点放在读时隙开始后的9到15us之间等从机稳定输出后再读。我自己的习惯是先用逻辑分析仪抓一遍波形确认每个时隙的实际电平时长和代码预期一致再去做上层协议这样可以省掉后面很多排查时间。3.3 读存储器与写暂存器流程有了底层函数读写DS2431就顺理成章了。读存储器命令是0xF0后面跟两个字节的目标地址低地址在前之后就可以连续读取数据。uint8_t ds2431_read_memory(uint8_t addr, uint8_t *buf, uint8_t len) { if (ow_reset() ! 0) // 复位检测设备 { return 1; } ow_write_byte(0xCC); // Skip ROM ow_write_byte(0xF0); // Read Memory ow_write_byte(addr 0xFF); // TA1 ow_write_byte(addr 8); // TA2 for (uint8_t i 0; i len; i) { buf[i] ow_read_byte(); } return 0; }写入流程分两步。第一步用0x0F命令把数据写到暂存器第二步读暂存器校验第三步用0x55命令把暂存器内容复制到EEPROM。这里放一个简化版的写入函数uint8_t ds2431_write_page(uint8_t page, const uint8_t *buf) { uint8_t addr page * 8; if (page 15) // 只有16页 { return 2; } if (ow_reset() ! 0) { return 1; } ow_write_byte(0xCC); // Skip ROM ow_write_byte(0x0F); // Write Scratchpad ow_write_byte(addr 0xFF); // TA1 ow_write_byte(addr 8); // TA2 for (uint8_t i 0; i 8; i) { ow_write_byte(buf[i]); // 8字节数据写入暂存器 } // 这里省略读暂存器(0xAA)回读校验流程产品代码务必补上 if (ow_reset() ! 0) { return 1; } ow_write_byte(0xCC); // Skip ROM ow_write_byte(0x55); // Copy Scratchpad ow_write_byte(addr 0xFF); ow_write_byte(addr 8); // 复制阶段需要强上拉持续给总线供电 STRONG_PULLUP_ON(); delay_ms(10); STRONG_PULLUP_OFF(); return 0; }为什么写入要先写暂存器再复制因为EEPROM在写入过程中如果电源不稳或者通信被中断阵列里的数据可能写到一半就停了造成整页数据损坏。有了暂存器这个中间缓冲写入EEPROM之前可以先完整校验一遍数据确认无误再执行复制动作。产品级代码里读暂存器回读校验这步绝对不能省尤其是在现场供电条件比较差的设备中。另外注意DS2431的写操作是以页为单位的一页8字节地址必须8字节对齐。如果只想改一页中的几个字节也需要先把整页读到RAM修改后再整页写回去。这是很多第一次用的人容易忽略的地方。3.4 强上拉EEPROM写入能不能成功的关键前面提到复制暂存器到EEPROM时需要强上拉这一步直接决定写入是否成功。普通通信状态下4.7kΩ上拉电阻提供的电流足够维持芯片工作但写EEPROM的瞬间电流需求会明显上升如果总线只靠一个弱上拉电压会被拉低到芯片无法维持正常工作的程度写入过程就会失败。典型表现是读操作一切正常写操作看起来命令都发出去了但读回数据发现要么全是0xFF要么只有部分字节写入成功。强上拉的实现方式有两种。第一种是硬件上用MOS管把数据线临时接到电源主机在复制暂存器阶段通过另一个GPIO控制MOS管导通第二种更常见软件上把DQ引脚从开漏输出临时切换成推挽输出高电平持续10ms左右然后再切回开漏。我用STM32的时候复制阶段就把GPIO模式切到推挽输出并置高延时10ms后再切回开漏。一开始我图省事没加这一步结果10次写入能成功8次剩下2次丢数据查了半天最后用示波器抓到EEPROM写入瞬间总线电压掉到2V以下才反应过来是强上拉的问题。延时时间我建议保守一点10ms是基于数据手册指标并留了足够裕量的值。有些资料写5ms就够但考虑到电源波动和芯片个体差异多等几毫秒谁也不会催你。常见问题与排查技巧实录4.1 搜不到设备的5个原因DS2431最让人头大的问题就是“找不到设备”ow_reset返回值永远是1。结合我的调试经历最常见的原因有五个症状可能原因解决办法复位后采样到高电平上拉电阻没接或阻值过大确认DQ引脚有4.7kΩ左右上拉到VCC复位后采样到高电平GPIO配置成推挽输出改成开漏输出复位后采样到高电平延时函数偏差过大用示波器对比波形校准延时复位后采样到高电平引脚复用没配置确认GPIO模式、AF位正确有设备但Read ROM全FF数据线过长、走线干扰缩短线距、降低上拉电阻、必要时降低速率排查的时候别再干瞪眼看代码了建议直接上逻辑分析仪。只看复位脉冲和存在脉冲这一段波形5秒钟就能判断问题在硬件还是软件。复位脉冲低电平宽度够不够、释放后有没有出现存在脉冲、采样点是不是落在存在脉冲区间内这三个信息一眼就能看出来。4.2 数据偶发错乱的排查方向比起彻底不通“有时候能读到有时候读不到”这种问题更折磨人。我总结下来这类偶发故障大概率出在三个地方时隙采样点太靠后、通信过程中被中断打断、总线时序边沿被寄生电容拖缓。时隙采样点太靠后是新手最容易犯的错。很多人写读位函数时拉低释放后等太久才去读总线结果从机已经释放总线了读到的全是高电平。正确做法是把采样点放在读时隙开始后的9到15us之间。如果你用逻辑分析仪看波形会发现从机拉低总线的时间窗口很短主机必须卡在那个窗口里采样。中断打断也是个经典坑。单总线的时隙是微妙级的如果通信过程中来了一个高优先级中断导致某个时隙的延时从60us变成500us整个通信时序就乱了。解决办法是在读写DS2431的过程中关中断或者把这段代码跑在临界区里。最狠的做法是直接在时隙延时里用while循环死等配合关闭调度器保证时序不被抢占。总线过长或者环境干扰大的时候边沿会变缓从机采样到的电平状态就不稳定。这种问题最直接的表现是短距离调试一切正常一接到现场长线缆上就时不时出错。对策是降低通信速率、加大复位脉冲和时隙的冗余时间或者在线缆上并一个小电容滤波但电容不能太大太大反而会把边沿拖得更慢。4.3 中断、延时和逻辑分析仪我的调试心得最后分享几个我用真金白银换来的经验。第一驱动层延时函数不要用那种会被编译器优化掉的空循环也不要依赖不精确的HAL_Delay这类的毫秒级接口。微秒级延时要么配置一个硬件定时器要么用处理器内核的DWT计数器精度有保证。调试STM32时我直接把DWT的CYCCNT使能用主频周期数换算微秒非常稳定。第二逻辑分析仪是我调试单总线最值得的投资。普通几十块钱的8通道逻辑分析仪采样率能到24MHz抓1-Wire信号绰绰有余。抓波形的时候把采样率调高一点不然时隙边缘会失真反而误导你。我一般抓完波形会导出来放大看每个时隙的宽度跟数据手册里的参数一一对照。第三产品代码里一定要给读操作加超时重试机制。单总线是异步通信没有硬件错误标志一旦时序被干扰读回来的数据可能是错的不代表通信失败。最稳妥的做法是读数据前先读CRC校验字节DS2431的ROM码自带CRC数据传输过程也可以额外计算CRC或者简单校验和。如果校验失败就重新复位、重新读取连续失败几次再报错。这样能挡掉绝大多数偶发故障。我在实际项目里用DS2431做设备参数存储前后迭代了三版固件才把驱动稳定下来。最深刻的体会是单总线协议入门很容易一两个小时就能写出能跑的读写函数但它对时序、供电、环境干扰都相当敏感真正到了现场环境考验的都是你工程师的基本功。硬件上把上拉和供电做好软件上把延时和采样点校准再用逻辑分析仪验证每个时隙这套流程走完DS2431能给你省下的空间和精力绝对值得你耐心的投入。最后再说一个小技巧如果你在量产产线上遇到一片找不到的芯片别急着怀疑芯片坏了先看看是不是有人把DQ和VCC焊反了这错误我在小批量试产时见过不止一次。