第1章:温度传感器集成——DS18B20数字温度传感器原理

各位同学,咱们今天聊聊温度传感器。做嵌入式这么多年,我接触过的温度传感器少说也有几十种。但要说最皮实、最省心的,DS18B20绝对排前三。这玩意儿我最早是在一个冷链监控项目里用的,当时要求每个节点成本低、布线简单,还得能级联。DS18B20几乎是为这种场景量身定做的。

1.1 DS18B20长什么样?

DS18B20是Dallas半导体(现在归Maxim了)出的一款数字温度传感器。它有三个引脚:VDD(电源)、GND(地)、DQ(数据)。封装形式很多,最常见的是TO-92,长得跟个三极管似的。

它的核心参数,我列个表给你看:

参数 典型值 备注
测温范围 -55°C ~ +125°C 工业级,够用
精度 ±0.5°C(-10°C ~ +85°C) 日常场景足够了
分辨率 9~12位可配置 默认12位,0.0625°C
供电电压 3.0V ~ 5.5V 兼容3.3V和5V系统
通信接口 单总线(1-Wire) 只需一根数据线

嗯,这里要注意:虽然它标称能测到125°C,但我个人不建议长时间工作在100°C以上。封装会老化,精度也会漂。我在一个高温老化箱项目里吃过这个亏,后来老老实实加了散热措施。

2.2 单总线通信协议——一根线怎么玩?

DS18B20用的是单总线协议,说白了就是一根数据线既传数据又传时钟。你想想看,一根线要完成复位、应答、读写,是不是挺神奇的?

单总线的核心时序就三种:

  • 复位脉冲:主机拉低总线480μs以上,然后释放。DS18B20会拉低60~240μs作为应答。
  • 写时序:写0时拉低60μs,写1时拉低1~15μs然后释放。
  • 读时序:主机拉低1~15μs启动读,然后释放,在15μs内采样总线电平。

我刚开始学的时候,总觉得时序要求很苛刻。其实没那么玄乎。单总线的时序容限很大,只要别太离谱,都能正常工作。我曾经用GPIO模拟时序,延时函数直接用for循环凑合,居然也跑通了。当然,产品级代码还是得用定时器或者SysTick。

核心要点:单总线的关键就是严格遵循时序窗口。尤其是读时序,主机必须在启动后的15μs内完成采样。早了晚了都可能读到错误数据。

2.3 STM32驱动代码实现

好,咱们直接上代码。我用的是STM32F103,HAL库。其实用标准库也行,逻辑都一样。

先看初始化部分:

// 定义DS18B20数据引脚
#define DS18B20_PORT     GPIOB
#define DS18B20_PIN      GPIO_PIN_0

// 总线操作宏
#define DS18B20_OUT_LOW()   HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_RESET)
#define DS18B20_OUT_HIGH()  HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_SET)
#define DS18B20_IN_READ()   HAL_GPIO_ReadPin(DS18B20_PORT, DS18B20_PIN)

// 微秒级延时(依赖SysTick或定时器)
void delay_us(uint32_t us) {
    // 实现略,建议用定时器或DWT
}

然后是复位函数:

uint8_t DS18B20_Reset(void) {
    uint8_t presence = 0;
    
    DS18B20_OUT_LOW();
    delay_us(480);      // 拉低480μs
    DS18B20_OUT_HIGH();
    delay_us(70);       // 等待15~60μs
    
    presence = DS18B20_IN_READ();  // 读取应答
    delay_us(410);      // 等待剩余时间
    
    return presence;    // 0表示存在,1表示无应答
}

读写一个字节:

void DS18B20_WriteByte(uint8_t data) {
    for (uint8_t i = 0; i < 8; i++) {
        DS18B20_OUT_LOW();
        delay_us(2);
        if (data & 0x01) {
            DS18B20_OUT_HIGH();
        }
        delay_us(60);
        DS18B20_OUT_HIGH();
        data >>= 1;
    }
}

uint8_t DS18B20_ReadByte(void) {
    uint8_t data = 0;
    for (uint8_t i = 0; i < 8; i++) {
        data >>= 1;
        DS18B20_OUT_LOW();
        delay_us(2);
        DS18B20_OUT_HIGH();
        delay_us(5);
        if (DS18B20_IN_READ()) {
            data |= 0x80;
        }
        delay_us(60);
    }
    return data;
}

小技巧:写时序时,我习惯先拉低再判断数据位。这样能保证时序的一致性。另外,读时序里那个5μs的延时,是我实测出来的。不同STM32主频下可能需要微调。

最后是读取温度的核心函数:

float DS18B20_GetTemperature(void) {
    uint8_t tempL, tempH;
    int16_t raw;
    float temperature;
    
    DS18B20_Reset();
    DS18B20_WriteByte(0xCC);  // 跳过ROM(单设备时用)
    DS18B20_WriteByte(0x44);  // 启动温度转换
    
    // 等待转换完成(12位分辨率需750ms)
    HAL_Delay(750);
    
    DS18B20_Reset();
    DS18B20_WriteByte(0xCC);
    DS18B20_WriteByte(0xBE);  // 读取暂存器
    
    tempL = DS18B20_ReadByte();
    tempH = DS18B20_ReadByte();
    
    raw = (tempH << 8) | tempL;
    
    // 12位分辨率:每个LSB代表0.0625°C
    temperature = raw * 0.0625f;
    
    return temperature;
}

注意:0xCC是跳过ROM指令,只适用于单总线只挂一个DS18B20的情况。如果挂了多个,需要用0x55匹配ROM指令。我曾经在一个项目里偷懒用了0xCC,结果挂了两个传感器,数据全乱套了。排查了一下午才发现问题。

2.4 药盒温控场景应用

好了,理论讲完了,咱们看看实际怎么用。药盒温控,说白了就是保证药品在合适的温度范围内。比如胰岛素需要2~8°C冷藏,某些抗生素不能超过25°C。

我的设计思路是这样的:

  1. 传感器布局:药盒内部至少放两个DS18B20,一个在顶部,一个在底部。为什么?因为冷空气下沉,热空气上升,单点测温可能不准。我见过一个案例,只测了顶部温度,结果底部药品已经结冰了。
  2. 采样策略:每30秒采样一次。太快了没必要,DS18B20转换一次就要750ms。太慢了又怕错过温度突变。
  3. 报警逻辑:如果连续3次采样都超出阈值,就触发报警。这样能避免偶发干扰误报。
  4. 数据记录:把温度数据存到EEPROM或Flash里,方便追溯。药品冷链管理要求记录至少保存2年。

这里有个坑我要提醒你:DS18B20的自发热。虽然它功耗很低,但连续读取时芯片自身会升温。我实测过,连续每秒读一次,温度会虚高0.3~0.5°C。解决办法很简单——降低采样频率,或者在两次采样之间让传感器进入休眠。

嗯,说到休眠,DS18B20其实没有专门的休眠指令。但你可以通过不发起通信来让它处于低功耗状态。待机电流只有1μA左右,几乎可以忽略。

实战建议:药盒温控系统里,我建议用寄生供电模式。就是VDD和GND都接地,只靠数据线供电。这样能省一根线,而且DS18B20在寄生模式下功耗更低。不过要注意,寄生模式下转换时间会变长,而且数据线不能长时间拉低。

最后说一句,DS18B20虽然好用,但不是万能的。如果你需要更高精度(比如±0.1°C),或者需要更快的响应速度,那得考虑其他方案。但在大多数药盒温控场景里,DS18B20的性价比和可靠性,真的很难被替代。

好,这一章就到这儿。下一章咱们聊聊湿度传感器,那个更有意思。