onewire - 单总线协议库
作者:王棚嶙
一、概述
OneWire(单总线)是一种由Dallas Semiconductor(现Maxim Integrated)开发的通信协议,它只需要一根数据线(加上地线)即可实现双向通信。这种协议广泛应用于温度传感器(如DS18B20)、存储器、ADC/DAC等设备。LuatOS支持OneWire协议,允许开发者方便地与各种单总线设备进行通信。
1.1 OneWire协议简介
- 协议特点:单线双向通信,节省IO资源
- 通信速率:标准模式16kbps,高速模式142kbps
- 拓扑结构:总线型拓扑,支持多设备并联
- 设备类型:温度传感器、存储器、ADC、DAC等
- 供电方式:支持寄生供电和外部供电
1.2 硬件接口特性
- GPIO映射:可配置任意GPIO作为OneWire总线
- 上拉电阻:需要4.7kΩ上拉电阻到VCC(3.3V)
- 总线电容:建议总线电容不超过1000pF
- 电缆长度:标准模式下可达100米
1.3 设备地址说明
OneWire设备地址由8字节组成:
--[[
设备地址格式:
字节0:家族码(Family Code)
字节1-6:序列号(Serial Number)
字节7:CRC校验码
常见家族码:
0x10:DS18S20温度传感器
0x28:DS18B20温度传感器
0x22:DS1822温度传感器
0x42:DS28EA00温度传感器
]]
二、核心示例
1、核心示例是指:使用本库文件提供的核心 API,开发的基础业务逻辑的演示代码;
2、核心示例的作用是:帮助开发者快速理解如何使用本库,所以核心示例的逻辑都比较简单;
3、更加完整和详细的 demo,请参考 LuatOS 仓库 中各个产品目录下的 demo/onewire;
-- 示例一
-- 初始化OneWire总线
onewire.init(0) -- 初始化硬件单总线ID为0
-- 配置时序参数
onewire.timing(0, false, 0, 480, 70, 70, 410, 70, 10, 10, 15, 70) -- DS18B20标准时序
-- 读取DS18B20温度(假设已知设备存在)
onewire.reset(0, true) -- 复位总线并检测设备
-- 发送温度转换命令
onewire.tx(0, 0x44, false, false, false) -- Convert T命令
sys.wait(750) -- 等待转换完成(12位精度需要750ms)
-- 读取温度数据
onewire.reset(0, true) -- 再次复位
onewire.tx(0, 0xBE, false, false, false) -- Read Scratchpad命令
local succ, data = onewire.rx(0, 9, false, false, false) -- 读取9字节数据
if succ and data and #data == 9 then
-- 计算温度(简化的整数部分)
local temp = data:byte(1) + (data:byte(2) * 256)
if temp > 32767 then temp = temp - 65536 end -- 处理负温度
temp = temp * 0.0625 -- DS18B20的分辨率为0.0625°C
log.info("温度", "读取成功", "温度:", temp, "°C")
end
-- 示例二
-- 初始化总线
onewire.init(0) -- 初始化硬件单总线ID为0
onewire.timing(0, false, 0, 480, 70, 70, 410, 70, 10, 10, 15, 70) -- DS18B20标准时序
-- 定义温度轮询函数
local function pollTemperature()
-- 启动温度转换
onewire.reset(0, true) -- 复位总线
onewire.tx(0, 0x44, false, false, false) -- 发送Convert T命令给所有设备
sys.wait(100) -- 等待转换
-- 读取温度(跳过ROM命令,读取第一个设备)
onewire.reset(0, true) -- 再次复位
onewire.tx(0, 0xCC, false, false, false) -- Skip ROM命令
onewire.tx(0, 0xBE, false, false, false) -- Read Scratchpad命令
local succ, temp_data = onewire.rx(0, 2, false, false, false) -- 读取2字节温度数据
if succ and temp_data and #temp_data == 2 then
local temp = temp_data:byte(1) + temp_data:byte(2) * 256
if temp > 32767 then temp = temp - 65536 end
temp = temp * 0.0625
log.info("温度传感器", temp_data:toHex(), temp, "°C")
end
end
-- 启动定时器轮询
sys.timerLoopStart(pollTemperature, 2000) -- 每2秒轮询一次
-- 示例三
-- 初始化OneWire
onewire.init(0) -- 初始化硬件单总线ID为0
onewire.timing(0, false, 0, 480, 70, 70, 410, 70, 10, 10, 15, 70) -- DS18B20标准时序
-- 定义总线状态检查函数
local function checkBusStatus()
-- 检查总线状态
local present = onewire.reset(0, true)
if present then
log.info("OneWire", "总线正常,设备存在")
-- 尝试读取设备ROM ID(使用Read ROM命令0x33)
onewire.reset(0, true) -- 复位
onewire.tx(0, 0x33, false, false, false) -- Read ROM命令
local succ, rom_data = onewire.rx(0, 8, false, false, false) -- 读取8字节ROM数据
if succ and rom_data and #rom_data == 8 then
log.info("设备ROM", rom_data:toHex())
else
log.warn("OneWire", "读取设备ROM失败")
end
else
log.warn("OneWire", "总线异常,无设备响应")
end
end
-- 启动状态监控定时器
sys.timerLoopStart(checkBusStatus, 5000) -- 每5秒检查一次
三、常量详解
核心库常量,顾名思义是由合宙 LuatOS 内核固件中定义的、不可重新赋值或修改的固定值,在脚本代码中不需要声明,可直接调用;
onewire核心库无常量
四、函数详解
4.1 onewire.init(id)
功能
初始化OneWire总线;
注意事项
初始化硬件单总线,每个ID对应一个硬件单总线;
参数
id
参数含义:OneWire总线ID;
数据类型:number;
取值范围:0~7;
是否必选:是;
注意事项:每个ID对应一个独立的OneWire总线,如果只有一条则随意填写;
参数示例:0;
返回值
无返回值
示例
-- 初始化OneWire总线0
onewire.init(0)
log.info("onewire", "初始化完成")
4.2 onewire.timing(id, is_tick, clk_div, tRSTL, tRSTH, tPDHIGH, tPDLOW, tSLOT, tStart, tLOW1, tRDV, tREC)
功能
配置OneWire总线时序参数,用于匹配不同的单总线设备;
注意事项
1、如果不配置,默认情况下是直接匹配DS18B20;
2、标准模式通信距离更远,高速模式速度更快;
3、需要在初始化后调用;
4、影响所有后续通信的时序;
5、从Maxim Integrated官网或其他芯片供应商处获取对应传感器的官方数据手册,重点关注"Timing Diagrams"或"Electrical Characteristics"章节,来查找时序图,芯片手册定义了芯片的理论能力范围;
6、DS18S20/DS18B20/DS1822/DS28EA00,这四种温度传感器请参考第六章节;
参数
id
参数含义:OneWire总线ID;
数据类型:number;
取值范围:0~7;
是否必选:是;
注意事项:必须是已初始化的总线ID;
参数示例:0;
is_tick
参数含义:后续时序参数是否是tick;
数据类型:boolean;
取值范围:true-是tick,false-单位是μs;
是否必选:是;
注意事项:除非具体平台有特殊要求,一般是μs;
参数示例:false;
clk_div
参数含义:tick参数下的分频系数;
数据类型:number;
取值范围:建议分频到1个tick=1个μs;
是否必选:是;
注意事项:如果μs参数,本参数忽略;
参数示例:0;
tRSTL
参数含义:reset拉低总时间;
数据类型:number;
取值范围:有效的时序值;
是否必选:是;
注意事项:单位根据is_tick决定;
参数示例:500;
tRSTH
参数含义:reset释放总时间;
数据类型:number;
取值范围:有效的时序值;
是否必选:是;
注意事项:单位根据is_tick决定;
参数示例:500;
tPDHIGH
参数含义:reset释放到开始探测时间;
数据类型:number;
取值范围:有效的时序值;
是否必选:是;
注意事项:单位根据is_tick决定;
参数示例:15;
tPDLOW
参数含义:reset探测时间;
数据类型:number;
取值范围:有效的时序值;
是否必选:是;
注意事项:单位根据is_tick决定;
参数示例:240;
tSLOT
参数含义:通信总有效时间;
数据类型:number;
取值范围:有效的时序值;
是否必选:是;
注意事项:单位根据is_tick决定;
参数示例:65;
tStart
参数含义:通信start信号时间,一般就是开头拉低;
数据类型:number;
取值范围:有效的时序值;
是否必选:是;
注意事项:单位根据is_tick决定;
参数示例:1;
tLOW1
参数含义:start信号到允许写的时间;
数据类型:number;
取值范围:有效的时序值;
是否必选:是;
注意事项:单位根据is_tick决定;
参数示例:15;
tRDV
参数含义:start信号到允许读的时间;
数据类型:number;
取值范围:有效的时序值;
是否必选:是;
注意事项:单位根据is_tick决定;
参数示例:15;
tREC
参数含义:通信结束前恢复时间;
数据类型:number;
取值范围:有效的时序值;
是否必选:是;
注意事项:单位根据is_tick决定;
参数示例:2;
返回值
无返回值
示例
-- 配置单总线时序匹配DS18B20,保留了点余量
onewire.timing(0, false, 0, 500, 500, 15, 240, 65, 1, 15, 15, 2)
log.info("onewire", "时序配置成功")
4.3 onewire.reset(id, need_ack)
功能
发送复位脉冲并检测设备是否存在;
注意事项
1、复位脉冲的使用策略:
- 设备初始化阶段:建议发送复位脉冲确保设备就绪;
- 关键命令前:如温度转换、ROM操作等建议复位;
- 连续数据传输:已知设备在线可省略复位以提高效率;
- 错误恢复:通信失败时应重新发送复位脉;
2、返回值表示是否有设备响应;
3、会重置总线上的所有设备;
参数
id
参数含义:OneWire总线ID;
数据类型:number;
取值范围:0~7;
是否必选:是;
注意事项:必须是已初始化的总线ID;
参数示例:0;
need_ack
参数含义:是否需要检测应答信号;
数据类型:boolean;
取值范围:true-需要检测,false-不需要检测;
是否必选:是;
注意事项:true需要检测,false不需要;
参数示例:true;
返回值
local present = onewire.reset(id, need_ack)
有一个返回值 present
present
含义说明:设备存在状态;
数据类型:boolean;
取值范围:true或false;
注意事项:检测到应答或无需检测返回true,失败返回false;
返回示例:true;
示例
-- 复位总线并检测设备
local present = onewire.reset(0, true)
if present then
log.info("onewire", "检测到设备")
else
log.warn("onewire", "未检测到设备")
end
4.4 onewire.bit(id, send1bit)
功能
硬件单总线发送或接收1bit;
注意事项
1、可以发送或接收单个位;
2、发送单个位时,send1bit参数为要发送的值;
3、接收单个位时,send1bit参数为nil或留空;
参数
id
参数含义:OneWire总线ID;
数据类型:number;
取值范围:0~7;
是否必选:是;
注意事项:必须是已初始化的总线ID;
参数示例:0;
send1bit
参数含义:发送bit的电平,1高电平,0低电平,留空或者其他值则是读1bit;
数据类型:number/nil;
取值范围:0或1;
是否必选:否;
注意事项:发送时为0或1,接收时为nil;
参数示例:1;
返回值
local result = onewire.bit(id, send1bit)
有一个返回值 result
result
含义说明:操作结果;
数据类型:int;
取值范围:如果是发送则忽略结果,如果是接收则是接收到的电平;
注意事项:发送模式返回0,接收模式返回接收到的位值(0或1);
返回示例:1;
示例
-- 发送1bit高电平
onewire.bit(0, 1)
-- 读取1bit数据
local bit_value = onewire.bit(0)
4.5 onewire.tx(id, data, is_msb, need_reset, need_ack)
功能
发送数据到OneWire设备;
注意事项
1、支持发送整数、字符串或zbuff数据;
2、如果是整数则是1个字节数据,如果是zbuff则是从开头到指针前的数据;
3、可以控制是否需要先发送reset信号;
参数
id
参数含义:OneWire总线ID;
数据类型:number;
取值范围:0~7;
是否必选:是;
注意事项:必须是已初始化的总线ID;
参数示例:0;
data
参数含义:要发送的数据;
数据类型:int/string/zbuff;
取值范围:有效的数据;
是否必选:是;
注意事项:如果是int则是1个字节数据,如果是zbuff则是从开头到指针前的数据;
参数示例:0x33;
is_msb
参数含义:是否需要先发送MSB;
数据类型:boolean;
取值范围:true-是,false-不是;
是否必选:否;
注意事项:默认情况下都是false;
参数示例:false;
need_reset
参数含义:是否需要先发送reset;
数据类型:boolean;
取值范围:true-需要,false-不需要;
是否必选:否;
注意事项:true需要检测,false不需要(默认),必须满足以下条件才能省略reset:
1、总线状态稳定 - 没有设备插入/移除
2、设备已初始化 - 之前的reset已成功建立通信
3、时序要求宽松 - 不是严格的协议要求场景
4、错误容忍度高 - 允许偶尔的通信失败;
参数示例:true;
need_ack
参数含义:是否需要检测应答信号;
数据类型:boolean;
取值范围:true-需要检测,false-不需要检测;
是否必选:否;
注意事项:true需要检测,false不需要(默认);
参数示例:true;
返回值
local result = onewire.tx(id, data, is_msb, need_reset, need_ack)
有一个返回值 result
result
含义说明:发送结果;
数据类型:boolean;
取值范围:true或false;
注意事项:检测到应答或无需检测返回true,失败或参数错误返回false;
返回示例:true;
示例
-- 复位并检测ACK,接收到ACK后发送0x33
local succ = onewire.tx(0, 0x33, false, true, true)
-- 复位后发送0x33,无视从机是否ACK
local succ = onewire.tx(0, 0x33, false, true, false)
-- 直接发送0x33
local succ = onewire.tx(0, 0x33)
4.6 onewire.rx(id, len, cmd, buff, is_msb, need_reset, need_ack)
功能
硬件单总线读取N字节数据;
注意事项
1、可以返回字符串或zbuff数据;
2、如果是zbuff,则是从开头到指针前的数据;
3、可以控制是否需要先发送reset信号;
参数
id
参数含义:OneWire总线ID;
数据类型:number;
取值范围:0~7;
是否必选:是;
注意事项:必须是已初始化的总线ID;
参数示例:0;
len
参数含义:需要读取的字节数量;
数据类型:number;
取值范围:1~256;
是否必选:是;
注意事项:接收数据长度,不能超过缓冲区大小;
参数示例:8;
cmd
参数含义:在读取前发送命令,可以填nil不发送任何命令;
数据类型:int/nil;
取值范围:有效的命令或nil;
是否必选:否;
注意事项:如果需要在读取前发送命令,可以指定命令字节;
参数示例:0x33;
buff
参数含义:接收数据缓存,接收前会清空整个缓存,如果填nil则输出字符串;
数据类型:zbuff/nil;
取值范围:有效的zbuff或nil;
是否必选:否;
注意事项:如果填nil则返回字符串数据;
参数示例:zbuff(8);
is_msb
参数含义:是否需要先接收MSB,true是,false不是,默认情况下都是false;
数据类型:boolean;
取值范围:true或false;
是否必选:否;
注意事项:默认情况下都是false;
参数示例:false;
need_reset
参数含义:是否需要先发送reset,true需要检测,false不需要;
数据类型:boolean;
取值范围:true或false;
是否必选:否;
注意事项:true需要检测,false不需要(默认),必须满足以下条件才能省略reset:
1、总线状态稳定 - 没有设备插入/移除
2、设备已初始化 - 之前的reset已成功建立通信
3、时序要求宽松 - 不是严格的协议要求场景
4、错误容忍度高 - 允许偶尔的通信失败;;
参数示例:true;
need_ack
参数含义:是否需要检测应答信号,true需要检测,false不需要检测;
数据类型:boolean;
取值范围:true或false;
是否必选:否,默认值false;
注意事项:
1. 应答信号检测原理:
- 主机发送480-960μs复位脉冲
- 等待15-60μs后检测设备是否拉低总线60-240μs
- 设备拉低总线表示"存在并准备好通信",这就是应答信号
2. true-需要检测应答信号:
- 适用场景:设备发现、错误诊断、关键通信
- 优点:确保设备在线,通信可靠性高
- 缺点:增加通信时间和系统开销
- 典型应用:DS18B20初始化、ROM命令序列
3. false-不需要检测应答信号:
- 适用场景:已知设备稳定在线、连续数据传输
- 优点:通信速度快,系统开销小
- 风险:无法检测设备是否存在,可能通信失败
- 典型应用:温度数据连续读取、状态轮询
4. 使用建议:
客户需根据自己的使用场景来决定要不要检测
- 设备初始化阶段:need_ack=true
- 稳定通信阶段:可设置为need_ack=false
- 通信失败时:切换回need_ack=true进行诊断
- 多设备环境:建议保持need_ack=true;
- 读数据时:建议保持need_ack=true;
参数示例:true;
返回值
local succ, rx_data = onewire.rx(id, len, cmd, buff, is_msb, need_reset, need_ack)
有两个返回值 succ 和 rx_data
succ
含义说明:检测到应答或无需检测返回true,失败或者参数错误返回false;
数据类型:boolean;
取值范围:true或false;
注意事项:true-成功,false-失败;
返回示例:true;
rx_data
含义说明:接收到的数据;
数据类型:string;
取值范围:接收到的数据;
注意事项:如果buff填nil,则接收数据从这里输出;
返回示例:"\x01\x02\x03";
示例
-- 直接接收8个字节
local succ, rx_data = onewire.rx(0, 8)
-- 先发送reset,检查ack信号,发送0x33,接收8个字节,这是DS18B20读ROM ID标准流程
local succ, rx_data = onewire.rx(0, 8, 0x33, buf, nil, true, true)
4.7 onewire.debug(id, onoff)
功能
单总线调试开关;
注意事项
1、开启后会输出详细的调试信息;
2、有助于排查通信问题;
3、量产环境建议关闭;
参数
id
参数含义:HW模式是硬件单总线编号,如果只有一条则随意填写;
数据类型:number;
取值范围:0~7;
是否必选:是;
注意事项:必须是已初始化的总线ID;
参数示例:0;
onoff
参数含义:调试开关;
数据类型:boolean;
取值范围:true或false;
是否必选:是;
注意事项:true-打开调试,false-关闭调试;
参数示例:true;
返回值
无返回值
示例
-- 打开调试模式
onewire.debug(0, true)
-- 关闭调试模式
onewire.debug(0, false)
4.8 onewire.deinit(id)
功能
关闭单总线;
注意事项
1、释放相关资源;
2、关闭后不能再使用该总线,除非重新初始化;
3、建议在程序退出或不再需要时调用;
参数
id
参数含义:单总线ID编号
数据类型:number;
取值范围:0~7;
是否必选:是;
注意事项:必须是已初始化的总线ID;
参数示例:0;
返回值
无返回值
示例
-- 关闭总线0
onewire.deinit(0)
五、产品支持列表
支持 LuatOS 开发的所有产品都支持onewire核心库。
六、参考资料
DS18B20 datasheet:DS18B20
DS18S20 datasheet:DS18S20
DS1822 datasheet:DS1822
DS28EA00 datasheet:DS28EA00