1. OneWire单总线技术解析
单总线技术最吸引人的地方在于它的极简主义设计——仅需一根数据线就能实现双向通信。这种设计理念最早由Dallas Semiconductor(现已被Maxim Integrated收购)提出,特别适合资源受限的嵌入式系统。想象一下,在传统I2C或SPI总线中,我们需要至少两条线(数据线+时钟线)才能实现通信,而OneWire将这个数字减少到了极致。
注意:虽然称为"单总线",但实际应用中仍需连接地线(GND),所以严格来说需要两根物理连线。
1.1 协议工作原理
OneWire协议的精妙之处在于它巧妙地利用了时间域来实现数据编码。它采用两种不同的脉冲宽度来区分逻辑"1"和"0":
- 逻辑"1":主机拉低总线15μs以内
- 逻辑"0":主机拉低总线60μs以上
这种脉宽调制方式使得从设备可以通过检测总线低电平的持续时间来解码数据。为了确保通信可靠,协议还定义了严格的时序要求:
| 时序参数 | 典型值 | 说明 |
|---|---|---|
| tRSTL | 480μs | 复位脉冲低电平时间 |
| tPDHIGH | 15-60μs | 从设备响应窗口 |
| tSLOT | 60μs | 时隙周期 |
在实际应用中,这些时序参数可以通过OneWire库的timing()函数进行调整,以适应不同设备的需求。
1.2 电气特性考量
单总线设计带来了一些独特的电气特性挑战:
- 上拉电阻选择:通常使用4.7kΩ上拉电阻,但在长距离或高干扰环境中可能需要减小阻值
- 总线电容限制:总线上所有设备的寄生电容总和应小于800pF
- 供电方式:DS18B20支持寄生供电(仅需两根线)或外部供电(三线制)
我曾在一个工业项目中遇到总线过长导致通信失败的问题,最终通过以下措施解决:
- 将上拉电阻从4.7kΩ降至2.2kΩ
- 在总线中段增加一个缓冲器
- 采用外部供电模式替代寄生供电
2. DS18B20温度传感器详解
DS18B20是OneWire总线上的明星器件,它的高精度(±0.5°C)和宽温度范围(-55°C至+125°C)使其成为嵌入式温度监测的首选。这个小小的三引脚器件内部其实相当复杂:
2.1 内部结构解析

- 64位ROM:包含8位家族码(0x28)、48位序列号和8位CRC
- 温度传感器:直接输出数字值的温度测量单元
- 配置寄存器:控制转换精度(9-12位)和报警阈值
- 暂存器:存储温度读数和配置参数
2.2 温度转换流程
- 主机发送Convert T命令(0x44)
- DS18B20开始温度转换(转换时间取决于精度设置)
- 主机可以通过Read Power Supply(0xB4)查询供电模式
- 转换完成后,主机发送Read Scratchpad(0xBE)读取温度值
提示:12位精度下转换时间最长可达750ms,在低功耗应用中可考虑使用9位精度(93.75ms)
3. 硬件连接与初始化
3.1 典型连接方案
bash复制+---------------+
| MCU |
| | 4.7kΩ
| GPIO2 --------+----/\/\/---- VCC
| | |
| GND ----------+----+-------- GND
+---------------+ |
|
+--+--+
|DS18B20|
| |
+-------+
3.2 初始化代码示例
lua复制-- 初始化OneWire总线
local ow = onewire.init(0) -- 使用硬件通道0
-- 配置时序参数(单位:us)
onewire.timing(ow, false, 0,
480, 480, -- tRSTL, tRSTH
60, 240, -- tPDHIGH, tPDLOW
60, 1, -- tSLOT, tStart
15, 15, 1) -- tLOW1, tRDV, tREC
-- 发送复位脉冲
local present = onewire.reset(ow, true)
if not present then
print("No device detected!")
return
end
4. 单设备操作模式
4.1 完整温度读取流程
lua复制function read_temperature(ow)
-- 发送温度转换命令
onewire.tx(ow, 0x44, true, true, false)
-- 等待转换完成(12位精度约750ms)
sys.wait(800)
-- 读取暂存器
onewire.tx(ow, 0xBE, true, true, false)
local data = zbuff.create(9)
onewire.rx(ow, 9, nil, data, true, false, false)
-- 校验CRC
if onewire.crc8(data, 8) ~= data[8] then
return nil, "CRC error"
end
-- 解析温度值
local temp = (data[0] | (data[1] << 8)) * 0.0625
return temp
end
4.2 精度配置技巧
DS18B20默认使用12位精度,但我们可以通过写配置寄存器来调整:
lua复制function set_resolution(ow, bits)
local config = 0x1F -- 默认配置
if bits == 9 then config = 0x1F & 0xE0
elseif bits == 10 then config = 0x1F & 0xC0
elseif bits == 11 then config = 0x1F & 0x80
end
onewire.tx(ow, 0x4E, true, true, false) -- 写暂存器命令
onewire.tx(ow, 0xFF, true, false, false) -- 报警上限
onewire.tx(ow, 0x00, true, false, false) -- 报警下限
onewire.tx(ow, config, true, false, false) -- 配置寄存器
end
5. 多设备管理系统
5.1 设备发现算法
OneWire总线上的每个设备都有唯一的64位ROM ID,我们可以通过以下算法发现所有设备:
lua复制function search_devices(ow)
local devices = {}
local last_discrepancy = 0
local done = false
repeat
local discrepancy = 0
local id = zbuff.create(8)
onewire.reset(ow, true)
onewire.tx(ow, 0xF0, true, false, false) -- SEARCH ROM命令
for i=0,63 do
local bit1 = onewire.bit(ow, 1)
local bit2 = onewire.bit(ow, 1)
if bit1 and bit2 then
break -- 无设备响应
elseif bit1 ~= bit2 then
-- 所有设备该位相同
onewire.bit(ow, bit1 and 1 or 0)
else
-- 出现分歧点
if i == last_discrepancy then
onewire.bit(ow, 1)
elseif i > last_discrepancy then
onewire.bit(ow, 0)
discrepancy = i
else
-- 检查之前记录的路径
local byte = math.floor(i/8)
local mask = 1 << (i%8)
onewire.bit(ow, (id[byte] & mask) ~= 0 and 1 or 0)
end
end
end
if discrepancy == 0 then done = true end
last_discrepancy = discrepancy
-- 读取完整ROM ID
onewire.reset(ow, true)
onewire.tx(ow, 0x55, true, true, false) -- MATCH ROM
onewire.rx(ow, 8, nil, id, true, false, false)
table.insert(devices, id)
until done
return devices
end
5.2 多设备温度采集策略
当总线上有多个DS18B20时,温度转换需要特别处理:
-
并行转换法:
- 发送Skip ROM(0xCC)命令,后跟Convert T(0x44)
- 所有设备会同时开始转换
- 分别读取每个设备的温度值
-
串行转换法:
- 依次选择每个设备进行转换
- 适合需要严格控制功耗的场景
lua复制function read_all_temperatures(ow, devices)
local temps = {}
-- 方法1:并行转换
onewire.reset(ow, true)
onewire.tx(ow, 0xCC, true, false, false) -- Skip ROM
onewire.tx(ow, 0x44, true, false, false) -- Convert T
-- 等待所有设备完成转换
sys.wait(800)
-- 逐个读取温度
for i, id in ipairs(devices) do
onewire.reset(ow, true)
onewire.tx(ow, 0x55, true, true, false) -- Match ROM
onewire.tx(ow, id, true, false, false)
temps[i] = read_temperature(ow)
end
return temps
end
6. 实战经验与故障排查
6.1 常见问题速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 复位无响应 | 总线未正确连接 | 检查接线,确保上拉电阻 |
| CRC校验失败 | 总线干扰或时序问题 | 调整上拉电阻值,缩短总线长度 |
| 温度读数异常 | 寄生供电不足 | 改用外部供电或增加强上拉 |
| 多设备无法识别 | ROM搜索算法错误 | 确保正确处理分歧点 |
6.2 性能优化技巧
-
总线负载管理:
- 每增加一个设备,总线电容约增加5-10pF
- 建议总线设备数不超过15个(寄生供电模式下)
-
电源去耦:
- 在每个DS18B20的VDD引脚添加0.1μF陶瓷电容
- 长距离布线时,每3-5米增加一个10μF电容
-
软件优化:
- 批量读取时使用Skip ROM命令提高效率
- 在非关键应用中降低转换精度以减少功耗
lua复制-- 高效的批量读取实现
function batch_read(ow, devices)
local results = {}
-- 启动所有设备转换
onewire.reset(ow, true)
onewire.tx(ow, 0xCC, true, false, false) -- Skip ROM
onewire.tx(ow, 0x44, true, false, false) -- Convert T
-- 利用转换时间处理其他任务
-- ...
-- 读取所有设备
for i, id in ipairs(devices) do
onewire.reset(ow, true)
onewire.tx(ow, 0x55, true, true, false) -- Match ROM
onewire.tx(ow, id, true, false, false)
results[i] = read_temperature(ow)
end
return results
end
在实际项目中,我发现DS18B20的转换时间会随温度变化而变化。极端低温环境下,12位精度的转换时间可能超过1秒。因此,对于时间敏感的应用,建议:
- 实现超时检测机制
- 使用Read Power Supply命令监控转换状态
- 考虑使用9位或10位精度模式
7. 高级应用场景
7.1 温度报警系统
利用DS18B20的内置报警功能,我们可以实现硬件级的温度监控:
lua复制function setup_alarm(ow, id, low, high)
onewire.reset(ow, true)
onewire.tx(ow, 0x55, true, true, false) -- Match ROM
onewire.tx(ow, id, true, false, false)
onewire.tx(ow, 0x4E, true, false, false) -- Write Scratchpad
onewire.tx(ow, high, true, false, false) -- 报警上限
onewire.tx(ow, low, true, false, false) -- 报警下限
onewire.tx(ow, 0x7F, true, false, false) -- 配置寄存器(12位)
-- 保存配置到EEPROM
onewire.reset(ow, true)
onewire.tx(ow, 0x55, true, true, false)
onewire.tx(ow, id, true, false, false)
onewire.tx(ow, 0x48, true, false, false) -- Copy Scratchpad
sys.wait(20) -- 等待EEPROM写入完成
end
function check_alarm(ow)
onewire.reset(ow, true)
onewire.tx(ow, 0xCC, true, false, false) -- Skip ROM
onewire.tx(ow, 0xEC, true, false, false) -- Alarm Search
local devices = search_devices(ow)
return #devices > 0, devices
end
7.2 长距离传输方案
在工业环境中,可能需要将DS18B20安装在距离控制器数十米的位置。这时需要考虑:
- 线路驱动:使用DS2480B等专用线路驱动器
- 屏蔽双绞线:降低电磁干扰
- 总线拓扑:避免星形连接,采用菊花链结构
我曾成功实现过100米距离的可靠通信,关键配置如下:
- 使用DS2480B作为总线驱动器
- 采用CAT5e网线中的一对双绞线
- 总线两端各加120Ω终端电阻
- 上拉电阻调整为1kΩ
8. 系统集成建议
将DS18B20集成到更大的物联网系统中时,建议考虑以下架构:
lua复制-- 物联网温度监测节点示例
function main()
-- 初始化硬件
local ow = onewire.init(0)
setup_onewire_timing(ow)
-- 发现总线设备
local devices = search_devices(ow)
if #devices == 0 then
log.error("No DS18B20 found!")
return
end
-- 主循环
while true do
local temps = batch_read(ow, devices)
-- 上传到云平台
for i, temp in ipairs(temps) do
local rom = string.toHex(devices[i])
publish("sensor/"..rom, temp)
end
-- 低功耗休眠
sys.wait(30000) -- 30秒间隔
end
end
对于需要高可靠性的应用,可以增加以下机制:
- 温度变化超过阈值立即上报
- 设备离线检测与自动恢复
- 历史数据本地缓存
- CRC校验失败重试机制
在资源受限的设备上,可以优化内存使用:
- 复用zbuff缓冲区
- 使用整型运算替代浮点
- 精简日志输出
通过合理设计,即使是基于Lua的嵌入式系统也能高效管理数十个DS18B20传感器。我在一个农业温室监控项目中就成功实现了对48个温度采集点的稳定监控,关键是将采集任务分散在不同时间片执行,避免总线过载。
