4、传感器数据采集:DHT11温湿度传感器驱动、模拟信号读取(光敏电阻)、数字信号读取(按键)、ADC转换原理、数据滤波算法

大家好,我是你们的老朋友。今天咱们聊聊传感器数据采集这块硬骨头。

说实话,做物联网这么多年,我见过太多设备「上云死」的案例。原因往往不是云平台不行,而是数据采集这第一关就没把好。你想想看,传感器数据不准,后面再好的算法也是白搭。

4.1 DHT11温湿度传感器驱动

DHT11这玩意儿,便宜、好用,但有个坑——它的时序要求很严格。我刚开始用的时候,就因为它那单总线协议折腾了一整天。

DHT11只有一根数据线,既当输入又当输出。主机先拉低总线至少18ms,再拉高20-40us,然后等着DHT11响应。它会先拉低80us,再拉高80us,告诉你「我准备好了」。

数据格式是40位:16位湿度、16位温度、8位校验和。嗯,这里要注意,校验和必须等于前4个字节之和的低8位,否则数据作废。

核心要点:DHT11的读取间隔不能小于1秒,否则会出错。这是芯片本身的限制,不是代码问题。

// DHT11读取函数(简化版)
uint8_t DHT11_ReadData(uint8_t *humidity, uint8_t *temperature) {
    uint8_t data[5] = {0};
    
    // 主机发送起始信号
    DHT11_PIN_OUT;
    DHT11_LOW;
    delay_ms(20);  // 至少18ms
    DHT11_HIGH;
    delay_us(30);
    
    // 等待响应
    DHT11_PIN_IN;
    if (DHT11_READ == LOW) {
        while(DHT11_READ == LOW);  // 等待80us低电平结束
        while(DHT11_READ == HIGH); // 等待80us高电平结束
        
        // 读取40位数据
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 8; j++) {
                while(DHT11_READ == LOW);  // 等待50us低电平
                delay_us(30);              // 在30us处采样
                if (DHT11_READ == HIGH) {
                    data[i] |= (1 << (7 - j));
                }
                while(DHT11_READ == HIGH); // 等待高电平结束
            }
        }
        
        // 校验
        if ((data[0] + data[1] + data[2] + data[3]) == data[4]) {
            *humidity = data[0];
            *temperature = data[2];
            return 1;  // 成功
        }
    }
    return 0;  // 失败
}

避坑指南:我曾经在量产时发现一批DHT11读数异常,排查了半天,结果是数据线太长(超过20米)导致信号衰减。后来加了电平转换芯片才解决。所以,传感器和MCU之间的连线别太长,最好控制在5米以内。

4.2 模拟信号读取:光敏电阻

光敏电阻这东西,说白了就是个可变电阻。光照越强,电阻越小。但有个问题——它的阻值变化不是线性的。

我一般用分压电路来读取。光敏电阻和固定电阻串联,中间点接ADC。这样光照变化就变成了电压变化。

光照强度 光敏电阻阻值 ADC读数(10位)
强光(1000 lux) 约1kΩ 约900
室内光(500 lux) 约10kΩ 约500
弱光(10 lux) 约100kΩ 约100
黑暗(0 lux) 约1MΩ 约10

你看这个表,从强光到黑暗,阻值变化了1000倍,但ADC读数只从900变到10。所以直接用ADC值当光照强度是不行的,得做映射或者查表。

注意:光敏电阻的响应速度很慢,大概几十毫秒。如果你要做快速光控(比如相机测光),得用光电二极管。光敏电阻只适合做环境光检测这种慢速应用。

4.3 数字信号读取:按键

按键看起来简单,不就是读个高低电平吗?但实际项目中,按键的「抖动」问题能让你怀疑人生。

机械按键按下和松开时,触点会弹跳,产生多个脉冲。如果不做处理,一次按键会被识别成几十次。

我常用的消抖方法有两种:

  1. 硬件消抖:加RC滤波电路,电容选0.1uF,电阻选10kΩ。简单粗暴,但占PCB面积。
  2. 软件消抖:检测到电平变化后,延时10-20ms再读一次。如果电平一致,才确认按键动作。
// 按键消抖函数
uint8_t Key_Read(void) {
    static uint8_t last_state = 1;  // 假设上拉,默认高电平
    uint8_t current_state;
    
    current_state = KEY_PIN_READ;
    
    if (current_state != last_state) {
        delay_ms(10);  // 延时消抖
        current_state = KEY_PIN_READ;
        if (current_state != last_state) {
            last_state = current_state;
            if (current_state == 0) {  // 按下(低电平有效)
                return KEY_PRESSED;
            }
        }
    }
    return KEY_RELEASED;
}

个人经验:我建议用状态机来做按键处理,而不是简单的延时。因为延时函数会阻塞CPU,影响其他任务的实时性。特别是RTOS环境下,延时消抖简直是灾难。用定时器轮询状态机,既消抖又不阻塞。

4.4 ADC转换原理

ADC,全称模数转换器。说白了,就是把连续的模拟电压,变成离散的数字值。

最常见的ADC是逐次逼近型(SAR)。它内部有个比较器,还有个DAC。工作原理就像猜数字:先猜中间值,比较一下是大了还是小了,然后缩小范围继续猜。10位ADC需要猜10次,12位需要猜12次。

分辨率公式很简单:

分辨率 = 参考电压 / 2^n

比如参考电压3.3V,10位ADC(n=10),分辨率就是3.3/1024 ≈ 3.2mV。也就是说,每变化3.2mV,ADC读数变化1。

ADC位数 量化等级 分辨率(3.3V参考)
8位 256 12.9 mV
10位 1024 3.2 mV
12位 4096 0.8 mV
16位 65536 0.05 mV

核心要点:ADC的精度不仅取决于位数,还取决于参考电压的稳定性。我见过有人用MCU的VDD当参考电压,结果电池电压一掉,ADC读数全飘了。老老实实用外部基准电压源,比如TL431或者REF3033。

4.5 数据滤波算法

传感器数据天生带噪声。你想想看,环境抖动、电源纹波、量化误差,这些都会让数据跳来跳去。直接拿原始数据上云,那曲线跟心电图似的。

我常用的滤波算法有这几种:

  • 限幅滤波:如果当前值和上次值的差值超过阈值,就丢弃。适合去除突发噪声。
  • 中位值滤波:连续采样N次,排序后取中间值。N一般取奇数,比如3、5、7。抗脉冲干扰效果好。
  • 算术平均滤波:连续采样N次,取平均值。N越大越平滑,但响应变慢。
  • 滑动平均滤波:维护一个队列,每次采样后入队,同时出队最早的数据,然后算队列平均值。实时性好,内存占用固定。
  • 一阶低通滤波:Y(n) = α * X(n) + (1-α) * Y(n-1)。α越小,滤波越强,但滞后越大。
// 滑动平均滤波(队列长度8)
#define FILTER_SIZE 8

uint16_t MovingAverageFilter(uint16_t new_sample) {
    static uint16_t buffer[FILTER_SIZE] = {0};
    static uint8_t index = 0;
    static uint32_t sum = 0;
    
    sum -= buffer[index];          // 移除旧数据
    buffer[index] = new_sample;    // 存入新数据
    sum += buffer[index];          // 累加新数据
    index = (index + 1) % FILTER_SIZE;  // 循环索引
    
    return sum / FILTER_SIZE;      // 返回平均值
}

避坑指南:我曾经在一个温控项目里用了算术平均滤波,采样64次取平均。结果温度变化时,响应慢得像蜗牛,系统振荡了好几次才稳定。后来换成滑动平均滤波,队列长度设成16,响应速度和平滑度都满意了。记住,滤波算法没有银弹,得根据实际场景调参。

好了,传感器数据采集这块就聊到这儿。下一章咱们讲讲数据上云前的最后一步——协议封装和数据格式化。到时候见!


公众号:蓝海资料掘金营,微信deep3321