2. 传感器数据采集基础:温度传感器(DS18B20)、湿度传感器(DHT22)、光照传感器(BH1750)的原理与驱动

各位同学,欢迎来到第二章。这一章我们直接上手干活——聊聊嵌入式温室里最常用的三个传感器:DS18B20、DHT22 和 BH1750。

说实话,传感器驱动这块,我早年踩过不少坑。记得第一次用 DS18B20 的时候,时序没调好,读出来的温度忽高忽低,差点把一整个温室的幼苗给坑了。嗯,从那以后,我对时序就特别敏感。

好,咱们一个一个来。

2.1 温度传感器 DS18B20

DS18B20 是个好东西。它用单总线(1-Wire)通信,说白了就是一根数据线既当电源又传数据。你想想看,省引脚啊!

核心原理:

  • 测温范围:-55°C 到 +125°C,精度 ±0.5°C(-10°C 到 +85°C 范围内)
  • 分辨率可配置:9 到 12 位,默认 12 位
  • 每个芯片有唯一的 64 位 ROM 编码,可以挂多个在一条总线上

重要提醒:DS18B20 的转换时间取决于分辨率。12 位分辨率下,最大转换时间 750ms。我建议你在代码里别急着读,等够时间再说。

驱动要点:

单总线协议其实不复杂,但时序要求严格。我习惯用 GPIO 模拟时序,因为很多 MCU 没有硬件 1-Wire 控制器。

核心步骤就三步:

  1. 初始化:主机拉低总线 480μs 以上,然后释放,等待 DS18B20 拉低 60-240μs 应答
  2. ROM 命令:跳过 ROM(0xCC)或者匹配 ROM(0x55)
  3. 功能命令:启动温度转换(0x44),然后读暂存器(0xBE)
// DS18B20 读取温度示例(简化版)
uint8_t ds18b20_reset(void) {
    uint8_t presence;
    GPIO_LOW;           // 拉低总线
    delay_us(480);      // 保持 480μs
    GPIO_HIGH;          // 释放总线
    delay_us(60);       // 等待 60μs
    presence = GPIO_READ; // 读应答信号
    delay_us(420);      // 等待剩余时间
    return presence;    // 0 表示有设备应答
}

float ds18b20_read_temp(void) {
    uint8_t temp_l, temp_h;
    int16_t raw_temp;
    
    ds18b20_reset();
    ds18b20_write_byte(0xCC);  // 跳过 ROM
    ds18b20_write_byte(0x44);  // 启动转换
    delay_ms(750);             // 等待转换完成
    
    ds18b20_reset();
    ds18b20_write_byte(0xCC);  // 跳过 ROM
    ds18b20_write_byte(0xBE);  // 读暂存器
    
    temp_l = ds18b20_read_byte();
    temp_h = ds18b20_read_byte();
    
    raw_temp = (temp_h << 8) | temp_l;
    return raw_temp * 0.0625;  // 12 位分辨率下,每个 LSB 代表 0.0625°C
}

我的经验:实际项目中,我一般会在启动转换后加一个超时机制。因为有时候传感器会掉线,死等 750ms 会卡死整个系统。加个 1 秒超时,读不到就重试,稳得很。

2.2 温湿度传感器 DHT22

DHT22(也叫 AM2302)是温室里测温湿度的主力。它比 DHT11 精度高不少,价格也贵一点,但我觉得值。

核心参数:

参数 DHT22 DHT11(对比)
温度精度 ±0.5°C ±2°C
湿度精度 ±2% RH ±5% RH
采样周期 2 秒 1 秒
数据格式 40 位(16 位湿度 + 16 位温度 + 8 位校验) 40 位

通信协议:

DHT22 也是单总线,但时序和 DS18B20 不一样。它由主机发起,传感器响应后发送 40 位数据。

我当年第一次调 DHT22 的时候,发现读出来的湿度一直是 0。查了半天,原来是主机拉低起始信号的时间不够。DHT22 要求至少 1ms,我给了 800μs,就差那么一点。

// DHT22 读取数据示例
uint8_t dht22_read_data(float *temp, float *hum) {
    uint8_t data[5] = {0};
    uint8_t i, j;
    
    // 主机发送起始信号
    GPIO_LOW;
    delay_ms(2);        // 拉低至少 1ms,我习惯给 2ms
    GPIO_HIGH;
    delay_us(30);       // 拉高 20-40μs
    
    // 等待传感器响应
    if (GPIO_READ == 0) {
        while (GPIO_READ == 0);  // 等待拉高
        while (GPIO_READ == 1);  // 等待拉低
    } else {
        return 0;  // 无响应
    }
    
    // 读取 40 位数据
    for (j = 0; j < 5; j++) {
        for (i = 0; i < 8; i++) {
            while (GPIO_READ == 0);  // 等待数据开始
            delay_us(40);            // 采样点
            if (GPIO_READ == 1) {
                data[j] |= (1 << (7 - i));
            }
            while (GPIO_READ == 1);  // 等待数据结束
        }
    }
    
    // 校验
    if ((data[0] + data[1] + data[2] + data[3]) != data[4]) {
        return 0;  // 校验失败
    }
    
    *hum = ((data[0] << 8) | data[1]) / 10.0;
    *temp = ((data[2] << 8) | data[3]) / 10.0;
    return 1;
}

注意:DHT22 的采样周期是 2 秒。你如果读得太频繁,它会不理你。我曾经在循环里每 100ms 读一次,结果传感器直接罢工了。后来加了 2 秒的间隔,一切正常。

2.3 光照传感器 BH1750

BH1750 是数字光照传感器,用 I2C 接口。它直接输出 lux 值,不用你自己算,省心。

为什么选它?

  • 测量范围:1 - 65535 lux,覆盖温室从阴天到强光
  • 分辨率可调:高分辨率模式(1 lux)、低分辨率模式(4 lux)
  • 内置 ADC,直接输出数字量,抗干扰能力强

I2C 地址:

BH1750 的 I2C 地址由 ADDR 引脚决定:

  • ADDR 接 GND:地址 0x23
  • ADDR 接 VCC:地址 0x5C

我习惯把 ADDR 接 GND,因为 0x23 好记。你想想看,调试的时候少查一次手册,就少一次出错的机会。

驱动流程:

  1. 上电后发送上电命令(0x01)
  2. 设置测量模式(如连续高分辨率模式 0x10)
  3. 等待测量完成(高分辨率模式约 120ms)
  4. 读取 2 字节数据,计算 lux = (high << 8 | low) / 1.2
// BH1750 读取光照示例(I2C 通信)
#define BH1750_ADDR     0x23
#define BH1750_POWER_ON 0x01
#define BH1750_MODE_H   0x10  // 连续高分辨率模式

float bh1750_read_lux(void) {
    uint8_t buf[2];
    uint16_t raw;
    
    // 上电
    i2c_write(BH1750_ADDR, BH1750_POWER_ON);
    delay_ms(10);
    
    // 设置模式
    i2c_write(BH1750_ADDR, BH1750_MODE_H);
    delay_ms(180);  // 等待转换完成,多给点余量
    
    // 读取数据
    i2c_read(BH1750_ADDR, buf, 2);
    
    raw = (buf[0] << 8) | buf[1];
    return raw / 1.2;  // 高分辨率模式,除以 1.2 得到 lux
}

避坑指南:BH1750 有个小脾气——它怕光突变。如果你从黑暗环境突然拿到强光下,第一次读到的值可能偏小。我一般会连续读两次,取第二次的值,或者做一次简单的低通滤波。

2.4 三种传感器的选型对比

最后,我给大家整理一个对比表。选型的时候,对着这个表看,基本不会出错。

特性 DS18B20 DHT22 BH1750
测量对象 温度 温度 + 湿度 光照强度
通信接口 1-Wire 1-Wire(自定义时序) I2C
精度 ±0.5°C ±0.5°C / ±2% RH ±20% 典型
采样周期 750ms(12位) 2s 120ms(高分辨率)
典型应用 多点温度监测 温湿度一体监测 光照强度监测
我的推荐 需要多个温度点时首选 温室环境监测标配 数字光照,简单可靠

好了,这一章的内容就到这里。三种传感器,三种接口,各有各的脾气。你只要记住:DS18B20 重时序,DHT22 重间隔,BH1750 重滤波。把这三点拿捏住,驱动就稳了。

下一章,我们聊聊如何把这些传感器整合到一起,做一个真正的温室数据采集系统。到时候,我会分享一个我实际项目中用过的架构,保证实用。

课后思考:如果让你用这三种传感器设计一个温室环境监测节点,你会怎么安排它们的采样顺序?为什么?