1. 单总线协议基础与传感器选型
第一次接触单总线设备时,我被它的简洁性惊艳到了——只需要一根数据线就能完成通信,这对PCB布线简直是福音。在实际项目中,DS18B20和DHT11这对"温度传感兄弟"经常同时出现,前者负责高精度测温,后者提供温湿度一体化方案。但它们的驱动实现差异很大,我先从最基础的单总线时序说起。
单总线协议的精髓在于用时间宽度表示数据。比如DS18B20规定:主机拉低总线15μs表示写"0",拉低60μs表示写"1"。这个过程中,所有时序都必须精确到微秒级。我在STM32F103上实测发现,使用72MHz主频时,用SysTick实现的延时函数误差能控制在±2μs内,完全满足需求。
选择传感器时有几个实用建议:
- DS18B20适合需要±0.5℃精度的场景,支持-55℃~+125℃宽范围
- DHT11成本更低但精度稍差(±2℃),响应速度也较慢
- 防水项目选金属封装DS18B20,常规环境用TO-92封装更经济
2. DS18B20驱动开发详解
2.1 底层时序实现要点
写DS18B20驱动时,最关键的三个时序是复位脉冲、写时隙和读时隙。我遇到过最典型的坑是:复位后没等够480μs就检测应答,导致设备永远无响应。正确的复位序列应该是:
- 主机拉低总线480μs
- 释放总线后等待60μs
- 读取从机应答脉冲(60-240μs低电平)
- 最后保持480μs空闲
读温度值的完整流程如下:
// 启动温度转换 Ds18b20_Reset(); Ds18b20_Write_Byte(0xCC); // 跳过ROM Ds18b20_Write_Byte(0x44); // 启动转换 HAL_Delay(750); // 等待转换完成 // 读取暂存器 Ds18b20_Reset(); Ds18b20_Write_Byte(0xCC); Ds18b20_Write_Byte(0xBE); // 读暂存器 LSB = Ds18b20_Read_Byte(); MSB = Ds18b20_Read_Byte();2.2 温度数据处理技巧
DS18B20返回的是16位补码数据,需要特殊处理负数情况。我优化后的转换算法:
int16_t raw_temp = (MSB << 8) | LSB; if(raw_temp & 0x8000){ // 负数判断 raw_temp = ~raw_temp + 1; temperature = -raw_temp * 0.0625f; }else{ temperature = raw_temp * 0.0625f; }对于需要显示正负号的场景,建议将温度值放大10倍转为整型处理,能避免浮点运算带来的性能损耗。
3. 多DS18B20设备管理方案
3.1 ROM地址搜索实战
单总线的精髓在于用64位ROM地址区分设备。搜索算法比较烧脑,但STM32完全能胜任。我的实现步骤:
- 发送搜索ROM命令(0xF0)
- 读取所有设备的位响应(与操作)
- 遇到分歧位时记录路径
- 根据路径选择后续搜索方向
实际项目中,我建议先用搜索命令获取所有设备地址,然后存入数组长期使用:
uint8_t rom_codes[3][8] = {0}; // 假设最多3个传感器 for(int i=0; i<3; i++){ DS18B20_SearchRom(rom_codes[i]); }3.2 分时读取优化策略
同时管理多个DS18B20时,要注意温度转换耗时问题。我的方案是:
- 初始化时启动所有设备转换
- 设置标志位记录转换状态
- 750ms后集中读取数据 这样比串行操作节省大量时间,实测读取5个传感器只需800ms(传统方式需要3750ms)
4. DHT11驱动设计与避坑指南
4.1 时序差异分析
虽然都是单总线,DHT11的时序与DS18B20完全不同:
- 启动信号要求主机拉低至少18ms
- 数据"0"的典型高电平时间是26-28μs
- 数据"1"的高电平持续70μs
常见问题排查表:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 读取超时 | 上拉电阻过大(>5.1K) | 改用4.7K电阻 |
| 数据校验失败 | 响应时间不足 | 启动信号后延迟30ms再读 |
| 湿度值固定为0 | 时序间隔不符合要求 | 调整延时函数精度 |
4.2 抗干扰优化方案
DHT11对时序抖动特别敏感,我总结的稳定读取技巧:
- 关闭所有中断 during通信过程
- 使用硬件定时器替代软件延时
- 添加0.1μF去耦电容
- 失败后自动重试3次
完整读取函数示例:
uint8_t DHT11_Read_Data(float *temp, float *humi){ uint8_t retry = 3; while(retry--){ if(DHT11_Start() == SUCCESS){ uint8_t data[5]; for(int i=0; i<5; i++) data[i] = DHT11_Read_Byte(); if(data[4] == (data[0]+data[1]+data[2]+data[3])){ *humi = data[0] + data[1]*0.1f; *temp = data[2] + data[3]*0.1f; return SUCCESS; } } HAL_Delay(100); } return ERROR; }5. 混合驱动架构设计
5.1 资源冲突解决方案
当DS18B20和DHT11共用GPIO时(通过开关切换),要注意:
- 每次切换前复位总线状态
- 重新初始化GPIO模式
- 添加至少100ms的隔离延时
我设计的引脚复用管理函数:
void OneWire_Switch_Device(DeviceType type){ static DeviceType current = NONE; if(current == type) return; HAL_GPIO_DeInit(GPIOB, GPIO_PIN_0); if(type == DS18B20){ MX_DS18B20_GPIO_Init(); }else if(type == DHT11){ MX_DHT11_GPIO_Init(); } current = type; HAL_Delay(150); }5.2 数据融合实践
在环境监控系统中,我通常这样处理多传感器数据:
- DS18B20作为主温度参考
- DHT11温度数据用于验证
- 当差值超过1℃时触发校准
- 湿度数据仅使用DHT11的
采用滑动窗口滤波算法:
#define WINDOW_SIZE 5 float temp_history[WINDOW_SIZE]; float get_filtered_temp(){ static int index = 0; temp_history[index] = DS18B20_GetTemp(); index = (index + 1) % WINDOW_SIZE; float sum = 0; for(int i=0; i<WINDOW_SIZE; i++){ sum += temp_history[i]; } return sum / WINDOW_SIZE; }6. 低功耗优化技巧
对于电池供电设备,我通过以下方式降低功耗:
- 将DS18B20设为12位分辨率(减少转换时间)
- 完成读取后立即切换GPIO到输入模式
- 使用HAL库的低功耗延时函数
- 两次采集间隔设置为10秒以上
实测电流对比:
| 工作模式 | 平均电流 |
|---|---|
| 持续转换模式 | 1.2mA |
| 间隔10秒采集 | 0.15mA |
| 深度睡眠+唤醒 | 85μA |
关键实现代码:
void Enter_LowPower_Mode(void){ HAL_GPIO_WritePin(DS18B20_PWR_GPIO_Port, DS18B20_PWR_Pin, GPIO_PIN_RESET); HAL_SuspendTick(); HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); SystemClock_Config(); // 唤醒后重新初始化时钟 }