3、传感器数据采集基础:温度传感器、湿度传感器、加速度计、陀螺仪的数据读取与预处理

各位同学,咱们今天聊聊传感器数据采集。说实话,这是嵌入式系统里最基础也最容易踩坑的环节。我见过太多项目,算法写得天花乱坠,结果传感器数据一塌糊涂,最后全白搭。

传感器数据采集,说白了就是让单片机跟传感器“对话”。对话的方式无非两种:I2C或者SPI。温度湿度传感器大多用I2C,加速度计和陀螺仪则两者都有。我个人习惯,能用I2C就用I2C,因为连线少,两根线搞定。

3.1 温度传感器与湿度传感器:DHT22与SHT30

先说说温湿度传感器。市面上最常见的两款:DHT22和SHT30。

DHT22,单总线协议,便宜但精度一般。我曾在农业大棚项目里用过它,说实话,湿度超过80%时数据就开始飘了。

SHT30,I2C接口,精度高,稳定性好。我个人更推荐它,虽然贵几块钱,但省心。

来看DHT22的读取代码:

#include <DHT.h>

#define DHTPIN 2
#define DHTTYPE DHT22

DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(115200);
  dht.begin();
}

void loop() {
  float h = dht.readHumidity();
  float t = dht.readTemperature();

  if (isnan(h) || isnan(t)) {
    Serial.println("读取失败!检查接线");
    return;
  }

  Serial.print("温度: ");
  Serial.print(t);
  Serial.print(" °C, 湿度: ");
  Serial.print(h);
  Serial.println(" %");
  delay(2000);
}
注意:DHT22的读取间隔必须大于2秒。我曾经连续快速读取,结果传感器直接“罢工”了,等了5分钟才恢复。

再来看SHT30的代码,I2C方式更优雅:

#include <Wire.h>
#include <Adafruit_SHT31.h>

Adafruit_SHT31 sht31 = Adafruit_SHT31();

void setup() {
  Serial.begin(115200);
  if (!sht31.begin(0x44)) {
    Serial.println("SHT30未找到!");
    while (1) delay(1);
  }
}

void loop() {
  float t = sht31.readTemperature();
  float h = sht31.readHumidity();

  if (!isnan(t)) {
    Serial.print("温度: "); Serial.print(t); Serial.print(" °C, ");
    Serial.print("湿度: "); Serial.print(h); Serial.println(" %");
  } else {
    Serial.println("读取失败");
  }
  delay(1000);
}
小技巧:SHT30的I2C地址默认是0x44,但有些模块是0x45。如果读不到数据,先检查地址对不对。我踩过这个坑,折腾了半小时才发现是地址搞错了。

3.2 加速度计与陀螺仪:MPU6050

加速度计和陀螺仪,最经典的组合就是MPU6050。它内部集成了3轴加速度计和3轴陀螺仪,通过I2C输出数据。

加速度计测量的是“比力”,说白了就是物体受到的加速度。陀螺仪测量的是角速度,也就是旋转的快慢。

为什么要同时用这两个?因为单独用加速度计,动态响应差;单独用陀螺仪,会有零漂。两者结合,通过卡尔曼滤波或互补滤波,才能得到稳定的姿态角。

来看MPU6050的原始数据读取:

#include <Wire.h>
#include <MPU6050.h>

MPU6050 mpu;

void setup() {
  Serial.begin(115200);
  Wire.begin();
  mpu.initialize();

  if (!mpu.testConnection()) {
    Serial.println("MPU6050连接失败");
    while (1);
  }
}

void loop() {
  int16_t ax, ay, az;
  int16_t gx, gy, gz;

  mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);

  Serial.print("加速度: ");
  Serial.print(ax); Serial.print(", ");
  Serial.print(ay); Serial.print(", ");
  Serial.print(az); Serial.print(" | ");
  Serial.print("陀螺仪: ");
  Serial.print(gx); Serial.print(", ");
  Serial.print(gy); Serial.print(", ");
  Serial.print(gz); Serial.println();

  delay(100);
}
重要:MPU6050的原始数据是16位整数,范围取决于量程设置。加速度计量程默认±2g,陀螺仪默认±250°/s。要转换成物理单位,需要做换算。

3.3 数据预处理:滤波与校准

原始数据直接拿来用?别闹。传感器数据里全是噪声,不处理根本没法用。

我常用的预处理方法有三种:

  • 均值滤波:连续采样N次,取平均值。简单有效,适合静态场景。
  • 中值滤波:排序后取中间值。能有效去除脉冲噪声,比如偶尔的尖峰。
  • 低通滤波:一阶RC滤波,适合动态场景。公式:output = α * input + (1-α) * last_output。

来看均值滤波的代码实现:

#define SAMPLE_COUNT 10

float readTemperatureFiltered() {
  float sum = 0;
  for (int i = 0; i < SAMPLE_COUNT; i++) {
    sum += dht.readTemperature();
    delay(10);
  }
  return sum / SAMPLE_COUNT;
}

低通滤波更常用,尤其对加速度计数据:

float alpha = 0.2;  // 滤波系数,越小越平滑
float filteredAx = 0;

void loop() {
  int16_t ax, ay, az;
  mpu.getAcceleration(&ax, &ay, &az);

  // 低通滤波
  filteredAx = alpha * ax + (1 - alpha) * filteredAx;

  Serial.println(filteredAx);
  delay(10);
}
经验之谈:α值怎么选?如果数据变化慢(比如温度),α取0.1-0.2;如果变化快(比如加速度),α取0.3-0.5。我一般先试0.2,再根据波形调整。

3.4 校准:消除零偏

传感器都有零偏。比如陀螺仪静止时,输出应该为0,但实际可能有个几十的偏移。不校准的话,积分出来的角度会一直漂。

校准方法很简单:静止状态下采样100次,取平均值作为偏移量。之后每次读数都减去这个偏移。

int16_t gx_offset = 0, gy_offset = 0, gz_offset = 0;

void calibrateGyro() {
  long sumX = 0, sumY = 0, sumZ = 0;
  for (int i = 0; i < 100; i++) {
    int16_t gx, gy, gz;
    mpu.getRotation(&gx, &gy, &gz);
    sumX += gx;
    sumY += gy;
    sumZ += gz;
    delay(5);
  }
  gx_offset = sumX / 100;
  gy_offset = sumY / 100;
  gz_offset = sumZ / 100;
}
警告:校准时传感器必须绝对静止。我曾在桌面上校准,结果桌子有点晃,校准值全偏了。后来我改用三脚架固定,才搞定。

3.5 数据融合:互补滤波

有了加速度计和陀螺仪的数据,怎么得到稳定的角度?互补滤波是最简单的方法。

原理:加速度计算角度(长期稳定但短期噪声大),陀螺仪积分算角度(短期准但长期漂移)。两者互补,各取所长。

float angle = 0;
float dt = 0.01;  // 采样周期10ms

void complementaryFilter(float accAngle, float gyroRate) {
  angle = 0.98 * (angle + gyroRate * dt) + 0.02 * accAngle;
}

0.98和0.02是权重系数,加起来等于1。权重越大,越信任陀螺仪;权重越小,越信任加速度计。我一般用0.98,效果不错。

3.6 避坑指南

最后,分享几个我踩过的坑:

  • 电源噪声:传感器和电机共用一个电源?数据肯定跳。我给传感器单独加了LDO,问题解决。
  • I2C上拉电阻:忘了加上拉电阻?通信时好时坏。4.7kΩ是标准值。
  • 采样频率:别贪快。MPU6050内部采样率最高1kHz,但你的主控可能处理不过来。我一般用100Hz。
  • 温度漂移:陀螺仪零偏随温度变化。如果工作温度范围大,建议做温度补偿。

嗯,传感器数据采集就讲这么多。说白了,就是读数据、滤波、校准、融合。每一步都不难,但每一步都容易出问题。多动手,多踩坑,慢慢就有感觉了。

下一章咱们聊聊异常检测算法,到时候会用到今天讲的这些预处理数据。