
1. 项目概述在嵌入式开发中我们常常需要为系统增加一小块非易失性存储空间用来保存一些掉电不能丢的数据。比如一个温控器的校准参数、一个遥控器的配对ID或者一个简单的设备运行计数器。传统方案是给单片机外挂一个I2C或SPI接口的EEPROM芯片这至少需要占用2到4个I/O口对于引脚资源本就紧张的8位MCU来说是个不小的负担。我第一次接触1-Wire总线就是被它的“一根线”所吸引。一根数据线既传数据又供电还能挂多个设备听起来简直是为极致精简的硬件设计而生的。DS2430A就是这样一款经典的256位32字节1-Wire EEPROM。而MC68HC705C8A以下简称C8A则是摩托罗拉后来的飞思卡尔HC05家族中非常经典的一款8位微控制器资源有限但足够可靠。把这两者结合起来用C8A的单个I/O口去驱动DS2430A实现数据的存储与读取是一个在资源受限场景下非常典型且实用的硬件接口设计案例。这篇文章我就来详细拆解这个接口的设计思路、通信协议的软件实现细节以及在实际调试中积累的一些关键经验。2. 核心器件与协议深度解析在动手写代码之前我们必须吃透两个核心DS2430A这颗芯片的“脾气”以及1-Wire总线协议的“语言”。只有理解了它们的工作原理才能写出稳定可靠的驱动。2.1 DS2430A不止是存储器DS2430A远不止一个简单的存储芯片它是一个集成了身份识别、状态管理和存储功能的智能从设备。2.1.1 内存架构与功能分区它的内存可以看作三个独立的部分理解这个结构对正确操作至关重要数据存储器这是主要的256位EEPROM被组织成32字节地址$00-$1F。但它不能直接写入。所有对EEPROM的写操作都必须经过一个256位的暂存器RAM。你可以把暂存器想象成一个“草稿纸”先把要写的数据放在这里检查无误后再一次性“誊写”复制到真正的EEPROM“笔记本”上。这个设计保证了EEPROM写入的可靠性防止误操作。应用寄存器这是一个独立的64位EEPROM区域带有自己的64位暂存器RAM。它的特殊之处在于可以一次性永久锁定。一旦锁定这个区域就变成只读的OTP一次性可编程存储器。常用于存储设备的唯一序列号、生产日期等一旦设定就永不更改的信息。状态寄存器一个8位的寄存器核心作用是指示应用寄存器的锁定状态。读取它如果值是$FF表示应用寄存器未锁定可读写如果是$FC则表示已锁定只能读不能写。任何写操作都会被忽略。2.1.2 唯一的身份64位激光ROM这是1-Wire设备的灵魂。每个DS2430A在出厂时都被激光刻蚀了一个全球唯一的64位ROM码。其结构为8位家族码对于DS2430A这个值是固定的用于标识设备类型。48位唯一序列号这才是真正的唯一ID保证了世界上没有两个相同的DS2430A。8位CRC校验码由前56位计算得出用于主机在读取ROM码时进行校验确保数据正确。这个ROM码是实现单总线上多设备寻址的基础。主机通过“匹配ROM”命令可以精准地对总线上的某一个设备“喊话”。2.2 1-Wire协议单线上的精密舞蹈1-Wire协议的本质是一种单主多从、半双工、异步串行通信协议。所有通信都由主机MCU发起和严格控制时序从设备DS2430A只在规定的时间窗口内响应。2.2.1 硬件接口与“寄生供电”硬件连接极其简单主机的一个I/O口、DS2430A的DATA引脚、一个上拉电阻通常4.7kΩ - 10kΩ、电源和地。这里的精妙之处在于“寄生供电”。DS2430A内部有一个电容当总线被主机拉高时会通过一个二极管向这个电容充电作为芯片的工作能量。当总线为低电平时芯片就依靠这个电容存储的电量工作。因此在发起需要芯片内部EEPROM编程如“复制暂存器”的长耗时操作时协议要求主机必须将总线持续拉高至少100ms就是为了保证芯片有足够的能量完成写入动作。这是很多初学者容易忽略而导致写入失败的关键点。2.2.2 通信基石复位与应答脉冲任何一次1-Wire通信对话都必须以“复位-应答”序列开始相当于打电话时的“喂听到请回答”。主机复位主机将总线拉低至少480µs然后释放变为高阻输入态。从机应答主机释放总线后会在接下来的60-240µs内检测总线。如果从设备存在且就绪它会在检测到总线被释放后主动将总线拉低60-240µs然后释放。主机检测主机在释放总线后的480µs内如果检测到一个由低到高的跳变从机拉低后又释放就表示收到了“应答脉冲”通信链路建立成功。如果主机没有检测到这个脉冲说明总线上没有设备或设备故障后续的通信也就无法进行。在软件驱动中复位子程序必须有超时判断避免程序死等。2.2.3 数据位的读写精确的时序控制复位成功后就开始传输数据。每一位的传输都发生在一个固定的“时间槽”内由主机发出的下降沿同步信号开启。写“1”时间槽主机拉低总线1-15µs然后释放。在时间槽剩余的时间里总线由上拉电阻拉高。从机在时间槽开始后的15µs内采样总线采样到高电平即视为“1”。写“0”时间槽主机拉低总线至少60µs整个时间槽的宽度然后释放。从机在15µs采样点会采样到低电平即视为“0”。读数据时间槽主机拉低总线1-15µs后释放然后迅速将I/O口切换为输入模式。从机在检测到总线被释放后如果要发送“1”则什么都不做总线被上拉电阻拉高如果要发送“0”则主动拉低总线。主机在时间槽开始后的15µs左右采样总线电平得到数据位。关键经验所有时间槽的持续时间tSLOT必须在60µs到120µs之间并且每个时间槽结束后必须留有至少1µs的“恢复时间”总线保持高电平让器件为下一位传输做好准备。这个恢复时间在软件延时中必须体现。3. 硬件接口设计与软件驱动实现理论清楚了我们来看如何用C8A这颗具体的MCU来实现。C8A没有硬件1-Wire控制器所以我们需要用软件“模拟”出一个也就是所谓的“位碰撞”。3.1 硬件连接极简至上电路图简单到令人感动C8A的任一I/O口示例中使用PA0连接至DS2430A的DATA引脚。DATA引脚通过一个4.7kΩ的上拉电阻连接到5V电源。DS2430A的GND接地VDD引脚可以悬空依靠寄生供电或直接接电源增强可靠性。引脚配置要点C8A的I/O口需要配置为开漏输出模式或者通过软件模拟开漏行为。即输出“0”时引脚驱动为低电平输出“1”时引脚设置为高阻输入态依靠外部上拉电阻将总线拉高。示例代码中通过直接操作数据方向寄存器DDRA和数据寄存器PORTA来实现这一切换。3.2 软件驱动核心三个关键子程序驱动层的核心是三个用汇编语言精心编写的子程序它们严格遵循了1-Wire的时序规范。代码是基于2MHz的内部总线时钟编写的每个指令周期为0.5µs所有延时都通过指令循环精确计算。3.2.1RESET_PULSE建立通信链路这个子程序负责发起复位并检测应答。RESET_PULSE: bclr DATA, PORTA ; 主机拉低总线开始复位脉冲 lda #160T ; 延时循环计数实现 480µs 的低电平 J1: deca bne J1 bclr DATA, DDRA ; 将DATA引脚设为输入释放总线 ; ... 后续代码检测应答脉冲 ...避坑指南延时精度480µs是最小值必须保证。示例中用了约481µs的延时是安全的。在实际项目中如果MCU主频不同必须重新计算循环次数。应答检测窗口主机释放总线后不能立即检测需要等待一段时间示例中约15µs以避免总线上的振铃效应然后再在一个足够长的窗口内示例中约483µs持续采样寻找那个由从机产生的低电平脉冲。错误处理如果超时未检测到应答程序必须跳转到错误处理流程而不是死循环。示例中通过检查TEST变量来实现。3.2.2TXD发送一个字节此子程序将EEPROM_WRITE变量中的一个字节按照LSB在先的规则发送出去。TXD: ldx #8T ; 设置8位计数器 WRITE: bclr DATA, PORTA ; 拉低总线启动时间槽 nop ; 短暂延时满足 tLOW1 或 tLOW0 要求 asr EEPROM_WRITE ; 将发送字节右移最低位进入进位标志C bcc WRITE_ZERO ; 如果C0跳转到写0分支 ; 写1分支拉高总线 bset DATA, PORTA bra DELAY WRITE_ZERO: ; 写0分支保持总线为低 bclr DATA, PORTA ; ... 延时以维持足够的低电平时间 ... DELAY: ; 延时约52µs填充时间槽的剩余部分 lda #17T J8: deca bne J8 bset DATA, PORTA ; 确保总线恢复高电平 ; ... 恢复时间处理及循环 ...操作意图解析启动时间槽无论写1还是写0都以拉低总线开始。决定电平根据要发送的位迅速决定是拉高写1还是保持低写0。写1时低电平持续时间很短1-15µs写0时低电平需持续整个时间槽。保持与恢复用精确的延时填充时间槽的剩余时间然后确保总线为高并插入恢复时间为下一位传输做准备。3.2.3RXD接收一个字节此子程序从总线上读取8个位组合成字节后存入EEPROM_READ变量。RXD: ldx #8T READ: bclr DATA, PORTA ; 主机拉低总线启动读时间槽 nop bclr DATA, DDRA ; 关键将引脚设为输入释放总线 ; 短暂延时约7µs等待从机驱动总线稳定 lda #2T J9: deca bne J9 ; 采样时刻检查总线电平结果存入进位标志C brclr DATA, PORTA, IS_ZERO ; 如果总线为高 (IS_ONE) sec ; 设置C1 bra STORE_BIT IS_ZERO: clc ; 清除C0 STORE_BIT: ror EEPROM_READ ; 将C数据位移入接收字节 ; ... 延时完成本时间槽将引脚重新配置为输出并恢复高电平 ...核心技巧切换输入模式的时机主机拉低总线启动读时间槽后必须尽快将I/O口切换为输入模式高阻态否则无法读取从机发送的数据。这个切换动作本身就有几微秒的延时正好作为tLOWR的一部分。采样点从机会在主机启动时间槽后的tRDV典型15µs时间内将数据准备好。因此主机在释放总线并短暂延时后示例约7µs在15µs左右进行采样是可靠的。位组装利用带进位位的循环右移指令ror可以优雅地将每次采样到的位在C中移入接收字节。4. 完整操作流程与汇编代码剖析有了底层的读写位和复位函数上层的数据读写命令就是按协议手册组合调用这些函数。我们以应用笔记中的测试流程为例分析一个完整的“写入EEPROM并验证”的过程。4.1 命令序列与数据流测试主程序START完成以下操作这是一个非常标准的使用流程初始化配置PA0为输出并输出高电平。读取内存到暂存器RESET_PULSE-TXD(SKIP_ROM)-TXD(READ_MEM)。READ_MEM命令会将整个EEPROM阵列的内容拷贝到内部的暂存器RAM中为后续可能的修改做准备。写数据到暂存器RESET_PULSE-TXD(SKIP_ROM)-TXD(WRITE_SCRATCH)-TXD($06)-TXD($55)。向暂存器RAM的地址$06处写入一个测试数据$55。验证暂存器写入RESET_PULSE-TXD(SKIP_ROM)-TXD(READ_SCRATCH)-TXD($06)-RXD。从暂存器地址$06读回数据与$55比较确保暂存器写入正确。将暂存器内容复制到EEPROMRESET_PULSE-TXD(SKIP_ROM)-TXD(COPY_SCRATCH)-TXD($A5)。发送复制命令后必须紧跟一个验证密钥$A5。随后程序必须将DATA线保持高电平至少100ms调用NV_WAIT子程序以确保EEPROM编程所需能量。从EEPROM读取验证RESET_PULSE-TXD(SKIP_ROM)-TXD(READ_MEM)-TXD($06)-RXD。再次使用READ_MEM命令将EEPROM内容载入暂存器然后从暂存器地址$06读取验证数据$55已成功写入EEPROM。4.2 关键代码段解读让我们看看主程序中“写暂存器并复制到EEPROM”这一核心链路的代码实现; ... 前期复位和跳过ROM ... lda #WRITE_SCRATCH sta EEPROM_WRITE jsr TXD ; 发送写暂存器命令 ($0F) lda #$06 sta EEPROM_WRITE jsr TXD ; 发送目标地址 ($06) lda TEST_DATA ; TEST_DATA预定义为 $55 sta EEPROM_WRITE jsr TXD ; 发送要写入的数据 ($55) ; ... 验证暂存器写入 ... lda #COPY_SCRATCH sta EEPROM_WRITE jsr TXD ; 发送复制暂存器命令 ($55) lda #$A5 sta EEPROM_WRITE jsr TXD ; 发送验证密钥 ($A5) bset DATA, PORTA ; *** 关键保持DATA线为高电平 *** jsr NV_WAIT ; *** 关键延时100ms供EEPROM编程 ***这里有两个至关重要的点也是实际调试中最容易出错的地方验证密钥$A5COPY_SCRATCH和COPY_LOCK命令后必须紧跟这个特定的字节这是一个安全特性防止意外写入。100ms保持时间发送完验证密钥后主机必须将总线保持在高电平状态至少100ms。在这段时间里DS2430A正在利用“寄生供电”的能量对EEPROM单元进行编程。如果此时总线被意外拉低或主机进行其他操作导致总线活动很可能导致编程失败或数据错误。NV_WAIT子程序就是一个用循环实现的大约100ms的延时。4.3 应用寄存器操作的特殊性应用寄存器的操作流程与数据存储器类似但有本质区别锁定特性通过COPY_LOCK命令后跟密钥$A5可以将应用寄存器的暂存器内容复制到EEPROM并永久锁定。锁定后状态寄存器变为$FC且无法再执行WRITE_APP命令。直接读取锁定后使用READ_APP命令读取时数据直接来自EEPROM而非暂存器。一次性锁定操作只能执行一次。这在存储设备唯一ID或生产信息时非常有用。操作流程示例写入并锁定应用寄存器RESET_PULSE-SKIP_ROMWRITE_APP-[起始地址]-[要写入的多个数据字节]RESET_PULSE-SKIP_ROMREAD_SCRATCH(针对应用寄存器区域) -[起始地址]-RXD... (验证写入)RESET_PULSE-SKIP_ROMCOPY_LOCK-$A5保持DATA高电平100msRESET_PULSE-SKIP_ROMREAD_STATUS-$00(密钥) -RXD(应读到$FC确认锁定)此后该应用寄存器区域变为只读。5. 调试心得与常见问题排查基于这个项目我总结了一些在8位MCU上实现1-Wire接口特别是操作EEPROM时的实战经验和排查思路。5.1 时序问题最普遍的“幽灵”1-Wire通信对时序极其敏感。在C8A上我们是用软件循环实现延时的这就对中断非常敏感。问题现象通信不稳定时而成功时而失败复位脉冲能检测到但数据读写错误。排查与解决关闭中断在RESET_PULSE、TXD、RXD这三个核心子程序的执行过程中必须禁止所有中断。一个突然到来的中断服务程序会打乱精心计算的延时导致时间槽长度超标或恢复时间不足通信必然失败。可以在调用这些子程序前关中断调用后开中断。校准延时示例代码基于2MHz总线时钟。如果你的C8A使用外部晶振或不同频率的RC振荡器必须重新计算所有延时循环的计数值。使用示波器或逻辑分析仪观察DATA线上的波形与DS2430A数据手册中的时序图对比是调试时序的黄金标准。重点检查复位低电平时间是否480µs写0时间槽的低电平时间是否持续60-120µs读时间槽中主机释放总线后到采样点的时间是否在15µs左右每个时间槽结束后是否有至少1µs的高电平恢复时间上拉电阻与布线过长的导线、过大的分布电容会减缓总线上升沿可能导致从机采样错误。如果通信距离稍长或总线负载重可以适当减小上拉电阻如用2.2kΩ但需注意MCU引脚的电流驱动能力。5.2 EEPROM写入失败能量与流程问题现象数据能正确写入暂存器但执行COPY_SCRATCH后从EEPROM读回的数据不正确。排查与解决检查100ms保持时间这是首要怀疑对象。确认在发送COPY_SCRATCH/COPY_LOCK命令和验证密钥$A5之后程序是否立即将DATA线置高并保持了足够长时间用示波器查看这100ms内总线必须持续为高不能有任何毛刺或低电平脉冲。电源稳定性如果使用“寄生供电”模式在EEPROM编程期间电源电压必须稳定。可以在DS2430A的VDD引脚到地之间加一个0.1µF的退耦电容或者直接给VDD引脚连接稳定的电源如3.3V或5V放弃纯寄生供电以提高写入可靠性。操作流程错误必须严格遵守“写暂存器 - 读回验证 - 复制到EEPROM”的流程。不能跳过验证步骤。DS2430A的暂存器提供了回读校验机制确保要写入EEPROM的数据是正确的。5.3 多设备寻址ROM命令的使用当总线上有多个1-Wire设备可以是多个DS2430A也可以是其他1-Wire器件如温度传感器DS18B20时就不能再用SKIP_ROM命令了。正确流程发送RESET_PULSE所有设备回应。发送SEARCH_ROM命令通过“二进制搜索算法”逐一获取总线上所有设备的64位ROM码并存储起来。当需要对特定设备操作时RESET_PULSETXD(MATCH_ROM)-TXD(64位ROM码LSB先发)此时只有ROM码匹配的设备会响应后续的内存命令其他设备处于静默状态。然后发送READ_MEM、WRITE_SCRATCH等命令。注意事项SEARCH_ROM算法实现起来稍复杂需要处理位冲突。如果系统设备固定也可以在生产时就将各设备的ROM码烧录到主控MCU的ROM中直接使用MATCH_ROM。5.4 代码移植到其他MCU的要点虽然示例代码是针对MC68HC705C8A的但其思路适用于任何8位或32位MCU。I/O口操作抽象将SET_DATA_HIGH、SET_DATA_LOW、SET_DATA_INPUT、READ_DATA_PIN这几个操作封装成宏或函数。这样移植时只需修改这几个底层函数。延时函数重构将微秒级延时delay_us(n)和毫秒级延时delay_ms(n)作为独立函数实现其精度依赖于系统时钟。TXD和RXD中的精细延时可以基于delay_us()实现。时序常数化将tRSTL、tSLOT、tRDV等关键时间参数定义为常量方便根据不同的主频调整。状态机设计对于更复杂的应用可以考虑用状态机来管理1-Wire通信流程使代码更清晰易于处理超时和错误。最后我想说的是在资源受限的8位机时代这种用软件模拟复杂协议的做法是工程师的必备技能。虽然现在很多32位MCU都有更强大的外设但在某些对成本极度敏感、引脚数寸土寸金的场合用一根线解决存储问题依然有其独特的魅力。理解并掌握1-Wire这样的底层协议不仅能解决眼前的问题更能加深你对串行通信、时序控制和硬件软件协同的理解。希望这篇基于老芯片的详细剖析能为你当前或未来的嵌入式项目带来一些扎实的参考。