在物联网和嵌入式系统里,我们通常默认设备联网需要至少两根数据线,比如RS-485的A/B差分对,或者以太网的四对双绞线。但如果场景对速率要求不高,只希望远程读取一个传感器、判断一个开关状态或让执行器响应命令,完全可以用一根普通导线加一根地线完成半双工数据交换。这根线既要承担主机下发命令,也要承担从机返回数据,核心是把时间切成不同的槽位,并约定好开漏驱动与上拉电阻的电平竞争规则。

一、单线通信的物理基础:分时复用与开漏总线
单线通信之所以能用一条线同时收发数据,是因为它不依赖独立的发送和接收通道,而是把所有设备都挂到同一条开漏总线上。主机和从机都只能把总线拉低,不能主动推高。总线空闲时由外部上拉电阻拉到高电平,谁需要发送逻辑0就短暂拉低导线,发送逻辑1则释放总线。这样即使多个设备同时操作,也不会出现推挽输出那种短路冲突。
这种设计的代价是通信必须严格分时。主机发起一次通信后,从机只能在约定的时间窗口内拉低总线作为应答。为了让大家识别不同宽度的低电平脉冲,协议层会规定几个关键时间,比如复位脉冲至少480微秒,读位采样在10微秒左右完成。只要软件控制GPIO的延时足够精确,一根线完全可以承载稳定的短距离数据交换。
// GPIO模拟1-Wire复位与位读写,时序参考标准速度模式
bool ow_reset(void) {
pinMode(OW_PIN, OUTPUT);
digitalWrite(OW_PIN, LOW);
delayMicroseconds(480); // 主机复位脉冲至少480us
pinMode(OW_PIN, INPUT); // 释放总线,由上拉电阻拉高
delayMicroseconds(70);
bool presence = digitalRead(OW_PIN) == LOW;
delayMicroseconds(410);
return presence; // true表示检测到从机存在
}
void ow_write_bit(bool bit) {
pinMode(OW_PIN, OUTPUT);
digitalWrite(OW_PIN, LOW);
delayMicroseconds(bit ? 2 : 65);
pinMode(OW_PIN, INPUT);
delayMicroseconds(bit ? 65 : 5);
}
bool ow_read_bit(void) {
pinMode(OW_PIN, OUTPUT);
digitalWrite(OW_PIN, LOW);
delayMicroseconds(2);
pinMode(OW_PIN, INPUT);
delayMicroseconds(10);
bool value = digitalRead(OW_PIN);
delayMicroseconds(53);
return value;
}
上面这段代码没有使用专用外设,只靠普通GPIO拉低与读取电平。它的问题在于延时期间如果被更高优先级中断打断,时序就会偏移,因此在实际项目中通常会关闭中断或使用定时器驱动。即便如此,这种实现已经足以验证一根线读取DS18B20这类传感器的基本流程。
二、1-Wire协议层:复位、应答与命令码
有了物理层的位传输能力,还需要规定一次完整交互的帧结构。1-Wire协议把每次操作分成几个阶段:主机先发复位脉冲,所有从机检测到总线上出现长低电平后,在释放后的60至240微秒内拉低总线作为存在脉冲。主机读到存在脉冲后,再发送8位ROM命令和8位功能命令。
ROM命令用来选择要通信的从机。单个设备时可以直接发送跳过ROM命令0xCC,后面跟随温度转换命令0x44或读暂存器命令0xBE。从机收到命令后开始执行动作,主机则根据命令类型等待转换时间或立即读取数据。下面是一个读取单个DS18B20温度的简化流程。
// 读取单个DS18B20的原始温度值,假设已连接OneWire引脚
bool request_temperature(void) {
if (!ow_reset()) return false;
ow_write_byte(0xCC); // SKIP ROM:跳过64位地址
ow_write_byte(0x44); // CONVERT T:启动温度转换
return true;
}
float read_temperature(void) {
if (!ow_reset()) return NAN;
ow_write_byte(0xCC);
ow_write_byte(0xBE); // READ SCRATCHPAD:读暂存器
uint8_t lsb = ow_read_byte();
uint8_t msb = ow_read_byte();
int16_t raw = (msb << 8) | lsb;
return raw / 16.0f;
}
这里先启动转换,再等待约750毫秒读取暂存器。需要注意,0xCC只适合总线上只有一个从机的场景;如果总线上同时存在多个设备,0xCC会导致所有设备同时响应,数据冲突。多设备场景必须使用64位ROM ID进行精确寻址,这就引出下面要讨论的搜索问题。
三、多设备寻址:ROM ID与二叉树搜索
每个1-Wire从机在出厂时都有一个全球唯一的64位ROM ID,其中前8位是家族码,后面48位是序列号,最后8位是CRC校验。当总线上挂载多个传感器时,主机不能靠跳过ROM命令同时读取数据,但可以利用这个唯一ID逐个建立通信。搜索ROM算法把64位地址的每一位都当作分支条件,让所有从机在同一个位上主动驱动,通过读取总线状态判断是否存在0和1两个分支。
搜索过程本质上是遍历一棵二叉树。主机先发送搜索ROM命令0xF0,然后逐位读取,每个位都读取两次:第一次读取所有从机该位的真实值,第二次读取该位的反码。如果读到10表示只有0分支,01表示只有1分支,00表示同时存在两个分支,11表示总线上没有设备。主机根据读到的信息选择先搜索哪一个分支,递归或循环处理直到得到64位完整ID。
// ROM搜索关键循环:每次从总线读取2位,判断搜索方向
uint8_t rom_bit = 0;
uint8_t rom_complement = 0;
for (uint8_t i = 0; i < 64; i++) {
rom_bit = ow_read_bit();
rom_complement = ow_read_bit();
if (rom_bit == 1 && rom_complement == 1) {
break; // 总线无设备响应
}
if (rom_bit == 0 && rom_complement == 0) {
// 该位存在0和1两个分支,需要记录分歧点
last_discrepancy = i;
}
// 根据搜索方向写入0或1,继续下一层
ow_write_bit(search_direction ? 1 : 0);
}
上述代码只展示了核心判断逻辑,完整算法还需要维护上次分歧点,以便下次搜索从另一个分支继续。搜索过程比较耗时,但通常只在设备入网或主机启动时执行一次。执行完成后,主机就可以用匹配ROM命令加48位序列号精确访问某个传感器,实现单总线上几十个节点的稳定响应。
四、桥接到HTTP:让单线网络真正响应上层请求
单线网络本身只能在一个小范围、低速率的总线上完成采集和控制。要让浏览器、后端服务或云端平台也能访问这些数据,需要一块具备Wi-Fi或以太网能力的网关。ESP32是常见选择,它既可以用GPIO模拟1-Wire读取传感器,又可以直接连接Wi-Fi,把温度、开关状态等数据以HTTP接口暴露出去。
在这种架构里,单线传感器负责现场数据采集,ESP32负责协议转换。传感器和网关之间仍然只有一根数据线,但网关可以处理寻址、重试、缓存等逻辑,上层看到的只是一个普通的HTTP API。下面是一段简化的ESP32 Arduino代码,实现定时读取DS18B20并通过HTTP POST上报。
#include <WiFi.h>
#include <HTTPClient.h>
#include <OneWire.h>
#include <DallasTemperature.h>
const char* ssid = "your-ssid";
const char* password = "your-password";
OneWire oneWire(4);
DallasTemperature sensors(&oneWire);
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
sensors.begin();
}
void loop() {
sensors.requestTemperatures();
float temp = sensors.getTempCByIndex(0);
HTTPClient http;
http.begin("http://192.168.1.50:8080/api/sensor");
http.addHeader("Content-Type", "application/json");
String body = "{\"temperature\":" + String(temp) + "}";
int code = http.POST(body);
Serial.printf("HTTP状态码:%d\n", code);
http.end();
delay(5000);
}
这里使用DallasTemperature库屏蔽了复位、写位、读位和ROM命令细节,开发时只需关注传感器地址与读取结果。实际部署时建议把HTTP客户端重试、鉴权、TLS证书校验和请求超时都加上,否则现场网络抖动会直接导致数据缺口。
除了HTTP,也可以把单线网络接入MQTT。网关订阅控制主题,把收到的开关指令翻译成匹配ROM命令和写位序列,从而让远端设备真正动作。由于单线总线是半双工的,网关对下行命令和上行采样的调度必须串行,不能同时进行。
五、工程落地注意点与贡献方向
单线通信的优势是布线简单、节点成本低,但它也有明显局限:标准速度大约为15.4kbit/s,过载模式下也只有125kbit/s;总线长度通常不建议超过几米到几十米,距离越长越容易受到分布电容影响,导致上升沿变慢。因此它不适合传输视频、音频或大量日志,更适合温度、湿度、开关状态等低频数据。
如果要把单线方案用在现场,至少需要处理几个问题:一是供电方式,可以从数据线寄生供电,也可以单独拉一根电源线;二是从机数量增加后,搜索时间和冲突概率都会上升;三是单线协议缺少原生安全机制,谁接入总线都能读取或发送数据。对于这些方向,开源项目仍然有大量可以贡献的空间。
- 协议栈容错:增加超时重试、总线恢复和坏节点隔离。
- ROM搜索优化:减少搜索过程中的无效分支,缓存设备列表。
- 网关安全:在HTTP和MQTT接入层加入设备身份与传输加密。
- 跨平台驱动:把时序代码抽象到定时器硬件,减少CPU占用。
总的来说,一根线确实可以让设备响应上层请求,前提是把物理层、寻址层和网关层做扎实。如果你对这个方向感兴趣,完全可以从实现一个稳定的GPIO模拟1-Wire协议栈开始,再逐步完善搜索算法、桥接服务和现场部署工具。

