1. OneWire单总线技术解析
1.1 单总线协议基础原理
OneWire单总线技术是Dallas Semiconductor(现已被Maxim Integrated收购)在1990年代推出的一种独特通信协议。它的核心创新点在于仅需一根数据线(外加地线)即可实现双向通信,这使其在硬件资源受限的嵌入式系统中具有显著优势。
单总线协议采用主从式架构,主机通过精确控制数据线的时序来实现与从机的通信。具体来说:
- 数据线通过4.7kΩ上拉电阻连接到3.3V或5V电源
- 通信速率通常为15.4kbps(标准模式)或125kbps(高速模式)
- 采用开漏输出结构,允许总线上的多个设备共享同一数据线
- 每个设备都有全球唯一的64位ROM ID作为地址标识
注意:上拉电阻的阻值选择很关键,过大可能导致信号上升沿过缓,过小则可能无法正确实现逻辑0电平。
1.2 DS18B20温度传感器特性
DS18B20是OneWire总线上的明星产品,具有以下突出特点:
- 温度测量范围:-55°C至+125°C(-67°F至+257°F)
- 精度:±0.5°C(-10°C至+85°C范围内)
- 分辨率可配置:9位(0.5°C)、10位(0.25°C)、11位(0.125°C)或12位(0.0625°C)
- 内置非易失性存储器,可保存报警阈值和配置参数
- 每个器件都有唯一的64位序列号,支持多设备并联
在实际应用中,DS18B20的典型连接方式非常简单:VDD接电源(3.0V-5.5V),DQ接数据线(通过4.7kΩ上拉电阻),GND接地。也可以采用寄生供电模式,此时VDD接地,器件通过数据线获取工作电源。
2. LuatOS的OneWire实现
2.1 硬件接口配置
LuatOS为OneWire总线提供了完整的硬件抽象层支持。以Air780E系列模组为例,其GPIO2引脚默认支持OneWire功能(硬件通道0模式),开发者无需额外配置引脚复用功能。
硬件连接建议:
- 使用4.7kΩ上拉电阻(精度1%为佳)
- 总线长度建议不超过30米(实际应用中3-5米为佳)
- 避免与高频信号线平行走线,减少干扰
2.2 API功能详解
LuatOS提供的OneWire API既考虑了硬件时序的精确控制,又提供了应用层的易用性封装:
lua复制-- 初始化OneWire总线(硬件通道0)
local ow = onewire.init(0)
-- 配置时序参数(单位:us)
onewire.timing(0, false, 0,
480, 480, -- tRSTL, tRSTH: 复位脉冲时间
15, 60, -- tPDHIGH, tPDLOW: 位周期时间
60, 1, -- tSLOT, tStart: 时隙和起始时间
6, 6, 6) -- tLOW1, tRDV, tREC: 其他时序参数
-- 发送复位脉冲并检测设备存在
local present = onewire.reset(0, true)
-- 发送1字节数据(MSB优先)
onewire.tx(0, 0xCC, true, false, false) -- 跳过ROM命令
-- 读取9字节温度数据
local data = onewire.rx(0, 9, nil, nil, true, false, false)
实操技巧:时序参数配置对通信稳定性至关重要。当总线负载较重或线路较长时,需要适当增加tRSTL、tRSTH和tSLOT等参数。
3. 单传感器应用实现
3.1 硬件连接方案
单DS18B20传感器的典型连接非常简单:
code复制[Air780E GPIO2] ----[4.7kΩ上拉电阻]---- 3.3V
|
+----[DQ] DS18B20 [VDD]---- 3.3V
| [GND]---- GND
+---- 其他OneWire设备(可选)
3.2 完整温度读取流程
- 初始化总线:发送复位脉冲,检测设备存在
- 发送跳过ROM命令(0xCC):适用于单设备场景
- 启动温度转换(0x44):开始温度测量
- 等待转换完成:典型转换时间为750ms(12位分辨率)
- 再次初始化总线
- 发送跳过ROM命令(0xCC)
- 发送读取暂存器命令(0xBE)
- 读取9字节数据(包括温度值、TH/TL报警值和CRC)
- 验证CRC校验
- 解析温度值
示例代码:
lua复制function read_ds18b20()
-- 初始化总线
if not onewire.reset(0, true) then
return nil, "No device present"
end
-- 跳过ROM选择
onewire.tx(0, 0xCC, true, false, false)
-- 启动温度转换
onewire.tx(0, 0x44, true, false, false)
-- 等待转换完成(750ms for 12-bit)
sys.wait(750)
-- 再次初始化
if not onewire.reset(0, true) then
return nil, "Device lost after conversion"
end
-- 跳过ROM选择
onewire.tx(0, 0xCC, true, false, false)
-- 发送读取命令
onewire.tx(0, 0xBE, true, false, false)
-- 读取9字节数据
local data = onewire.rx(0, 9, nil, nil, true, false, false)
-- 校验CRC
if not check_crc(data) then
return nil, "CRC error"
end
-- 解析温度值
local temp_raw = data:byte(1) + data:byte(2)*256
local temp_c = temp_raw * 0.0625 -- 12-bit resolution
return temp_c
end
3.3 温度数据解析细节
DS18B20的温度数据以16位补码形式存储在暂存器的前两个字节中:
- 位15-11:符号位(0为正,1为负)
- 位10-4:整数部分
- 位3-0:小数部分(LSB=0.0625°C)
例如:
- 0x0191 = 0000000110010001 → 25.0625°C
- 0xFF6E = 1111111101101110 → -25.0625°C
常见问题:当读取到0x0550或类似值时,通常表示传感器未完成温度转换或通信错误,应检查等待时间是否足够。
4. 多传感器系统实现
4.1 设备发现与枚举
在多设备系统中,首先需要通过搜索算法发现总线上的所有设备。OneWire协议使用了一种巧妙的二进制搜索算法:
- 发送复位脉冲并检测设备存在
- 发送搜索ROM命令(0xF0)
- 逐位读取设备的ROM ID
- 根据冲突情况决定下一步操作
- 重复直到发现所有设备
LuatOS实现示例:
lua复制function search_devices()
local devices = {}
local last_discrepancy = 0
local done = false
repeat
if not onewire.reset(0, true) then
break
end
onewire.tx(0, 0xF0, true, false, false) -- SEARCH ROM
local rom_id = ""
local discrepancy_marker = 0
for i = 1, 8 do -- 8 bytes ROM ID
local byte = 0
for j = 1, 8 do -- 8 bits per byte
local bit1 = onewire.bit(0, 0) -- Read bit
local bit2 = onewire.bit(0, 0) -- Read complement
if bit1 == 1 and bit2 == 1 then
break -- No devices
elseif bit1 == 0 and bit2 == 0 then
-- Conflict
if i*8+j-1 == last_discrepancy then
byte = byte | (1<<(j-1))
elseif i*8+j-1 > last_discrepancy then
byte = byte | (0<<(j-1))
else
local rom_byte = devices[#devices]:byte(i)
byte = byte | ((rom_byte>>(j-1))&1)<<(j-1)
end
if ((byte>>(j-1))&1) == 0 then
discrepancy_marker = i*8+j-1
end
else
-- No conflict
byte = byte | (bit1<<(j-1))
end
onewire.bit(0, (byte>>(j-1))&1) -- Write bit
end
rom_id = rom_id .. string.char(byte)
end
if #rom_id == 8 then
table.insert(devices, rom_id)
end
last_discrepancy = discrepancy_marker
done = (last_discrepancy == 0)
until done
return devices
end
4.2 多设备温度采集策略
在多设备系统中,温度采集需要特别考虑以下因素:
- 并行转换:所有DS18B20可以同时启动温度转换(通过跳过ROM命令+0x44)
- 顺序读取:转换完成后,逐个设备读取温度数据
- 电源管理:寄生供电模式下,需要严格控制总线负载
优化后的采集流程:
lua复制function read_multiple_temp(devices)
-- 启动所有设备转换
if not onewire.reset(0, true) then
return nil, "Bus error"
end
onewire.tx(0, 0xCC, true, false, false) -- Skip ROM
onewire.tx(0, 0x44, true, false, false) -- Convert T
-- 等待转换完成(考虑最慢的设备)
sys.wait(750)
local results = {}
-- 逐个读取温度
for i, rom in ipairs(devices) do
if onewire.reset(0, true) then
onewire.tx(0, 0x55, true, false, false) -- Match ROM
for j = 1, 8 do
onewire.tx(0, rom:byte(j), true, false, false)
end
onewire.tx(0, 0xBE, true, false, false) -- Read Scratchpad
local data = onewire.rx(0, 9, nil, nil, true, false, false)
if check_crc(data) then
local temp_raw = data:byte(1) + data:byte(2)*256
results[rom] = temp_raw * 0.0625
else
results[rom] = nil
end
end
end
return results
end
4.3 实际应用中的优化技巧
- 动态电源控制:对于寄生供电设备,在温度转换期间可强制拉高数据线提供额外电源
- 分辨率选择:根据应用需求选择合适的分辨率(9-12位),平衡精度和转换时间
- CRC校验优化:预计算CRC表加速校验过程
- 异常处理:实现设备热插拔检测和自动恢复机制
避坑指南:在多设备系统中,确保每个DS18B20的ROM ID正确识别至关重要。常见问题是设备接触不良导致ROM ID读取错误,建议在系统初始化时完整扫描并记录所有设备ID。
5. 高级应用与故障排查
5.1 寄生供电模式实现
DS18B20支持寄生供电模式(Parasite Power Mode),此时VDD引脚接地,器件通过数据线获取工作电源。这种模式节省了一根电源线,但需要特别注意:
- 温度转换期间(最多750ms)必须保持强上拉
- 总线负载能力有限,通常不超过3个设备
- 通信速率可能需要降低
实现强上拉的典型方法:
lua复制function start_conversion_parasite()
-- 正常启动转换
onewire.reset(0, true)
onewire.tx(0, 0xCC, true, false, false) -- Skip ROM
onewire.tx(0, 0x44, true, false, false) -- Convert T
-- 启用强上拉(通过GPIO直接驱动)
gpio.setup(pin_pullup, 1)
sys.wait(750) -- 等待转换
gpio.setup(pin_pullup, 0) -- 恢复
end
5.2 常见故障与解决方法
| 故障现象 | 可能原因 | 解决方案 |
|---|---|---|
| 持续读取85°C | 电源不足或转换未完成 | 检查电源,确保足够等待时间 |
| CRC校验失败 | 信号质量差或时序不当 | 调整上拉电阻,检查线路长度 |
| 设备偶尔丢失 | 接触不良或电源问题 | 检查连接器,考虑增加去耦电容 |
| 温度读数跳变 | 电磁干扰或电源噪声 | 缩短总线长度,增加滤波电容 |
| 无法发现设备 | 总线未正确初始化 | 检查复位脉冲和时序配置 |
5.3 性能优化实践
- 批量读取优化:对于固定安装的多设备系统,可以缓存ROM ID避免每次搜索
- 温度报警功能:利用DS18B20的报警搜索功能(0xEC),仅读取超出阈值的设备
- 自适应采样率:根据温度变化率动态调整采样频率
- 数据平滑处理:实现移动平均或低通滤波算法减少读数波动
lua复制-- 移动平均滤波实现
local temp_history = {}
local history_size = 5
function filtered_temp(new_temp)
table.insert(temp_history, new_temp)
if #temp_history > history_size then
table.remove(temp_history, 1)
end
local sum = 0
for _, t in ipairs(temp_history) do
sum = sum + t
end
return sum / #temp_history
end
在实际项目中,我发现DS18B20的稳定性很大程度上取决于硬件设计的细节。特别是长距离布线时,使用双绞线并适当降低通信速率可以显著提高可靠性。另外,对于关键应用,建议实现温度读数的合理性检查(如与历史数据比较)和超时重试机制,确保系统鲁棒性。
