简介:本资源是面向嵌入式安全开发工程师与物联网设备固件开发者的技术支持包,聚焦Maxim DS28E01-100加密芯片的C语言底层驱动与SHA-1硬件加速应用。资源提供完整可移植的C代码实现,涵盖单总线通信协议封装、芯片初始化、密钥管理、挑战响应认证及SHA-1摘要计算等核心功能,适用于汽车电子、医疗设备和工业控制系统等对数据完整性与身份认证有严苛要求的场景。压缩包共11个文件(21KB),含7个头文件(.h)定义寄存器映射与接口函数、3个源文件(.c)实现OW(单总线)底层驱动、DS28E01专用操作逻辑及SHA算法调用封装,另附1份中文文档《DS28E01-100函数说明.doc》详解API用法与典型调用流程。已有1075人学习下载,代码结构清晰、注释完备,可直接集成至Keil/IAR等主流嵌入式开发环境,显著降低安全芯片接入门槛。
DS28E01-100 加密芯片 C 语言驱动全解析:从时序到认证代码的落地实践
做嵌入式这几年,加密芯片一直是个绕不开的话题,尤其是耗材认证、配件防伪、版权保护这一类场景。市面上方案不少,但我用得最多的还是 DS28E01,特别是 DS28E01-100 这个型号。前几天整理资料,翻出了一个老项目的代码包DS28E01-100-C-code.zip,里面封装好的 C 语言驱动代码相当完整,直接改改就能用。想想当初调试这个芯片时踩过的坑——1-Wire 时序的微妙、SHA-1 认证流程的绕弯、还有总线上下拉电阻的玄学——决定把这套东西彻底拆开讲一遍,从芯片内部结构到代码逐段分析,再到实际调试中的坑,一次说清楚。这篇文章不是简单的代码注释,而是把"为什么这么做"和"实际会上遇到什么问题"都讲透,适合正在做耗材加密、板卡认证、或者第一次接触 DS28E01 的嵌入式开发者。
1. DS28E01 到底是个什么样的芯片
1.1 芯片定位与核心特性
DS28E01-100 是 Maxim(现在归 ADI 旗下)推出的一颗 1-Wire 接口的 EEPROM 加密芯片。很多人第一次看到它,第一反应是"这不就是个 EEPROM 吗?"——确实,它内部有 1Kb(128 字节)的 EEPROM 空间,可以当普通存储用。但这颗芯片真正值钱的地方,不是存储,而是内置的 SHA-1 认证引擎。
打个比方,普通 EEPROM 就是一把能打开的锁,谁拿到都能读;DS28E01 则是带门禁的保险柜——你要么持有密钥,要么知道开锁暗号,否则连门都摸不到。它的典型应用有三类:
第一类是耗材认证。打印机墨盒、医疗设备探头、净水器滤芯,这些高利润耗材里放一颗 DS28E01,主机每次开机都去认证一次,非原装耗材直接拒绝工作。第二类是配件防伪。电池、充电器、传感器模块,通过认证确保用的是原厂配件,既保护品牌利益,也避免劣质配件引发安全事故。第三类是数据加密存储。设备序列号、校准参数、激活状态这类敏感数据,以密文形式存储,防止被篡改或克隆。
DS28E01-100 和 DS28E01-00 的区别也在这里:-100 后缀代表芯片出厂时已经写入了唯一的 64 位 ROM 注册码,每颗芯片都不同,你可以把它理解成芯片的"身份证号"。这个注册码配合 SHA-1 算法,可以实现双向认证——主机验证芯片真伪,芯片验证主机合法性。
1.2 为什么加密芯片要用 1-Wire 接口
接 口 选 型 是 很 多 人 忽 视 的 关 键 点 。DS28E01 用的是 1-Wire 总线,只有一根数据线加一根地线,供电和数据共用同一根线。这在很多场景下是巨大优势:
- 节省 IO 资源。单片机本来就 IO 紧张,一颗芯片只占一个 IO 口,哪怕是只有 8 个引脚的 STM8 或 PIC 单片机也能轻松接。
- 远距离传输能力。1-Wire 总线在标准速率下理论传输距离可达 100 米以上,虽然实际用不到这么远,但相比 I2C 和 SPI,抗干扰能力和布线灵活性确实强不少。
- 连接器成本低。因为是单线,接插件只需要两针,在小尺寸设备里非常好布线。
当然,代价就是时序要求严格。1-Wire 是"半双工+时分复用"的通信方式,所有设备靠一个开漏输出口通信,时序精度取决于主机延时精度,尤其是复位脉冲和存在检测脉冲的时序窗口很短,用定时器中断或者空循环 delay 的时候要格外小心。
1.3 DS28E01-100 内部架构速览
这颗芯片的内部结构可以拆成几个模块:
- ROM(64 位):出厂固化的唯一序列号,包含 8 位家族码(DS28E01 是 0x4D)、48 位唯一序列号、8 位 CRC 校验。
- EEPROM(128 字节):分 4 页,每页 32 字节。支持写保护,一旦写入保护位,整页就变只读。
- SHA-1 引擎:核心加密模块,由主机发送 512 位(64 字节)的挑战数据,芯片内部的 SHA-1 引擎结合密钥和 ROM 序列号,计算出 160 位(20 字节)的消息认证码(MAC)。
- 暂存器:所有写操作先写入暂存器,确认无误后(比如校验暂存器 CRC)才执行"复制暂存器"命令,把数据真正写入 EEPROM。
理解这个架构很重要,因为在代码层面,SHA-1 的算法流程、CRC 校验的调用时机、EEPROM 读写时的状态切换,全都是围绕这几个模块来设计的。
2. C 语言代码包结构分析与驱动设计思路
2.1 代码包内文件组成与职责划分
DS28E01-100-C-code.zip解压后,典型的文件结构如下(不同版本可能略有差异,但职责划分大同小异):
DS28E01-100-C-code/ ├── main.c // 演示主程序 ├── ds28e01.c // 芯片驱动,读写、认证封装 ├── ds28e01.h // 芯片驱动头文件,定义命令和结构体 ├── onewire.c // 1-Wire 总线底层时序驱动 ├── onewire.h // 总线驱动头文件 ├── sha1.c // SHA-1 算法实现 ├── sha1.h // SHA-1 头文件 └── crc.c // CRC 校验实现(CRC8 和 CRC16)这套分层设计思路是很清晰的:
底层是 onewire.c,负责最基础的位时序操作——写 0、写 1、读位、复位、存在检测。这一层跟硬件强相关,移植时主要改这里的延时函数和 IO 操作宏。
中间层是 ds28e01.c,基于 onewire 底层,实现芯片命令:跳过 ROM、读 ROM、匹配 ROM、读暂存器、写暂存器、复制暂存器、读存储器、SHA-1 认证等。
上层是主程序 main.c,演示了完整的认证流程:准备挑战数据→发送到芯片→读取 MAC→主机自己计算 SHA-1→对比结果。
分层的好处是:底层不用动,中间层只需改接口适配你的单片机平台,上层是业务逻辑,想怎么改都行。这种设计对嵌入式工程来说非常友好,实际项目里我也推荐沿用这个架构,比把时序和业务逻辑揉在一起要省心得多。
2.2 为什么代码里一定要有两条 SHA-1 路径
这个代码包最容易被忽略的精髓,是你既能在主机端看到 SHA-1 算法实现(sha1.c),又能在芯片端体验到 SHA-1 计算流程(通过命令触发芯片内部引擎)。
对加密认证稍有了解的人都知道,SHA-1 认证绝不是"芯片算一遍、主机算一遍、结果一样就通过"这么简单。这里面的关键在密钥管理:
- 主机端算 MAC,需要知道密钥(可能是 4 字节、8 字节或 16 字节的 secret)。
- 芯片端算 MAC,密钥存储在芯片内部的安全 EEPROM 区域。
- 挑战数据(challenge)是主机动态生成的随机数。每次认证都用不同的随机数,防止重放攻击。
- MAC 计算时,不仅包含挑战数据,还把芯片的 ROM 序列号、命令码、EEPROM 内容等一并参与散列。
这就解释了为什么代码里既要实现主机端的 SHA-1,又要操作芯片端对数据进行 SHA-1——两条路径算出来的 MAC 要完全一致,才能真正完成双向认证。如果只是把芯片当普通 EEPROM 读写,那加密就没有意义了。
2.3 SHA-1 算法在嵌入式平台上的适配技巧
SHA-1 算法本身的实现,网上有大把标准 C 代码,一般直接抄进项目没问题。但嵌入式平台上需要注意几个点:
第一,消息长度。SHA-1 标准处理 512 位(64 字节)为一个分组。DS28E01-100 的认证命令,主机要发给芯片 64 字节挑战数据,这正是恰好一个分组,非常巧妙。但主机自算 MAC 时,往往要把多个字段拼接起来(挑战数据 + ROM 序列号 + 密钥 + 命令码 + 存储数据),拼出来的长度就不是正好 64 字节了。这时 SHA-1 内部会做填充(补位 + 补长度),代码里的sha1_update()函数会自动处理,你只需要注意调用顺序别调换就行。
第二,字节序问题。这是最容易踩的坑。DS28E01 的数据手册里,所有多字节数据都是先发最低有效字节(Littl-endian)。而 SHA-1 算法内部对消息的处理是按大端序来理解字的。我在第一次移植时就因为这个问题,MAC 死活对不上。后来排查发现,需要在拼接数据的时候把每个字节按正确顺序放入消息缓冲区,关键要看数据手册里给的示例向量来对照。
第三,内存占用。标准的 SHA-1 实现需要 80 字节的 W 数组(每个字 4 字节 × 80 个字),加上中间变量,堆栈开销大概 200 字节左右。对于 RAM 只有 1KB 的小单片机,要小心别爆栈。有些精简版实现可以只保留 16 个字滚动计算,但代码复杂度会上去。这个代码包用的是标准实现,在 STM32 上跑毫无压力。
3. 1-Wire 底层时序驱动的实现细节
3.1 复位与存在检测:一切通信的起点
1-Wire 总线的所有通信,都必须以"复位脉冲 + 存在检测"开始。这是主机和各从设备建立同步的第一道握手,过程分四个阶段:
- 主机拉低总线,至少保持 480μs,然后释放。
- 总线由上拉电阻拉高,主机释放总线后等待 15-60μs。
- DS28E01 检测到上升沿后,等待 15-60μs,然后拉低总线,保持 60-240μs。
- 主机在释放总线后的 60-240μs 窗口内读取总线状态。如果读到低电平,说明芯片存在。
代码里的实现大概是:
uint8_t ow_reset(void) { uint8_t presence; OW_LOW(); delay_us(480); OW_HIGH(); delay_us(70); presence = OW_READ(); delay_us(410); return presence; // 返回 0 表示检测到芯片 }注意几个关键点:OW_LOW()和OW_HIGH()操作的是开漏 IO,不能直接把 IO 配置成推挽输出拉高,而应该是"设置为输出低电平"和"释放 IO(配置为输入或高阻)",靠外部上拉电阻把总线拉高。很多新手在这里直接把 IO 拉高,导致总线电平异常,通信不稳定。这个区别在 3.3 节会再详细展开。
3.2 读写位的时序窗口
1-Wire 在标准速率(15.4kbps)下,每一位的传输都有一个严格的时序窗口,大约 60-120μs。
写 1 时序:主机拉低总线 1-15μs,然后释放,靠上拉电阻拉高,保持到时序结束。
void ow_write_bit(uint8_t bit) { if (bit) { OW_LOW(); delay_us(6); OW_HIGH(); delay_us(64); } else { OW_LOW(); delay_us(60); OW_HIGH(); delay_us(10); } }写 0 时序:主机拉低总线,保持 60-120μs,然后释放。
读时序:主机拉低总线 1-15μs,然后释放,再在 15μs 内采样总线电平。代码里常见的做法是:
uint8_t ow_read_bit(void) { uint8_t bit; OW_LOW(); delay_us(6); OW_HIGH(); delay_us(9); bit = OW_READ(); delay_us(55); return bit; }这里最讲究的就是"拉低后多久释放"和"释放后多久采样"这两个时间窗。如果主机在低电平释放得太晚,芯片可能无法判断这是读时序还是写 0 时序;如果采样太晚,芯片已经释放总线,读到的一律是高电平。
实际调试中,如果读出来的数据全是 0xFF 或者全是 0x00,十有八九是读写时序的延时参数不对,用示波器对比数据手册的时序图,逐个调整延时就行。开发时建议把延时函数独立封装,方便根据主频调整。
3.3 开漏 IO 与上拉电阻的工程选择
前面反复提到开漏 IO,这里把原理一次性讲清楚。1-Wire 总线的设计核心是:任何设备(包括主机)都只能把总线拉低,不能主动拉高。总线空闲时为高电平,靠的是主机 IO 口上的上拉电阻。
所以主机 IO 的正确配置是:输出模式时,写 0 就是拉低,写 1 要切换成输入模式(高阻态),让上拉电阻把电平拉上去。有些单片机支持"开漏输出模式 + 内部上拉",也可以直接使用,但内部上拉电阻通常有 30-50kΩ,阻值偏大,在长线或强干扰环境下容易出问题。
设计建议:
- 外部上拉电阻选择 4.7kΩ。这是 1-Wire 总线的经典值。总线长度超过 1 米时,可以改成 2.2kΩ;如果总线上挂的设备数量多(超过 5 颗),也要适当减小上拉电阻。
- 加一个 100Ω 左右的串联电阻,放在主机 IO 和总线之间。这个电阻可以抑制总线反射,对长线通信有明显改善。
- ESD 保护不可少。DS28E01 经常用在可插拔的耗材上,拔插瞬间的静电很容易打坏芯片。总线入口加一个 TVS 管或者 ESD 阵列,成本几毛钱,能省很多返修麻烦。
另外有一点要注意,1-Wire 总线不适合用长排线直接连接,尤其是和电机、继电器等感性负载靠得很近的场合。我有一次把 1-Wire 线绑在电机线上,通信直接瘫痪,后来用双绞线并拉开距离,问题才解决。
4. DS28E01-100 读写与认证流程的完整实现
4.1 ROM 命令:先找到芯片,再谈其他
1-Wire 总线上可以挂多个 DS28E01,所以通信的第一步是"选择跟谁说话"。DS28E01 支持 4 条 ROM 命令:
- 0x33 读 ROM:总线上只有一颗芯片时使用,直接读出 8 字节 ROM 序列号。
- 0x55 匹配 ROM:主机先发送 8 字节 ROM 码,总线上匹配的芯片才会响应后续命令。
- 0xF0 搜索 ROM:总线上有多颗芯片时,通过算法逐位搜索,找出所有芯片的 ROM 码。
- 0xCC 跳过 ROM:不指定芯片,直接跟总线上唯一的一颗芯片通信。
代码包里 main.c 的典型流程是:
// 复位总线并检测存在 ow_reset(); // 发送跳过 ROM 命令 ow_write_byte(0xCC); // 发送写暂存器命令 ow_write_byte(0x0F); // 发送目标地址(低字节 + 高字节) ow_write_byte(addr & 0xFF); ow_write_byte((addr >> 8) & 0xFF); // 然后发送要写入的数据...实际项目里,如果系统只有一颗 DS28E01,用跳过 ROM 最省事;如果可能有多颗,建议用匹配 ROM,先把 ROM 码烧在主机固件里,每次通信先发送匹配命令。这样即使总线上混入了其他 1-Wire 设备,也不会影响通信。
4.2 存储器读写操作与暂存器机制
DS28E01 的 EEPROM 读写有三个关键命令,配合暂存器机制,能有效避免数据写坏:
写暂存器(0x0F):主机发送目标地址(双字节,低字节在前)和数据,数据先进入芯片内部暂存器,不会直接写入 EEPROM。写完暂存器后,主机必须发送"读暂存器"命令,把暂存器内容回读出来并验证 CRC,确认无误后才执行下一步。
复制暂存器(0x55):这条命令把暂存器中的数据真正烧入 EEPROM。但这里有个重要细节——执行复制暂存器命令后,如果目标是写保护区域,芯片会返回错误,而且该命令执行时总线会被拉低,表示"正在烧写",烧写 EEPROM 的时间大约需要 10ms。所以代码里在执行复制暂存器后,要延时 10-20ms 再进行下一步操作,且最好检测总线状态确认芯片是否烧写完毕。
读存储器(0xF0):读取 EEPROM 数据,可以连续读,读到页末尾自动回卷。实际调用时,发送起始地址后,逐字节读取数据,最后一字节读完后芯片会发送 CRC16 校验值(两字节),主机可以据此确认读取的数据是否完整。
这个"先写暂存器→回读验证→再复制"的三步式设计,是 1-Wire EEPROM 的通用机制。一定要按这个规范做,不要图省事直接把数据写进去。EEPROM 写次数有限(DS28E01 的擦写寿命大约是 10 万次),如果频繁写数据,建议做磨损均衡。
4.3 SHA-1 认证的核心流程和数据格式
终于到了重头戏:SHA-1 认证。在 DS28E01 的数据手册里,认证命令是0xA5,完整流程如下:
- 主机复位总线,发送跳过 ROM(或匹配 ROM)。
- 主机发送 0xA5 命令。
- 主机发送 64 字节挑战数据(challenge)。
- 芯片内部的 SHA-1 引擎计算 MAC,主机逐字节读取 20 字节 MAC 值。
- 主机在本地用相同的挑战数据、密钥、ROM 序列号和必要的数据,计算自己的 SHA-1 MAC。
- 比较两个 MAC。一致则认证通过。
这里最关键的问题是:芯片计算 MAC 时,哪些数据参与了 SHA-1 散列?根据数据手册,芯片端的 SHA-1 输入数据块由 5 个部分组成:
| 输入段 | 长度 | 说明 |
|---|---|---|
| 挑战数据 | 64 字节 | 主机随机生成 |
| ROM 序列号 | 8 字节 | 芯片唯一 ID |
| 密钥(Secret) | 4 字节 | 存储在芯片的安全 EEPROM 区域 |
| 命令码 | 1 字节 | 0xA5 |
| EEPROM 数据 | 取决于具体应用 | 可选,参与认证可防止数据被篡改 |
这五个部分拼接起来,总长度通常会超过 64 字节。SHA-1 算法内部会先处理第一个分组(64 字节),再处理后续分组。主机端实现时必须严格按照数据手册规定的拼接顺序,一点都不能乱。更麻烦的是,其中 "密钥" 的具体位置、参与方式,在不同型号(DS28E01 和 DS28E01-00)之间是有区别的。这份代码包里的 sha1.c 已经把这些细节处理好了,我强烈建议在正式使用前,先拿数据手册里的测试向量(test vector)跑一遍,确认主机和芯片算出的 MAC 一致,再集成到项目里。
另外一个工程要点是:挑战数据必须每次都不一样。如果挑战数据固定,攻击者可以录制一次通信过程,之后一直重放。代码里用了一个伪随机数生成器,以系统时钟或 ADC 噪声做种子来生成挑战数据,这样每次认证的 MAC 都不同,重放攻击就失效了。
4.4 安全密钥的写入与保护机制
密钥写入是 DS28E01 项目里最需要严谨对待的环节。密钥(通常 4 字节)存在 EEPROM 的特定地址,写入后需要设置写保护,防止后续被覆盖或读出。
DS28E01 的写保护机制是按页生效的。每页有对应的保护位,一旦置位,该页就变成只读。所以推荐的做法是:
- 把密钥放在第 3 页(地址 0x60-0x7F),也就是最后一页。
- 写完密钥后,立即设置第 3 页的保护位。
- 保护位本身也是 EEPROM 位,一旦写 1 之后,就不能再写回 0(OTP 特性)。
这意味着你必须在出厂前就把密钥写好,一旦写保护,芯片的密钥区就永久锁定,只能报废。我见过不少项目在这个环节出了问题,密钥写错但保护已经置位,整批芯片作废,损失不小。建议在生产流程中加一道"写入校验"步骤:先写密钥,再读回验证,确认无误后再执行写保护命令。
另外还应该注意,不要把密钥硬编码在主机固件里。固件一旦被人读出来,密钥就全泄露了。正确做法是把主机端密钥存储在安全芯片里,或者至少做一层密钥分散(用设备序列号派生密钥)。代码包里为了方便演示,密钥是写死在常量里的,实际项目要替换成安全的密钥管理方案。
5. 常见问题与排查技巧实录
5.1 时序问题导致的通信失败
现象:复位后读不到存在脉冲,或者能读到存在脉冲但读数据全是 0xFF / 0x00。
排查思路:
第一,用示波器直接看总线波形。重点检查三个地方:
- 复位脉冲的低电平时间是否达到 480μs;
- 存在检测脉冲是否在主机释放总线后的 15-60μs 窗口内出现;
- 读写位的时序是否符合数据手册的时序图。
第二,检查延时函数是否准确。很多开发板的延时函数依赖系统时钟,如果时钟配置错误或优化等级开太高,delay_us(6)实际执行时间可能严重偏短或偏长。建议写一个简单的校准函数:拉高 IO 保持一段时间,用示波器量实际高电平时间,反过来调整延时次数。
第三,检查 IO 模式配置。我遇到最多的问题就是 IO 口没有配置成开漏模式,或者切换输入输出时没有正确释放总线。
经验值参考:
| 参数 | 最小值 | 典型值 | 最大值 |
|---|---|---|---|
| 复位低电平时间 | 480μs | 480μs | — |
| 存在检测采样窗口 | 60μs | 70μs | 240μs |
| 写 0 低电平时间 | 60μs | 60μs | 120μs |
| 写 1 低电平时间 | 1μs | 6μs | 15μs |
| 读采样延时 | — | 9-15μs | — |
5.2 上拉电阻与实际电路引起的信号质量
现象:短距离通信正常,拉长到 1 米以上就随机失败,或者温度变化时通信不稳定。
排查思路:
先检查上拉电阻阻值。4.7kΩ 是通用值,但如果总线电容较大(长线、多节点),建议换成 2.2kΩ 甚至 1kΩ,以加快上升沿。
再检查总线布线。1-Wire 数据线尽量不要走长排线,更不能和电源线或信号线平行走太长的距离。实在避不开,用双绞线,并和强干扰源保持距离。
最后检查地线。1-Wire 是单线通信,地线就是回路。如果设备和主机的地电位不一致,通信肯定不稳定。确保共地良好。
5.3 SHA-1 认证失败的所有可能原因
现象:读写操作完全正常,但每次认证比对 MAC 都失败。
排查思路(按出现频率排序):
- 字节序错误:最常见。芯片发出来的 MAC 字节序和主机计算的字节序不一致。逐字节对比,看是否有逆序规律。
- 参与数据不完整或有误:密钥、ROM 序列号、挑战数据、命令码,任何一个字段不对,MAC 必不相同。在代码里加打印,把主机端参与计算的所有字段打出来,和芯片端数据手册的示例对比。
- 密钥错误:芯片存储的密钥和主机计算的密钥不一致。如果芯片之前写过别的密钥,需要先擦除整个 EEPROM 区再写入正确密钥。
- 挑战数据重复:某些伪随机数生成器实现不当,可能导致每次生成的挑战数据相同。这不会引起认证立刻失败,但会降低安全性。
- SHA-1 实现本身有 bug:可以用 FIPS 180-1 官方测试向量验证 sha1.c 是否正确。比如 SHA-1("abc") 的结果应该是 0xA9993E36 4706816A BA3E2571 7850C26C 9CD0D89D。
注意:在排查 MAC 不一致时,优先用数据手册里的测试向量和环境做对照,不要直接用随机数据调试。数据手册里给了几组完整的挑战数据-密钥-MAC 对应关系,我建议把其中一组写死在代码里做联调,确认通过后再上随机数。
5.4 批量生产时的常见隐患
芯片认证方案从原型走向量产,还有几个坑:
- 芯片写入污染:生产环境中的静电容易打坏芯片的 EEPROM,导致密钥写入后读出来是错的。务必在烧录工位加 ESD 防护。
- 写保护顺序:建议在产测软件里做成"写入→读回→比对→写保护"四个独立步骤,每一步都有明确界面提示,避免操作员误操作。
- 唯一序列号的记录:在用"匹配 ROM"方案时,出厂前要把每颗芯片的 ROM 序列号记录到主机端。这个操作很容易漏,建议用机器自动读取,不要人工抄录。
结合个人经验,还有一个小建议是:在产测阶段,把芯片的 ROM 序列号、密钥版本、认证结果一起打包存数据库。以后出了问题,能快速定位是哪一批物料、哪颗芯片、哪个环节出了问题。
6. 代码级优化与项目移植建议
6.1 延时函数与平台适配的真实做法
拿到代码包后,第一件事不是编译,而是把底层 IO 操作宏改成你平台对应的实现。以 STM32 为例:
// 原来的宏定义 #define OW_LOW() HAL_GPIO_WritePin(OW_GPIO_Port, OW_Pin, GPIO_PIN_RESET) #define OW_HIGH() HAL_GPIO_WritePin(OW_GPIO_Port, OW_Pin, GPIO_PIN_SET) #define OW_READ() HAL_GPIO_ReadPin(OW_GPIO_Port, OW_Pin)但我在实际项目中不用HAL_GPIO_WritePin/ReadPin,因为 HAL 库的函数调用封装层级多、耗时长,在 1-Wire 这种微秒级时序下很容易超时。更稳的做法是直接操作寄存器:
#define OW_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() #define OW_PORT GPIOB #define OW_PIN GPIO_PIN_12 #define OW_LOW() (OW_PORT->BSRR = (uint32_t)OW_PIN << 16) #define OW_HIGH() (OW_PORT->BSRR = OW_PIN) #define OW_READ() ((OW_PORT->IDR & OW_PIN) ? 1 : 0)按键控的方式直接置位/复位,快且可控。初始化时把引脚配置为开漏输出:
GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = OW_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 开漏 GPIO_InitStruct.Pull = GPIO_PULLUP; // 内部上拉 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(OW_PORT, &GPIO_InitStruct);如果你用的单片机没内部上拉,或者内部上拉不够强,需要在外部添加上拉电阻。
6.2 挑战数据的随机数生成方案
代码包里演示用的随机数生成器比较简陋(通常是个 LFSR 或者简单的伪随机数)。在真实项目里,挑战数据的不可预测性直接决定防重放攻击的能力,建议按优先级选择:
- 硬件真随机数发生器(如 STM32L4/R5 系列的 RNG 外设),这是最佳选择。
- 片内 ADC 噪声采集。把某个悬空 ADC 引脚的采样值做 seed,配合 LFSR 或 xorshift 生成伪随机序列。SPI 总线上如果有别的外设,也可以用 SPI 时序的时间抖动来产生熵。
- 实时时钟低字节配合计数器。安全性差一些,但比固定值好太多。
无论用哪种方案,都要保证系统每次上电时挑战数据的种子不同。否则攻击者只要知道生成算法,就能预测挑战数据,整个认证形同虚设。
6.3 低功耗场景的特别注意事项
如果你做的是电池供电设备,DS28E01 所在电路的功耗也要精打细算:
1-Wire 总线上拉电阻在上拉状态下会产生静态电流(4.7kΩ 上拉到 3.3V,大约 0.7mA)。关键是 1-Wire 空闲时总线默认是高电位,这个电流会一直存在。低功耗设备里,建议用 MOSFET 或 GPIO 控制上拉电阻的通断:平时断开上拉,认证时再打开。
DS28E01 本身有休眠模式,但需要主机通过特殊时序进入。代码包里没有包含这个功能,如果你对功耗特别敏感,建议查数据手册的相关章节。
执行 SHA-1 认证时芯片功耗会瞬时增大,这在电池供电场合影响不大,但如果是能量采集(太阳能、热电)供电,要评估瞬时功耗是否超出电源能力。
6.4 代码包测试向量验证流程
拿到代码包后,别急着改业务逻辑,先做一遍"标准测试向量验证"。具体做法是:
从数据手册里找一组给定的密钥、挑战数据和预期 ROM 序列号,把这些值填入 main.c 的测试区域,编译后烧进单片机,串口打印主机端计算结果和芯片端返回结果。如果两个 MAC 完全一致,说明驱动程序底层通信、SHA-1 实现、数据拼接顺序全部正确。
注意这里一个容易混淆的点:数据手册的测试向量在计算主机端 MAC 时,用的是标准的 SHA-1 还是改过的 SHA-1?DS28E01 的 SHA-1 引擎是标准的 SHA-1,没有修改算法内部逻辑。有区别的是输入数据的组织方式和填充逻辑。拿官方测试向量验证时,如果两边不一致,先检查拼接顺序和字节序,别急着怀疑算法实现。
7. 从一个驱动到完整认证体系的扩展思路
7.1 双向认证:从单方验证到互相确认
前文讲的都是主机验证芯片(防伪方向)。但 DS28E01 还有另一个方向——芯片验证主机。这个能力在某些场景下非常关键,比如防止主机固件被克隆后批量生产山寨设备。
双向认证的思路是:
- 主机向芯片发送挑战数据 A,芯片返回 MAC_A。
- 芯片(内部逻辑)通过另一组密钥验证主机的响应,或者主机再发送一组由自己私钥计算的签名数据,芯片验证。
- 只有两者都通过,设备和耗材才建立互信。
DS28E01 的 SHA-1 引擎天然支持这种双向认证,只是需要你设计两套密钥和两组挑战数据。不过需要注意,DS28E01 不能主动发起挑战,它只能被动响应。所以真正的双向认证逻辑,还是由主机主导:先是主机验证芯片,再是主机发一个特殊命令让芯片验证主机的计算结果。
7.2 多颗芯片共存的总线管理
在 1-Wire 总线上挂多颗 DS28E01 的用法不多,但确实存在——比如一个设备有两路可换耗材,每一路都有自己的认证芯片。这时候就需要用"搜索 ROM"流程来枚举总线上的所有芯片。
1-Wire 搜索 ROM 的算法有点类似二叉树搜索,代码包里如果没有实现,需要自己写。核心思路是利用 1-Wire 的"与逻辑"特性:在搜索 ROM 命令下,总线上所有芯片同时应答。当主机要读某一位时,如果所有芯片该位都是 0,则总线为低;如果都是 1,则总线为高;如果既有 0 也有 1,总线为低,此时主机需要再发一个"读补码"位,来判断是否存在两种值。根据读到的结果,主机决定下一位置 0 还是置 1,逐步缩小搜索范围,直到找出一颗完整的 ROM 码,然后再从头开始搜索下一颗。
这个算法网上标准的实现比较多,需要注意的是:搜索过程中主机发出的每一位都必须让所有芯片都能识别,所以要用"读两位 + 写一位"的三步循环。实际项目里如果只需要支持固定数量的芯片,也可以简化:比如点对点连接,直接跳过搜索流程。
7.3 从耗材防伪到数据溯源
最后说一点超出"驱动代码"本身的思路。DS28E01 这种加密芯片的价值,不只是"能认证",而在于它把物理世界的每一颗芯片都变成了一个不可克隆的数字身份。ROM 序列号全球唯一,EEPROM 可以存储生产批次、制造日期、校准数据,密钥保证这些数据不可伪造。
在数据溯源的场景里,这个特性非常有用。比如医疗设备探头,每一根探头出厂时写入了唯一的批次号、生产日期和校准参数,主机每次使用前进行认证和数据读取。一旦探头出现问题,可以精确追溯到是哪个批次、哪个工厂、哪个时间段的工艺问题。
所以在设计产品时,别只把 DS28E01 当作"防伪开关",它还可以承担身份标识、参数存储、数据安全等多重角色。驱动代码只是地基,上面的业务设计才决定方案的最终价值。
// 一个把多功能结合起来的示意代码 void device_auth_and_init(void) { uint8_t mac[20]; uint8_t serial[8]; uint8_t calib[16]; // 1. 读取芯片唯一序列号 ds28e01_read_rom(serial); // 2. 执行 SHA-1 认证 if (ds28e01_auth(serial, mac) != DS28E01_OK) { // 认证失败,拒绝初始化 return; } // 3. 认证通过后读取校准参数 ds28e01_read_mem(0x20, calib, 16); // 4. 根据校准参数完成设备初始化... }这套模式在很多产品里都验证过,稳定性高,抗攻击能力强,值得复用。
8. 结语:代码之外的真实工程经验
写到这里,我想说一下实际做这个项目时的感受。DS28E01 这套方案,从原理上看并不复杂——SHA-1 算法是公开的,1-Wire 时序是现成的,代码包也提供了完整的驱动。但它之所以能成为经典方案,恰恰是因为这些"简单"环节组合在一起后,形成了多层防线:即使攻击者读到了 EEPROM 数据,没有密钥也无法伪造认证;即使拿到了密钥,芯片的 ROM 序列号无法复制,一机一密的思路让批量复制变得没有意义。
在整个调试过程中,我最大的体会是:拿到现成的代码包只是起点,真正的工程量在验证和适配。每一种单片机的延时特性不同,每一批芯片的电气参数也有微小差异,你不能百分之百依赖"之前跑过没问题"的代码。用示波器量一遍时序,用数据手册的测试向量验证一遍 SHA-1 的 MAC 结果,这一步省不得。
最后再分享一个小技巧:如果你在调试时发现某个字节读出来总是错的,或者 MAC 比对总是若即若离地对不上,别急着改算法,先在一条干净的总线上(直接用杜邦线连接主机和芯片,旁边不接其他任何外设)把所有原始数据打印出来人工比对。很多时候问题不是出在加密逻辑,而是出在最底层那根信号线上——总线电平有一点点不干净,所有上层计算都会跟着跑偏。
DS28E01 的生命力比我预想的强得多,即使在加密方案层出不穷的今天,它依然在很多工业产品里默默运行。希望这篇拆解能帮你把它的潜力完全发挥出来。
本文还有配套的精品资源,点击获取