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);
}
再来看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);
}
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);
}
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);
}
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。
- 温度漂移:陀螺仪零偏随温度变化。如果工作温度范围大,建议做温度补偿。
嗯,传感器数据采集就讲这么多。说白了,就是读数据、滤波、校准、融合。每一步都不难,但每一步都容易出问题。多动手,多踩坑,慢慢就有感觉了。
下一章咱们聊聊异常检测算法,到时候会用到今天讲的这些预处理数据。