|
一、单总线协议的核心概念 单总线协议的操作分为三个层次: 初始化:建立主机与 DS18B20 的连接(类似“握手”); ROM 命令:识别总线上的传感器(通过唯一 64 位 ROM 地址); 功能命令:执行具体操作(如温度转换、读数据)。 二、具体通信时序 单总线的所有操作都基于时间片(Time Slot),关键是延时的准确性(需用示波器校准或用精确的软件延时)。 1. 初始化时序(Initialization Sequence) 作用:主机检测总线上是否存在 DS18B20,并同步通信节奏。 步骤(主机主动发起): 复位脉冲(Reset Pulse):主机将数据线(DQ)拉低 480~960μs(典型 500μs),然后释放总线(由上拉电阻拉回高电平)。 应答脉冲(Presence Pulse):DS18B20 检测到复位脉冲后,会在 15~60μs 内拉低总线 60~240μs(典型 120μs),表示“我在”。 主机需在释放总线后等待至少 480μs,确保应答完成。 时序图: 主机:低电平(480-960μs)→ 高电平(释放) DS18B20:等待 15-60μs → 低电平(60-240μs)→ 高电平(恢复) 2. ROM 命令(ROM Commands) 总线上可能有多个 DS18B20(最多 8 个,因 1-Wire 总线驱动能力限制),ROM 命令用于选择目标传感器。常用 ROM 命令: 注意:单传感器系统中,优先使用 0xCC(Skip ROM),简化流程。 3. 功能命令(Function Commands) 在完成初始化和 ROM 命令后,主机发送功能命令控制传感器工作。常用功能命令: | | | | | | | | 读取传感器内部暂存器(共 9 字节,前 2 字节是温度值) | | | | | | |
4. 写时隙(Write Time Slot) 作用:主机向 DS18B20 写一个字节(8 位)。 规则: 写 0:主机拉低 DQ 至少 60μs(≤120μs),然后释放(上拉电阻拉回高)。 写 1:主机拉低 DQ 1~15μs(典型 5μs),然后释放,让总线自然回到高。 每两位写时隙之间需间隔 ≥1μs。 时序图(以写 1 为例): 主机:低电平(1-15μs)→ 高电平(释放)→ 等待 ≥1μs(下一个时隙) 5. 读时隙(Read Time Slot) 作用:主机从 DS18B20 读一个字节(8 位)。 规则: 主机拉低 DQ 1~15μs(典型 5μs),然后立即释放(转为输入模式) 在释放后的 15μs 内,读取 DQ 的状态:高电平为 1,低电平为 0。 每两位读时隙之间需间隔 ≥1μs。 时序图: 主机:低电平(1-15μs)→ 释放(转为输入)→ 15μs 内读状态 → 等待 ≥1μs(下一个时隙) 三、编程示例(C 语言,51 单片机为例) 以下是单传感器系统的完整流程:初始化→跳过 ROM→启动温度转换→等待转换完成→读暂存器→解析温度。 1. 硬件连接 VCC:3.3V/5V(推荐外部供电,寄生供电需额外处理); GND:接地; DQ:接 MCU 的 P1.0 引脚(或其他 I/O),必须串联 4.7kΩ 上拉电阻到 VCC。 2. 基础延时函数(关键!) 单总线对延时敏感,需用机器周期精确控制(51 单片机晶振 12MHz 时,1 机器周期=1μs): #include <reg52.h>#include <intrins.h> // 含 _nop_() #define uchar unsigned char#define uint unsigned int // 延时 1μs(12MHz 晶振)void delay_us(uint t) { while(t--) _nop_(); // _nop_() 是 1μs 延时(12MHz) } // 延时 1ms(12MHz 晶振)void delay_ms(uint t) { uint i, j; for(i = t; i > 0; i--) for(j = 110; j > 0; j--); } 3. 单总线核心函数 (1)初始化函数(返回 1 表示成功检测到传感器) bit Init_DS18B20(void) { bit ack; // 应答标志:0=无传感器,1=有传感器 DQ = 0; // 拉低 DQ(P1.0) delay_us(500); // 复位脉冲:480-960μs(选 500μs) DQ = 1; // 释放总线(上拉电阻拉高) delay_us(60); // 等待 15-60μs(选 60μs) ack = DQ; // 读取应答脉冲(DS18B20 拉低时为 0) delay_us(420); // 等待剩余时间(总时间 ≥480μs) return ~ack; // 反转:ack=0 表示有应答,返回 1 } (2)写一个字节函数 void Write_OneByte(uchar dat) { uchar i; for(i = 0; i < 8; i++) { DQ = 0; // 拉低总线,开始写时隙 delay_us(2); // 保持 1-15μs(选 2μs) DQ = dat & 0x01; // 写最低位(1 则释放,0 则保持低) delay_us(60); // 保持 ≥60μs(写 0 时足够,写 1 时自动释放) DQ = 1; // 释放总线(准备下一位) dat >>= 1; // 右移,处理下一位 } } (3)读一个字节函数 uchar Read_OneByte(void) { uchar i, dat = 0; for(i = 0; i < 8; i++) { DQ = 0; // 拉低总线,开始读时隙 delay_us(2); // 保持 1-15μs(选 2μs) DQ = 1; // 释放总线,转为输入 delay_us(8); // 等待 15μs 内的状态稳定(选 8μs) dat >>= 1; // 右移,腾出高位 if(DQ) dat |= 0x80; // 读当前位(高则置 1) delay_us(50); // 等待时隙结束(≥1μs) } return dat; } 4. 主流程:读取温度值 float Read_Temperature(void) { uchar LSB, MSB; // 温度低字节、高字节(16 位带符号数) int temp; // 16 位整数(用于符号处理) float t; // 最终温度值(℃) // 1. 初始化 + 跳过 ROM(单传感器) Init_DS18B20(); Write_OneByte(0xCC); // Skip ROM 命令 Write_OneByte(0x44); // 启动温度转换(Convert T) // 2. 等待转换完成(12 位分辨率需 750ms) delay_ms(750); // 简单延时,或用“读总线状态”判断(更高效) // 3. 再次初始化 + 读暂存器 Init_DS18B20(); Write_OneByte(0xCC); // Skip ROM Write_OneByte(0xBE); // 读暂存器(Read Scratchpad) LSB = Read_OneByte(); // 读低字节(温度小数部分+整数部分低4位) MSB = Read_OneByte(); // 读高字节(符号位+整数部分高4位) // 4. 解析 16 位温度数据(补码格式) temp = (MSB << 8) | LSB; // 合并为 16 位整数 if(temp & 0x8000) { // 负温度(最高位为 1) temp = ~temp + 1; // 取反加 1,得到绝对值的补码 t = -(temp * 0.0625); // 12 位分辨率:0.0625℃/LSB } else { // 正温度 t = temp * 0.0625; } return t; } 5. 主函数调用 void main(void) { float temp; while(1) { temp = Read_Temperature(); // 此处可将 temp 发送到串口/显示(如 LCD) delay_ms(1000); // 每秒读一次 } } 四、关键细节说明 温度数据解析: DS18B20 输出的 16 位是带符号二进制补码,格式如下(12 位分辨率): 高 5 位:符号位(S); 中间 7 位:整数部分(T_integer); 低 4 位:小数部分(T_fraction,1/16℃ 步进)。 例如:0000 0110 0101 0000→ 0x0650 → 1616 → 1616×0.0625=101℃?不对,等一下,正确的计算是: 16 位数据是 MSB<<8 | LSB,比如 LSB=0x50(80),MSB=0x06(6),合并后是 0x0650=1616,1616×0.0625=101℃?这显然超过量程,哦,错了——12 位分辨率时,有效位是 12 位,即 16 位数据中,低 4 位是小数,高 12 位是整数+符号。正确的计算应该是: 对于 12 位分辨率,temp = (MSB << 8 | LSB),然后 t = temp * 0.0625(因为 2^12=4096,1/4096=0.00024414?不对,等一下,DS18B20 的分辨率是: 9 位:0.5℃ → 1/512=0.001953125?不,等一下,官方文档说: 分辨率 | 温度增量 | 转换时间 9 位 | 0.5℃ | 93.75ms 10 位 | 0.25℃ | 187.5ms 11 位 | 0.125℃ | 375ms 12 位 | 0.0625℃| 750ms 所以,12 位时,每 LSB 对应 0.0625℃,不管符号,直接用 temp × 0.0625即可(符号由最高位决定)。 转换完成的判断: 上述示例用了 delay_ms(750)等待转换,更高效的方法是读总线的“忙”状态: 启动转换后,DQ 会被 DS18B20 拉低(表示忙),转换完成后释放为高。主机可通过读 DQ 状态判断是否完成(无需固定延时)。 多传感器组网: 若总线上有多个 DS18B20,需先通过 Search ROM(0xF0) 读取所有传感器的 64 位地址,再用 Match ROM(0x55)+ 地址 选通目标传感器。 五、常见问题排查 无应答(初始化返回 0):检查接线(上拉电阻是否接 4.7kΩ?DQ 是否接对?)、电源电压(是否 3-5V?)、传感器是否损坏。 读值错误:检查时序延时(用示波器测 DQ 波形)、是否有干扰(用屏蔽线)、是否在转换完成前读数据。 温度偏差大:确认分辨率设置(默认 12 位)、是否用了寄生供电(高温时建议外部供电)、传感器是否靠近热源。 通过以上步骤,你可以快速实现 DS18B20 的温度读取。如果需要更高效的代码(如用中断处理时序),可进一步优化延时函数(如用定时器),但核心逻辑不变。
|