OneWire单总线技术是嵌入式系统中一种独特而高效的通信方式,它仅需一根数据线(外加地线)就能实现主机与从机之间的双向通信。这种设计理念最早由Dallas Semiconductor(现已被Maxim Integrated收购)提出,特别适合资源受限的嵌入式应用场景。
单总线协议的精妙之处在于它通过精确的时序控制实现数据通信。与I2C或SPI等传统总线不同,OneWire采用"线与"逻辑,所有设备共享同一根数据线。通信过程由主机严格掌控时序,通过特定的时间槽(time slot)来区分逻辑"1"和逻辑"0"。
关键提示:OneWire总线需要接一个4.7kΩ的上拉电阻,这是确保总线空闲时保持高电平的必要条件。
通信的基本单位是时间槽,每个槽位持续60-120微秒。逻辑"0"通过主机拉低总线超过60μs表示,而逻辑"1"则是主机短暂拉低总线(通常15μs内)后释放。从机通过在特定时间窗内采样总线状态来识别数据位。
DS18B20是OneWire总线上的明星器件,具有以下突出特点:
在实际项目中,我强烈建议使用外部供电而非寄生供电模式。虽然寄生供电可以节省一根电源线,但在长距离布线或多设备场景下容易导致供电不足,影响温度转换精度。
对于DS18B20的标准连接方式,我们需要考虑以下几个要素:
电源方案选择:
布线注意事项:
多设备连接:
LuatOS提供了完善的OneWire支持,初始化过程非常简单:
lua复制-- 初始化OneWire总线(使用硬件通道0,GPIO2)
local ow = onewire.init(0)
if not ow then
print("OneWire初始化失败")
return
end
-- 配置时序参数(使用默认值)
onewire.timing(0, false, 0, 480, 480, 30, 30, 60, 1, 10, 10, 1)
时序参数配置详解:
tRSTL/tRSTH:复位脉冲低/高电平时间(μs)tPDHIGH:从机响应存在脉冲的最短时间tSLOT:标准时间槽持续时间tStart:起始沿到采样沿的延迟对于大多数应用,使用默认时序即可正常工作。但在高干扰环境或长距离布线时,可能需要适当延长复位和时间槽时长。
读取单个DS18B20的温度值需要遵循严格的命令序列:
总线复位:
lua复制local present = onewire.reset(0, true)
if not present then
print("未检测到DS18B20")
return
end
发送ROM命令(跳过ROM,0xCC):
lua复制onewire.tx(0, 0xCC, false, false, false) -- 跳过ROM寻址
启动温度转换(转换命令,0x44):
lua复制onewire.tx(0, 0x44, false, false, false)
sys.wait(750) -- 等待转换完成(12位分辨率)
再次复位总线:
lua复制onewire.reset(0, true)
发送读取命令(读取暂存器,0xBE):
lua复制onewire.tx(0, 0xCC, false, false, false) -- 跳过ROM
onewire.tx(0, 0xBE, false, false, false) -- 读取暂存器
读取9字节数据(包含温度值和CRC):
lua复制local buff = zbuff.create(9)
onewire.rx(0, 9, nil, buff, false, false, false)
解析温度值:
lua复制local temp_low = buff[1]
local temp_high = buff[2]
local temp = (temp_high << 8 | temp_low) * 0.0625
DS18B20返回的数据包含CRC校验字节,正确实现校验可以大幅提高系统可靠性:
lua复制local function crc8(data, len)
local crc = 0
for i = 1, len do
local byte = data[i]
for j = 1, 8 do
local mix = (crc ~ byte) & 0x01
crc = crc >> 1
if mix == 1 then
crc = crc ~ 0x8C
end
byte = byte >> 1
end
end
return crc
end
-- 校验示例
local crc = crc8(buff, 8)
if crc ~= buff[9] then
print("CRC校验失败")
return
end
经验之谈:在实际项目中,我发现连续3次读取失败后应该触发硬件检查流程,这能有效识别接触不良或传感器故障。
OneWire总线上的每个DS18B20都有唯一的64位ROM编码,搜索算法采用二叉树遍历原理:
LuatOS实现示例:
lua复制local devices = {}
local function search_rom(ow_id, prev_rom)
local rom = prev_rom or {}
local last_zero = -1
onewire.reset(ow_id, true)
onewire.tx(ow_id, 0xF0, false, false, false) -- 搜索ROM命令
for i = 1, 64 do
local bit1 = onewire.bit(ow_id, 1)
local bit2 = onewire.bit(ow_id, 1)
if bit1 == 1 and bit2 == 1 then
return nil -- 无设备响应
elseif bit1 ~= bit2 then
-- 无冲突,所有设备返回相同位
rom[i] = bit1
else
-- 发生冲突
if i > #rom or rom[i] == nil then
rom[i] = 0
last_zero = i
end
end
onewire.bit(ow_id, rom[i])
end
-- 存储找到的设备
table.insert(devices, rom)
-- 继续搜索其他设备
if last_zero >= 1 then
rom[last_zero] = 1
search_rom(ow_id, rom)
end
return devices
end
在多传感器系统中,温度采集需要特别考虑以下因素:
并行转换优化:
顺序读取技巧:
lua复制for i, rom in ipairs(devices) do
onewire.reset(ow_id, true)
onewire.tx(ow_id, 0x55, false, false, false) -- 匹配ROM
for j = 1, 8 do onewire.tx(ow_id, rom[j], false, false, false) end
onewire.tx(ow_id, 0xBE, false, false, false) -- 读取暂存器
local buff = zbuff.create(9)
onewire.rx(ow_id, 9, nil, buff, false, false, false)
-- 温度解析...
end
电源管理建议:
DS18B20支持可配置的分辨率,不同设置对转换时间的影响:
| 分辨率 | 温度增量 | 最大转换时间 | 适用场景 |
|---|---|---|---|
| 9位 | 0.5°C | 93.75ms | 快速响应 |
| 10位 | 0.25°C | 187.5ms | 常规应用 |
| 11位 | 0.125°C | 375ms | 高精度 |
| 12位 | 0.0625°C | 750ms | 实验室级 |
配置方法:
lua复制onewire.reset(0, true)
onewire.tx(0, 0xCC, false, false, false) -- 跳过ROM
onewire.tx(0, 0x4E, false, false, false) -- 写暂存器
onewire.tx(0, 0xFF, false, false, false) -- TH寄存器
onewire.tx(0, 0xFF, false, false, false) -- TL寄存器
onewire.tx(0, 0x3F, false, false, false) -- 配置寄存器(9位分辨率)
在工业环境中,OneWire总线可能面临各种干扰,以下是我总结的实战经验:
信号质量问题:
电源问题:
CRC错误频发:
多设备通信混乱:
对于电池供电的物联网设备,功耗优化至关重要:
间歇工作模式:
lua复制function read_temperature()
-- 上电总线
gpio.setup(pwr_pin, 1)
sys.wait(10) -- 稳定时间
-- 执行温度采集...
-- 关闭电源
gpio.setup(pwr_pin, 0)
end
利用报警功能:
优化采集频率:
DS18B20可以无缝融入LuatOS的物联网框架:
数据上报云平台:
lua复制local function upload_data(temp)
local data = {
devid = mobile.imei(),
temp = temp,
time = os.time()
}
mqtt.publish("/sensor/data", json.encode(data))
end
本地存储与告警:
lua复制local history = {}
local MAX_HISTORY = 24 -- 存储24个记录
local function save_reading(temp)
table.insert(history, {
time = os.date("%H:%M"),
value = temp
})
if #history > MAX_HISTORY then
table.remove(history, 1)
end
-- 检查温度阈值
if temp > 30 then
sms.send("13800138000", "高温告警:"..temp.."°C")
end
end
基于DS18B20和OneWire总线,可以开发多种创新应用:
分布式温度监测系统:
液位检测方案:
设备状态监测:
智能家居应用:
在实际部署多传感器网络时,我建议给每个物理位置编号,并将ROM编码与位置信息建立映射关系。这样当需要更换传感器时,系统可以自动识别位置而无需重新配置。