第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。
我的设计思路是这样的:
- 传感器布局:药盒内部至少放两个DS18B20,一个在顶部,一个在底部。为什么?因为冷空气下沉,热空气上升,单点测温可能不准。我见过一个案例,只测了顶部温度,结果底部药品已经结冰了。
- 采样策略:每30秒采样一次。太快了没必要,DS18B20转换一次就要750ms。太慢了又怕错过温度突变。
- 报警逻辑:如果连续3次采样都超出阈值,就触发报警。这样能避免偶发干扰误报。
- 数据记录:把温度数据存到EEPROM或Flash里,方便追溯。药品冷链管理要求记录至少保存2年。
这里有个坑我要提醒你:DS18B20的自发热。虽然它功耗很低,但连续读取时芯片自身会升温。我实测过,连续每秒读一次,温度会虚高0.3~0.5°C。解决办法很简单——降低采样频率,或者在两次采样之间让传感器进入休眠。
嗯,说到休眠,DS18B20其实没有专门的休眠指令。但你可以通过不发起通信来让它处于低功耗状态。待机电流只有1μA左右,几乎可以忽略。
实战建议:药盒温控系统里,我建议用寄生供电模式。就是VDD和GND都接地,只靠数据线供电。这样能省一根线,而且DS18B20在寄生模式下功耗更低。不过要注意,寄生模式下转换时间会变长,而且数据线不能长时间拉低。
最后说一句,DS18B20虽然好用,但不是万能的。如果你需要更高精度(比如±0.1°C),或者需要更快的响应速度,那得考虑其他方案。但在大多数药盒温控场景里,DS18B20的性价比和可靠性,真的很难被替代。
好,这一章就到这儿。下一章咱们聊聊湿度传感器,那个更有意思。