
第一次接触Onewire是在一个冷链温度记录仪的项目上。当时要在一个狭小的探头里塞进三路温度传感器PCB面积紧张到连0603的电阻都得精打细算板内I2C总线的走线距离又碰到干扰问题SPI更是要额外占用三根线。折腾了一圈最后被一个老工程师点醒试试DS18B20一根数据线加一根地线就能把所有传感器串起来。那是我第一次真切感受到什么叫用最少的硬件做最多的事——一根线几十米距离十几个设备就这么解决了整个项目的测温网络。这些年做嵌入式看到太多人对Onewire的认知停留在哦那个读温度传感器的协议其实它的价值远不止于此。Onewire是Dallas Semiconductor现在并入ADI设计的一种单总线通信协议最核心的特征就是数据线和电源线在多数应用场景下可以合并主机和从机之间只需要一根双向数据线DQ和公共地线。它的极简程度在串行通信协议里几乎没有对手。这篇就把我对Onewire的理解、底层时序、驱动写法、Linux/RTOS下的落地经验、多设备组网以及选型边界一次讲透给正在选型或者准备手写驱动的朋友一份能直接参考的实战笔记。1. 为什么说Onewire是一根线的极致通信1.1 从物理连接看Onewire的与众不同嵌入式通信里大家最熟悉的几个协议UART至少TX和RX两根线点对点。I2C两根线SCL时钟、SDA数据多设备靠地址区分半双工。SPI至少四根线SCLK、MOSI、MISO、CS全双工速率高。CAN两根差分线CANH、CANL抗干扰强适合工业现场。Onewire呢一根DQ线双向传输数据。再加上地线总共两条物理导线。更极端的情况——如果使用寄生供电模式VDD引脚直接接地从机完全靠DQ线上的高电平偷电工作那整个传感器网络真的就是一根信号线加一根地线。这个设计思路在今天的通信架构里看起来有点反潮流因为大家都在追求高带宽、差分信号、多通道并行而Onewire反过来做减法。它的核心哲学是如果应用场景不追求高速率那么每一根多余的导线都是成本、是故障点、是PCB面积的浪费。1.2 与I2C/SPI/UART的真实对比少一根线的代价和回报先看一张我在实际选型时常用的对比表总线信号线数标准速率典型距离多设备典型应用UART2TX/RX115200 bps~数Mbps15米左右点对点调试、串口屏、GPSI2C2SCL/SDA100 kbps~3.4 Mbps1~2米板内7位/10位地址板内传感器、EEPROMSPI4SCLK/MOSI/MISO/CS数十Mbps1米左右靠片选扩展Flash、ADC、显示屏Onewire1DQGND15.4 kbps标准100米良好布线理论无限实际受负载温度传感、电池管理、EEPROM、身份识别光看速率Onewire在表格里垫底15.4kbps的带宽在今天的嵌入式系统里甚至算不上低速而是龟速。但恰恰是这个龟速换来了极简单的物理层一根线、开漏输出、上拉电阻搞定一切。关键点在于回报。我在一个农用大棚环境监测的项目里用一根两芯屏蔽线串了八个DS18B20传感器分散在两百米的温室范围内。如果改用I2C200米线缆的电容和压降早就把总线拖垮了如果改用RS485每个节点需要额外的收发器和地址配置成本和复杂度直接翻两倍。Onewire在这种低速、分布式、传感器密集的场景里是物理层最优解。还有一个经常被忽略的优势传感器地址在出厂时已经固化了。每个Onewire器件都有一个全球唯一的64位ROM码不需要像RS485那样逐台设置地址也不需要像I2C那样担心地址冲突I2C某些型号地址引脚有限挂多了还得用多路开关。这一点在批量部署和维护时能省大量人工。1.3 哪些场景真正需要Onewire根据我这几年落地的项目经验Onewire适合的场景主要有这五类分布式温度采集冷链运输、温室大棚、机房环境、粮仓测温。这些场景温度传感器分布广、数量多、对速率没要求、但要求布线简单。空间受限的可穿戴和消费电子耳机、智能手环里塞温度传感器PCB面积紧张一根线比两根线友好得多。需要身份识别的附件管理打印机墨盒、电池包、传感器探头用Onewire EEPROMDS2431等存放序列号、校准系数。插上就能读不需要额外的连接器引脚。板内近距离低速通信比如主板上的温度监控、电源模块的配置存储一根线走线方便EMC压力也小。需要线缆最少的工业现场哪怕现场只有几十米每少一根芯电缆成本、端子数量、故障排查难度都在下降。2. 底层时序拆解一个位到底是怎么传的要真正用好Onewire不能只会调库函数必须把它的时序吃透。这个协议精妙的地方在于它没有独立的时钟线时钟完全靠主机主动产生从机的动作全部由主机的拉低和释放来触发。这意味着时序窗口非常敏感差几微秒位就可能读错。2.1 三个关键时机复位脉冲、存在脉冲、时间片一次完整的Onewire通信分为三个阶段复位阶段主机把DQ线拉低480μs以上然后释放由上拉电阻把线拉高。释放后等待15~60μs如果总线上有从机从机会主动把线拉低60~240μs这个低电平就是存在脉冲表示我在呢。主机在释放线之后的一个窗口内检测这个低电平就知道总线上有设备。这一步相当于两个人见面时的握手。ROM命令阶段复位成功之后主机发送一个ROM命令比如读ROM0x33、匹配ROM0x55、搜索ROM0xF0、跳过ROM0xCC等。如果是单设备直接跳过ROM节省时间多设备则需要通过搜索ROM来枚举所有设备的64位ID。功能命令阶段ROM命令指定了要操作哪个设备功能命令指示这个设备干什么比如DS18B20的启动温度转换0x44、读取暂存器0xBE。功能命令结束后数据在时间片slot里逐个bit进行传输。2.2 写0与写1的微小差别Onewire最精巧、也是最容易被新手忽略的地方就是写0和写1的区别不是电平高低而是低电平的持续时间不同。写1时隙主机把线拉低1~15μs然后立即释放线被上拉为高整个时隙持续60~120μs。从机在主机释放后采样看到的是高电平记为1。写0时隙主机把线拉低并持续保持低电平60~120μs整个时隙都是低然后释放。从机采样看到的是低电平记为0。一句话记忆法写1是意思一下拉低写0是全程按住拉低。每个时隙结束后中间最好留至少1μs的恢复时间然后才开始下一个bit。2.3 读时序为什么必须在特定时刻采样读数据时主机同样产生一个时隙拉低1~15μs释放。但关键是释放后要立刻15μs之内采样DQ线的电平。为什么因为从机的行为是检测到主机拉低时隙的下降沿之后如果是想传0就把线拉低并保持到整个时隙结束如果是想传1就不动作线由上拉电阻自然拉高。这个机制决定了主机必须主动拉低产生时隙然后马上释放并快速采样。如果在释放后等太久才采样可能已经错过从机的低电平窗口如果采样太早又可能误读到主机自己拉低产生的低电平。实际操作中我一般在释放后延时几微秒大约1~2μs然后快速读引脚。DS18B20的手册上写的是释放后15μs内完成采样保守起见我通常把采样点放在释放后5μs左右。2.4 时序参数速查表这是我从DS18B20数据手册里提炼并实测定标过的一份参数表时序动作最小典型最大说明复位低电平480μs480μs960μs太短从机不识别太长总线卡死复位后存在脉冲等待15μs30μs60μs这个窗口内检测低电平存在脉冲宽度60μs120μs240μs从机拉低主机读写1低电平时间1μs6μs15μs之后必须释放写0低电平时间60μs60μs120μs期间不释放读时隙低电平时间1μs6μs15μs之后必须释放读采样点释放后1μs释放后5μs释放后15μs采样点晚于15μs会读错时隙总长度60μs60μs120μs位与位之间留1μs恢复时间提示这些参数不是大概就行Onewire对时序的容差远没有I2C宽松。我在调驱动的时候吃过亏复位脉冲只有300μs时不时的存在脉冲检测失败读采样点放到了20μs每个字节读出来都像随机数。建议在写驱动之前先用逻辑分析仪抓一遍主机发出的波形确认时序落在上表范围内。3. 实战手写一份可跑的DS18B20驱动协议说得再多不如代码来得直接。这一节以最常用的DS18B20为例从头写一份可以在STM32、ESP32以及任何带GPIO的MCU上跑的驱动。这里不依赖HAL库的现成接口用最原始的GPIO操作来模拟时序方便你移植到任何平台。3.1 硬件连接和上拉电阻选择先看电路。DS18B20的DQ引脚是开漏输出结构所以必须在外部加一个上拉电阻。这个电阻的取值直接影响波形质量4.7kΩ最常见短距离10米和单设备场景首选。2.2kΩ~3.3kΩ线缆较长10~30米或者挂多设备把上拉拉低一点上升沿更快抗干扰更好。1kΩ长线50米以上或者寄生供电模式的严苛环境但要考虑驱动能力和功耗。电路连接很简单VCC(3.3V/5V) ──┬── 4.7kΩ ──┬── DQ (GPIO) │ │ DS18B20 VDD │ DS18B20 DQ ───┘ DS18B20 GND ── GND注意DS18B20支持3.3V和5V供电但3.3V供电时的时序参数会稍微严格一点而且如果总线上有多个设备3.3V时上拉电阻可能要降到2.2kΩ才能保证信号质量。3.2 仿真的关键微秒级延时的实现软件层最大的拦路虎是微秒级延时。Onewire的时序操作全部在几十微秒的尺度普通rtos_delay和HAL_Delay都太粗糙而且在不同优化等级下行为差异很大。我惯用两种方案方案ADWT时钟计数器Cortex-M核这个最准靠内核调试单元里的32位计数器做精确us延时不受中断影响但要手动使能。static void delay_us_init(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; } static void delay_us(uint32_t us) { uint32_t start DWT-CYCCNT; uint32_t ticks us * (SystemCoreClock / 1000000U); while ((DWT-CYCCNT - start) ticks); }方案B纯循环延时简单但要看编译器生成的汇编优化等级别乱改。static void delay_us(uint32_t us) { for (volatile uint32_t i 0; i us * 8; i); }方案B里的8必须用示波器实测校准直接打字My搞了一个USB逻辑分析仪实测循环延时在-O2优化下大概每us循环8次换编译器之后又变过。——所以能用DWT就用DWT。3.3 核心驱动代码复位、读写位、读写字节、温度读取直接上完整可用的驱动框架。#include onewire.h #include gpio.h // 你自己平台的GPIO控制头文件 #define OW_LOW() ow_gpio_write(0) // 拉低DQ #define OW_HIGH() ow_gpio_write(1) // 释放DQ实际由上拉拉高 #define OW_READ() ow_gpio_read() // 读取DQ电平 // 复位总线返回1表示检测到存在脉冲 uint8_t ow_reset(void) { uint8_t presence 0; OW_LOW(); delay_us(480); // 复位低电平 OW_HIGH(); delay_us(60); // 等待存在脉冲窗口 presence (OW_READ() 0) ? 1 : 0; // 低电平表示有设备 delay_us(420); // 补足复位周期 return presence; } // 写一个bit void ow_write_bit(uint8_t bit) { if (bit) { OW_LOW(); delay_us(6); // 拉低6us OW_HIGH(); // 释放写1 delay_us(60); // 时隙剩余时间 } else { OW_LOW(); delay_us(60); // 全程拉低写0 OW_HIGH(); delay_us(6); // 补足整个时隙长度 } } // 写一个字节低位先出 void ow_write_byte(uint8_t byte) { for (uint8_t i 0; i 8; i) { ow_write_bit(byte 0x01); byte 1; } } // 读一个bit uint8_t ow_read_bit(void) { uint8_t bit; OW_LOW(); delay_us(6); // 拉低产生读时隙 OW_HIGH(); // 释放 delay_us(5); // 释放后采样点 bit OW_READ(); delay_us(50); // 补足时隙 return bit; } // 读一个字节低位先出 uint8_t ow_read_byte(void) { uint8_t byte 0; for (uint8_t i 0; i 8; i) { byte 1; if (ow_read_bit()) { byte | 0x80; } } return byte; }接着是DS18B20的具体操作// 启动温度转换 void ds18b20_convert(void) { ow_reset(); ow_write_byte(0xCC); // 跳过ROM ow_write_byte(0x44); // 启动转换 } // 读取温度返回温度值单位0.0625℃ int16_t ds18b20_read_temp_raw(void) { uint8_t data[2]; ow_reset(); ow_write_byte(0xCC); ow_write_byte(0xBE); // 读暂存器 data[0] ow_read_byte(); // 温度低字节 data[1] ow_read_byte(); // 温度高字节 ow_reset(); // 通信结束 return (int16_t)((data[1] 8) | data[0]); } float ds18b20_get_temp(void) { int16_t raw ds18b20_read_temp_raw(); return raw * 0.0625f; // 12位分辨率LSB 0.0625℃ }主函数调用流程void main_loop(void) { ds18b20_convert(); delay_ms(750); // 12位分辨率需要750ms绝对不能少 float temp ds18b20_get_temp(); printf(temp %.2f C\n, temp); }3.4 CRC校验为什么不建议省很多例程只读温度后直接返回不做CRC校验这在短期实验没问题但在产品里就是个隐患。DS18B20暂存器占9字节温度低、高字节TH、TL报警值配置寄存器3个保留字节最后是CRC校验字节。CRC算法是变体的CRC-8多项式为x^8x^5x^41即0x31反向计算用0x8C。推荐至少对读出来的9字节做一次CRC校验如果CRC不对就重读连续几次都错就判定设备异常不要使用错误数据。尤其在长线、电磁环境复杂的场景CRC是判断通信是否可靠的关键手段。驱动写好之后把温度读出来放到串口上跑一整夜统计CRC错误率如果错误率高于预期就要从硬件和时序两方面找原因。4. Linux/RTOS底下跑Onewire有哪些坑嵌入式环境很少是裸机一旦上了Linux或者RTOSOnewire的调试难度会陡增。下面说几个我实际踩过的坑。4.1 先从Linux内核w1子系统说起如果用的是Linux尤其是带设备树的嵌入式平台imx6ull、RK3288、全志等最省事的方案是直接用内核自带的w1_gpio驱动。设备树里加一段onewire0 { compatible w1-gpio; gpios gpio0 3 GPIO_ACTIVE_HIGH; // 引脚号和控制器按实际改 pinctrl-names default; pinctrl-0 pinctrl_w1; };然后内核就会在/sys/bus/w1/devices/下生成以28-开头的目录28是DS18B20的家族码每个目录对应总线上一个设备。读取温度只需要cat /sys/bus/w1/devices/28-xxxxxxxxxxxx/w1_slave输出两行第一行末尾是CRC判定第二行是txxxxx单位是0.001℃比如t23437就是23.437℃。这个方法好在哪里内核驱动把复位、时序、搜索、CRC全给你干完了应用层只需要读文件。热插拔的传感器也能自动识别极大减少工作量和 bug 面。我在几个项目里直接用这套方案稳定性很好。但要注意w1_gpio是gpio模拟时序内核里会有临界区操作如果系统里其他驱动的中断非常频繁时序仍然有被破坏的风险。这种情况下可以考虑外挂一个Onewire转UART的桥接芯片或者用一个专门的IO扩展器。4.2 时序模拟在操作系统下的困境中断和调度在FreeRTOS这类RTOS里任务调度器本身就可能打断你的GPIO时序操作。前面提到的一个bit时隙只有60μs而很多RTOS的tick中断1ms一次虽然不会打断60μs的操作但更高优先级的中断ISR如果执行超过几十微秒就会破坏时序。我采用的方案是portENTER_CRITICAL(); ow_write_byte(0xCC); ow_write_byte(0x44); portEXIT_CRITICAL();读温度转换结果时也是一样9字节的读操作要连续执行中间进一次中断就可能让一个bit读错。临界区的代价是关中断时间变长但这9个字节在标准速度下大约需要9870μs5ms左右关中断5ms在大多数RTOS里都是不可接受的。所以我通常把温度读取放在一个专用任务里这个任务优先级最高只做Onewire操作而且把整个读操作拆成转换期间让出CPU读暂存器时短暂关中断void temp_sensor_task(void) { while (1) { ds18b20_convert(); vTaskDelay(pdMS_TO_TICKS(800)); // 转换期间睡觉让出CPU portENTER_CRITICAL(); int16_t raw ds18b20_read_temp_raw(); portEXIT_CRITICAL(); // 把raw存储到某个共享变量 vTaskDelay(pdMS_TO_TICKS(1000)); } }这样关中断的时间被压缩到了读暂存器的几个毫秒以内在大多数产品里是可接受的。如果对中断延迟极度敏感那只能换方案——用专门的硬件Onewire控制器或者干脆换I2C传感器。4.3 半双工超时、设备掉线和总线恢复策略Onewire半双工的本质决定了主机必须严格控制拉低和释放的时机。设备掉线最常见的表现是复位检测不到存在脉冲。读到全0xFF或者全0x00。温度值突然跳变到85℃DS18B20上电默认值或者-55℃传感器短路。我在一个数据采集器项目里遇到一种诡异现象某一路传感器每隔几个小时就报一次85℃后来用示波器抓到罪魁祸首——线缆接头氧化导致接触电阻增大复位时的存在脉冲变得很弱主机误读成设备在线但后续数据全是垃圾。最后的处理是软件上增加连续3次复位失败才判断设备离线。每次读取前先检测存在脉冲存在脉冲缺失就做一次热复位流程拉低480μs释放再拉低480μs。数据异常时强制重新搜索总线而不是继续用之前枚举出来的设备列表。5. 多设备组网64位ROM能支撑起多大的家族Onewire真正让人上头的能力是一条总线上挂多个设备。我最多在一根总线上挂过24个DS18B20实际还能更多但受限于线缆电容和上拉驱动能力超过一定数量后就需要降低上拉电阻或增强驱动。5.1 每个传感器出厂既有的身份证每个Onewire设备内部都有一个64位激光刻录的ROM码结构如下字段位数含义家族码8 bit区分设备类型DS18B20是0x28序列号48 bit全球唯一出厂分配CRC8 bit对前56位做CRC-8校验这64位ROM相当于传感器的身份证号主机通过ROM命令来识别和寻址设备。因为序列号是全球唯一的所以不存在I2C那种两个同型号传感器地址冲突的问题。5.2 单总线搜索算法怎么做到逐个点名总线上一开始并不知道挂了哪些设备需要用一个搜索算法把所有ROM码找出来。搜索ROM命令是0xF0。搜索过程的核心思路是用二分法回溯主机复位后发送0xF0搜索命令。对ROM码的第0位主机发起一个读时隙读到的第一个bit是所有从机该位取反之后的与再发起一个读时隙读到的第二个bit是所有从机该位的与。根据两位组合判断11总线上没有设备。00该位存在冲突说明至少一个设备此位为0、另一个为1。01所有设备该位为0。10所有设备该位为1。出现冲突时主机先选择一条分支比如先走0并记录当前位的位置然后发送一个写时隙把选定值写给总线让所有该位不等于这个值的设备退出本次搜索。继续下一位重复直到64位全部读完得到第一个完整ROM码。再次复位从记录的最近一个冲突位开始选择另一条分支继续搜索下一个设备。这个过程类似在一棵二叉树上做深度优先遍历每个叶子对应一个设备。算法不难但坑很多我第一次自己实现时就在冲突位记录上栽了跟头——必须记录最近一次选0的冲突位回退的时候才能保证不重复。实际项目中我不会每次上电都重新搜索总线而是把搜到的ROM码存储到EEPROM或者Flash里下次上电直接按列表逐个匹配。重新搜索只作为发现新设备或者设备掉线重找的兜底逻辑。5.3 寄生供电模式的工作原理和限制寄生供电是Onewire又一个省硬件的绝活DS18B20的VDD引脚直接接地所有电能都从DQ线上窃取。内部有一个电容在DQ为高电平的时候充电在DQ为低电平或者温度转换期间放电维持工作。寄生模式的最大限制是温度转换期间电流需求很大可达1.5mA如果此时DQ线上的上拉电阻太大或线缆太长电压跌落超过传感器的最低工作电压转换就会失败甚至设备复位。所以寄生供电模式下要满足两个条件上拉电阻不要超过2.2kΩ长线最好用1kΩ。在发出0x44转换命令后主机要主动把DQ线拉高并保持不能靠上拉电阻或者使用MOSFET强上拉电路给从机提供足够的转换电流。实际项目中如果传感器离主机超过几米我基本不推荐寄生供电。一来长线供电压降风险高二来寄生供电时DQ线上有较大电流变化对信号质量有影响。如果真要用寄生供电就用强上拉电路别省那个三极管和电阻的成本。6. Onewire不是万能的选型时的边界判断不是所有场景都适合Onewire。这篇文章写到这里我必须把什么时候不要用它也讲清楚免得你踩了坑反过来骂协议垃圾。6.1 速率、距离、可靠性的天花板先给Onewire划一条能力边界速率标准模式15.4 kbpsoverdrive模式也就100kbps。想靠它传音频、图片、日志流趁早放弃。它适合的是传感器数据这个量级温度、湿度、电压、电量、序列号、校准参数。距离理论上100米是可能的但需要优质线缆和合理的上拉设计。超过50米我更建议用RS485虽然多一根线但差分信号在长线和强干扰环境下的优势是碾压性的。实时性Onewire没有中断线从机无法主动上报数据。主机必须周期性地轮询所有设备。如果系统要求毫秒级响应Onewire会让你抓狂。可靠性单线制在物理层天然没有差分抗共模干扰的能力。在变频器、大功率电机附近干扰可能直接串进总线。这种场景老老实实上CAN或者RS485。我自己的选型标准是温度测量为主、速率要求低、设备数量多、布线要精简——这四个条件同时满足我才会选Onewire。只要有一条不满足换总线。6.2 什么情况下我劝你换I2C或SPI如果你要传的数据量稍大、或者需要全双工交互、或者对时序稳定性要求高别用Onewire。典型反面场景ADC读取和音频采集速率不够SPI一脚油门的事。多字节固件升级往Onewire EEPROM里写一页数据要发一大堆命令花几秒钟写个128KB的固件能等到天荒地老。电池管理系统里的AFE和库仑计这些芯片通常用I2C或SPI接口天然就是那两个硬要用Onewire反而要加逻辑转换。高速闭环控制里的传感器反馈I2C都能到400kbps甚至1MbpsOnewire的15.4kbps根本跟不上控制周期。需要大量自定义寄存器配置的复杂外设Onewire的指令集和暂存器模型过于简单用起来很憋屈。6.3 实测中一根线方案最后变成双线的原因说个真相很多标榜单总线的产品最后的物理连接其实是三根线——VCC、DQ、GND。只有DS18B20这种能寄生供电的器件才可能把VCC和GND并一起真正变两根线。即便如此为了可靠性我大多数项目里都老老实实接了VCC没有用寄生供电。因为寄生供电省下的那一根线换来的是转换期间电压不稳、时序变敏感最后排查问题的时间成本远超省下的那根线。还有更隐蔽的情况Onewire器件内部是开漏输出但有些MCU的GPIO弱上拉不够强你不得不在板上加外部上拉电阻。电阻一端接DQ另一端接VCC。这么一看板上其实又多了两个连接点。所以最少的硬件是相对的关键看应用场景是否承受得起这份极简带来的代价。每次测试新的Onewire总线布局我都会先用逻辑分析仪抓一遍波形再写代码。几十微秒的时序用肉眼盯着逻辑分析仪的窗口看比读一万遍手册都有效。我个人体会是Onewire这个协议属于那种看着简单、用起来要敬畏的类型。它用最少的东西换取最广的适用范围但前提是设计者对时序有足够的理解和耐心。如果你在产品里驾驭好了它后续的传感器选型、布线采购、生产测试都会变得特别省心。希望这篇能帮你少走我当初走过的弯路。