简介:DS2431完整驱动是一套基于单总线协议的轻量级驱动代码,面向嵌入式开发者和物联网硬件工程师,旨在快速集成DS2431芯片的存储读写与初始化操作。该驱动将底层时序控制、命令发送与数据校验封装在简洁的API中,用户只需将DS2431.c和DS2431.h两个源文件添加至工程,并指定连接端口即可完成调用,无需关心复杂的总线协议细节,大幅缩短开发调试周期;同时提供独立的读写接口,便于按需读取或修改芯片内部数据。压缩包内文件总数仅为2个,包含一个C源文件与一个对应的头文件,整体大小约6KB,代码结构紧凑,可在Keil、IAR等常见MCU开发环境中直接编译使用。驱动还提供了对芯片非易失性存储区域的访问支持,适用于设备标识、校准参数等需掉电保留的数据管理场景,在智能终端、工业控制与物联网节点中均有实用价值。截至目前,该资源已有2644人学习下载,适合具备一定单片机基础、正在评估或使用DS2431器件的工程师参考借鉴。 做过产品序列号管理、设备身份识别,或者需要在断电后保存校准参数的嵌入式朋友,对 DS2431 这颗芯片应该不陌生。它是 ADI(原 Maxim)出品的一款 1-Wire 单总线 EEPROM,容量 1Kbit,封装小、成本低,只靠一根数据线和地线就能完成通信,非常适合用在设备身份识别、校准数据存储、耗材防伪这类场景。这篇文章不打算复述芯片手册,而是把我从零写的一套 DS2431 完整驱动摊开讲清楚,包含 1-Wire 协议时序、可移植的 C 代码、写 EEPROM 时容易翻车的细节,以及实际调试中踩过的坑。无论你用 STM32、GD32 还是其它 MCU,只要弄懂了这套设计思路,基本可以无缝抄作业。
先说清楚一件事:这里的“驱动”不是电脑上那种 CH340、CP2102 串口驱动,而是 MCU 里的底层外设驱动。很多人搜 DS2431 驱动搜到的是串口安装包,那是两码事。下面进入正题。
1. 为什么选 DS2431:一颗 1-Wire EEPROM 能解决什么问题
1.1 DS2431 的核心参数与硬件特点
DS2431 的存储空间是 1024 bit,也就是 128 字节,被分成 4 页,每页 32 字节。它最大的特点是所有通信只走一根数据线 DQ,加上地线 VSS 就够用了,而且这颗芯片既支持外部供电(2.8V~5.25V),也支持寄生供电——直接从数据线上“偷电”工作。典型待机电流只有 1uA 左右,对于电池供电的设备来说非常友好。
每一颗 DS2431 出厂时都刻录了一个 64 位的唯一 ROM ID,其中包含 8 位家族码、48 位序列号和 8 位 CRC 校验码。这意味着每颗芯片都有全球唯一的身份标识,不需要外部单片机也能做设备认证。另外它的每一页还带独立的写保护位 WP 和 EPROM 仿真模式位 EP,可以用来做一次性写入或者防篡改处理。
1.2 典型应用场景:从防伪到校准存储
我在实际项目里见过并且自己用过几种典型玩法:
第一类是设备身份认证。比如仪器仪表在出厂前把序列号、型号、生产日期写进 DS2431,之后上位机通过 1-Wire 转 USB 工具读取这颗芯片的 ID 和数据,就能快速识别设备身份,防止第三方假货混入。
第二类是校准参数存储。传感器设备在产线校准时会把校准系数、温漂补偿值写进去,设备每次上电时读取这些参数。因为 DS2431 是 EEPROM,断电不丢数据,也比外挂 SPI Flash 省 GPIO。
第三类是耗材防伪。打印机墨盒、医疗耗材、电池组里面焊一颗 DS2431,设备通过读取芯片里的加密信息和 ID 来判断耗材是否原装、剩余寿命多少。
这颗芯片的容量不大,但胜在接口极简、稳定性好、驱动逻辑一旦吃透就很可控。相比用现成的 OneWire 库,自己写驱动的好处是代码量小、便于裁剪,还能在关键节点做定制化处理。
2. 1-Wire 协议机制:驱动必须吃透的底层时序
2.1 硬件连接与上拉电阻
1-Wire 总线本质上是一条开漏信号线,所有设备共用一个 DQ 节点,不通信时总线处于高电平。MCU 的 GPIO 必须配置成开漏输出模式,并在 DQ 线上接一个上拉电阻。电阻值的选择很关键:标准速率下典型值是 4.7kΩ,如果总线较长或者挂载设备较多,可以降到 2.2kΩ;但不要用 10kΩ 以上的电阻,否则上升沿太慢,时隙容易出错。
如果使用寄生供电模式,要注意 DS2431 在 EEPROM 写操作期间需要较大的电流,总线必须有足够的上拉能力,否则写入会失败。我的建议是:只要条件允许,尽量用外部供电,省掉一堆麻烦。后面我会专门讲寄生供电踩坑的事。
2.2 五种时隙:复位、写 0、写 1、读、等待应答
1-Wire 协议没有独立的时钟线,所有时序都是通过 GPIO 拉低、释放的宽度来定义的。主机和从机之间通信的最小单位是“时隙”,每个时隙大约 60us~120us。时序参数不算复杂,但必须精确,尤其是复位和读时隙。
| 时序类型 | 参数要求 | 说明 |
|---|---|---|
| 复位脉冲 | 主机拉低 480us~960us 后释放 | 用于初始化总线 |
| 应答脉冲 | 从机拉低 60us~240us | 从机存在信号,主机释放后约 60us 采样 |
| 写 1 时隙 | 拉低 1us~15us,然后释放 | 剩余时间保持高电平 |
| 写 0 时隙 | 拉低 60us~120us 整个时隙 | 期间不允许释放总线 |
| 读时隙 | 拉低 1us~6us 后释放 | 主机需在释放后 15us 内完成采样 |
这里有一个很多新手容易搞错的地方:读时隙和写 1 时隙的起始动作看起来很像,都是主机拉低一小段时间然后释放。但区别在于,读时隙中主机会在释放后主动去采样总线电平,而写 1 时隙中主机并不关心总线状态。所以代码里读时序一定要在拉低释放后的极短窗口内读 GPIO,不能等到时隙快结束了才读。
2.3 ROM 命令与存储命令:完整读写流程
1-Wire 总线可以挂多个 DS2431,靠 64 位 ROM ID 区分设备。访问设备时,主机先发送复位脉冲,然后发送 ROM 命令。最常用的有:
- Read ROM(0x33):读取总线上唯一设备的 ROM ID
- Skip ROM(0xCC):不匹配地址,直接访问总线上的设备,适用于单设备场景
- Match ROM(0x55):后跟 64 位 ROM ID,精确匹配指定设备
- Search ROM(0xF0):搜索总线上所有设备,多设备场景用
ROM 命令之后,进入存储命令阶段。DS2431 的命令字节如下:
| 命令 | 字节码 | 作用 |
|---|---|---|
| Write Scratchpad | 0x0F | 向暂存器写入目标地址和数据 |
| Read Scratchpad | 0xAA | 回读暂存器,验证写入内容 |
| Copy Scratchpad | 0x48 | 把暂存器内容复制到 EEPROM |
| Read Memory | 0xF0 | 从 EEPROM 指定地址连续读取 |
写 EEPROM 的标准流程是:先复位,再 Skip ROM,接着发 0x0F 写暂存器,把目标地址和要写入的数据发过去;然后再次复位,发 0x48 复制暂存器,附带目标地址和校验字节 ES;最后等待 tPROG 编程时间(典型值 10ms),再回读验证。这个过程如果省略任何一步,都可能出现写入不稳定或者丢数据的情况。
3. 完整驱动实现:从时隙函数到应用 API
3.1 平台适配层:四行宏搞定移植
我在写这个驱动时,把硬件相关的操作封装成了 4 个宏加一个延时函数。这样移植到不同 MCU 时,只需要修改这一小段,协议层代码完全不用动。
/* ====== ds2431.h ====== */ #ifndef __DS2431_H #define __DS2431_H #include <stdint.h> /* 移植时只需修改以下宏和延时函数 */ #define DS2431_DQ_OUT() HAL_GPIO_WritePin(DS2431_PORT, DS2431_PIN, GPIO_PIN_RESET) // 占位示例 #define DS2431_DQ_IN() // 切换为输入模式(或释放总线) #define DS2431_DQ_LOW() // DQ 拉低 #define DS2431_DQ_HIGH() // DQ 拉高(开漏模式下释放) #define DS2431_DQ_READ() // 读取 DQ 电平,返回 0 或 1 void DS2431_DelayUs(uint32_t us); void DS2431_DelayMs(uint32_t ms); #endif这里有个细节:1-Wire 总线是开漏结构,所以“释放总线”等价于把 GPIO 配置为输入模式或者开漏输出时输出高电平。我在 STM32 上习惯把 DQ 引脚初始化为开漏输出,然后需要读的时候切换成输入模式,写完再切回开漏输出。只有输入模式才能避免 MCU 内部上拉或下拉影响采样结果。
3.2 底层时序:复位、写位、读位
时序层是整个驱动的根基,必须保证微秒级延时准确。我建议在 STM32 上用 DWT 实现延时,后面第 4 章会给出具体代码。先把时序函数写出来:
/* ====== ds2431.c(时序部分)====== */ uint8_t DS2431_Reset(void) { uint8_t presence = 0; DS2431_DQ_OUT(); DS2431_DQ_LOW(); DS2431_DelayUs(520); /* 复位脉冲,拉低 520us */ DS2431_DQ_IN(); /* 释放总线 */ DS2431_DelayUs(70); /* 等待从机应答窗口 */ presence = DS2431_DQ_READ(); /* 有应答时总线被从机拉低,读到 0 */ DS2431_DelayUs(200); /* 等待应答结束,释放总线 */ return presence; /* 返回 0 表示检测到设备 */ } void DS2431_WriteBit(uint8_t bit) { DS2431_DQ_OUT(); DS2431_DQ_LOW(); if (bit) { DS2431_DelayUs(3); /* 写 1:低脉冲 3us 后释放 */ DS2431_DQ_HIGH(); DS2431_DelayUs(57); /* 补满整个时隙 */ } else { DS2431_DelayUs(60); /* 写 0:保持低电平整个时隙 */ DS2431_DQ_HIGH(); DS2431_DelayUs(3); } } uint8_t DS2431_ReadBit(void) { uint8_t dat = 0; DS2431_DQ_OUT(); DS2431_DQ_LOW(); DS2431_DelayUs(3); /* 读时隙起始脉冲 */ DS2431_DQ_IN(); /* 释放总线 */ DS2431_DelayUs(8); /* 在 15us 窗口内采样 */ dat = DS2431_DQ_READ(); DS2431_DelayUs(50); /* 补满时隙 */ return dat; } void DS2431_WriteByte(uint8_t dat) { for (uint8_t i = 0; i < 8; i++) { DS2431_WriteBit(dat & 0x01); dat >>= 1; /* LSB first,先发低位 */ } } uint8_t DS2431_ReadByte(void) { uint8_t dat = 0; for (uint8_t i = 0; i < 8; i++) { dat >>= 1; if (DS2431_ReadBit()) { dat |= 0x80; /* LSB first,低位先读 */ } } return dat; }注意复位函数的返回值逻辑:我返回的是 DQ 引脚的电平,如果读到的电平是 0,说明从机有应答,设备存在。很多人在这一步会写反,建议在代码里用宏或者注释明确区分“检测到设备”和“返回值 0”。
3.3 命令层:Skip ROM、Read ROM 与 CRC8
时序函数就位之后,命令层就简单了。多设备模式和单设备模式的差别主要在 ROM 命令上,我先把最常用的 Skip ROM 和 Read ROM 封装好。
/* ====== ds2431.c(命令层)====== */ void DS2431_SkipROM(void) { DS2431_WriteByte(0xCC); } uint8_t DS2431_ReadROM(uint8_t *rom) { uint8_t crc = 0; if (DS2431_Reset() != 0) return 0xFE; /* 无设备应答 */ DS2431_WriteByte(0x33); /* Read ROM 命令 */ for (uint8_t i = 0; i < 7; i++) { rom[i] = DS2431_ReadByte(); crc = DS2431_CRC8(crc, rom[i]); } rom[7] = DS2431_ReadByte(); /* 第 8 字节是 CRC */ if (crc != rom[7]) return 0xFC; /* CRC 校验失败 */ return 0; } uint8_t DS2431_CRC8(uint8_t crc, uint8_t data) { uint8_t i; crc ^= data; for (i = 0; i < 8; i++) { if (crc & 0x01) crc = (crc >> 1) ^ 0x8C; /* 反向多项式 */ else crc >>= 1; } return crc; }这里的 CRC8 是 Dallas/Maxim 1-Wire 系列通用的校验算法,反向多项式是 0x8C。它主要用于校验 64 位 ROM ID,确保读到的序列号没有受到干扰。如果你不需要做多设备匹配,只做单设备访问,Skip ROM 就够用了,Read ROM 可以作为产线身份识别时的辅助手段。
3.4 存储 API:ReadMemory 与 WriteMemory 的完整实现
存储层的核心是 Read Memory 和 Write Memory。Read Memory 相对简单,Write Memory 要注意写暂存器、复制暂存器、等待编程完成、回读校验这四个环节缺一不可。
/* ====== ds2431.c(存储 API)====== */ uint8_t DS2431_ReadMemory(uint16_t addr, uint8_t *buf, uint16_t len) { if (buf == NULL || len == 0) return 0xFF; if (addr + len > 128) return 0xFD; /* 地址越界 */ if (DS2431_Reset() != 0) return 0xFE; DS2431_SkipROM(); DS2431_WriteByte(0xF0); /* Read Memory */ DS2431_WriteByte <p> <a href="https://download.csdn.net/download/qq_20967663/9666949" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>