4、传感器数据采集:常见传感器接口与ESP32读取方法

在BLE边缘AI应用中,传感器是物理世界与数字智能之间的桥梁。无论是环境监测中的温湿度、可穿戴设备中的IMU与心率传感器,还是工业现场的振动与压力信号,其数据质量与采集效率直接决定了后续模型推理的准确性与实时性。ESP32作为一颗集成Wi-Fi/BLE、拥有丰富外设接口的SoC,其ADC、I2C、SPI等总线能力是连接各类传感器的核心手段。本章将系统梳理常见传感器的接口类型与数据格式,深入解析ESP32三大读取方法的原理、配置与实战要点,帮助你在边缘AI项目中构建稳定、高效的数据采集链路。

ESP32 传感器数据采集链路架构 传感器域 模拟传感器 温度(NTC)/光敏/电位器 数字 I2C 传感器 SHT30/MPU6050/MAX30102 数字 SPI 传感器 MAX31865/ICM20602 ESP32 外设接口 ADC (12-bit) GPIO32-GPIO39 / 衰减配置 I2C 控制器 SDA/SCL + 7bit地址 + 寄存器 SPI 控制器 MOSI/MISO/SCLK/CS + 时钟极性 数据处理与AI 数据预处理 滤波/校准/单位换算 特征提取 均值/方差/FFT/峰值 边缘AI推理 TFLite Micro / ESP-DL 模拟信号经ADC数字化,数字传感器通过I2C/SPI直接读取,最终统一进入预处理与推理流水线

上图展示了ESP32传感器采集的完整链路:左侧为不同类型的传感器(模拟、I2C、SPI),中间为ESP32对应的外设接口,右侧为数据进入预处理与AI推理的流程。理解这条链路,是进行传感器选型、驱动开发和数据质量保障的基础。

4.1 常见传感器接口与数据格式

传感器根据输出信号类型可分为模拟传感器和数字传感器两大类。模拟传感器直接输出连续变化的电压或电流信号,而数字传感器内部集成了ADC或直接通过数字总线输出离散数据。在BLE边缘AI场景中,数字传感器因其抗干扰能力强、数据格式规范、支持多设备挂载等优势,成为主流选择。

传感器类型典型型号接口类型数据格式典型采样率应用场景
温度/湿度SHT30 / DHT22I2C / 单总线16bit 温度、16bit 湿度10 Hz环境监测、冷链物流
IMU (6轴)MPU6050 / ICM20602I2C / SPI16bit 加速度 + 16bit 陀螺仪100-1000 Hz姿态识别、运动检测
心率/血氧MAX30102I2C18bit 红光/红外光ADC值50-100 Hz可穿戴健康监测
气压BMP280 / LPS22HHI2C / SPI20bit 气压、16bit 温度10-50 Hz高度测量、气象站
环境光BH1750 / OPT3001I2C16bit 光照度 (lux)1-10 Hz屏幕亮度调节
模拟温度NTC热敏电阻ADC12bit 数字量 (0-4095)1-100 Hz低成本温度采集

数据格式的理解至关重要。以MPU6050为例,其加速度计输出为16位有符号整数,量程可配置为±2g、±4g、±8g、±16g。当量程设为±2g时,灵敏度为16384 LSB/g,即原始值除以16384即可得到以g为单位的物理量。若忽略量程配置而直接解析数据,将导致加速度值严重失真。同样,SHT30的温度数据为16位无符号整数,需乘以175.0再除以65535,最后减去45,才能得到摄氏度值。

经验提醒:在BLE边缘AI项目中,建议在传感器驱动层就完成原始数据到物理单位的换算,并统一数据格式(如float类型)。这样上层AI模型无需关心具体传感器型号,只需接收标准化数据,便于模型复用与系统扩展。

4.2 ESP32 ADC读取方法

ESP32内置两个12位逐次逼近型ADC(ADC1和ADC2),共支持18个通道。ADC1的8个通道(GPIO32-GPIO39)在Wi-Fi/BLE开启时仍可正常工作,而ADC2的通道(GPIO0、GPIO2、GPIO4、GPIO12-GPIO15、GPIO25-GPIO27)在Wi-Fi使用时会受到干扰,因此边缘AI项目中应优先使用ADC1通道。

ADC读取的核心配置包括衰减倍数和采样位数。衰减倍数决定了可测量的电压范围:0dB衰减对应0-1.1V,2.5dB对应0-1.5V,6dB对应0-2.2V,11dB对应0-3.3V。对于大多数传感器输出0-3.3V的场景,应选择11dB衰减。采样位数可配置为12位(默认)、11位、10位或9位,位数越高分辨率越高,但转换时间也相应增加。

// ESP32 ADC 单次读取示例 (Arduino框架)
#include "driver/adc.h"

void setup() {
  Serial.begin(115200);
  // 配置ADC1通道0 (GPIO36)
  adc1_config_width(ADC_WIDTH_BIT_12);      // 12位分辨率
  adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11); // 0-3.3V量程
}

void loop() {
  int raw = adc1_get_raw(ADC1_CHANNEL_0);   // 读取原始值 0-4095
  float voltage = raw * 3.3f / 4095.0f;     // 转换为电压
  // 根据传感器分压电路计算物理量
  float temperature = (voltage - 0.5f) * 100.0f; // 示例:LM35线性温度传感器
  Serial.printf("Raw: %d, Voltage: %.3f V, Temp: %.2f C\n", raw, voltage, temperature);
  delay(100);
}

ADC读取的精度受多种因素影响:电源噪声、参考电压漂移、采样电容充放电时间等。为提高精度,可采用多次采样取平均的方法,或使用ESP32的DMA连续采样模式。对于高精度应用,建议在PCB布局中将模拟地与数字地分离,并在ADC引脚附近添加100nF去耦电容。

风险提示:ESP32的ADC在量程两端存在非线性区域(接近0V和接近3.3V时),测量误差可达数十毫伏。若传感器输出范围接近满量程,建议调整衰减倍数或使用分压电路,使信号工作在ADC线性区(0.2V-3.0V)。

4.3 I2C接口读取方法

I2C(Inter-Integrated Circuit)是一种两线制同步串行总线,由时钟线SCL和数据线SDA组成。ESP32的I2C控制器支持主机和从机模式,通信速率最高可达400kHz(快速模式)甚至1MHz(快速模式+)。在传感器采集中,I2C因其接线简单(仅需两根线)、支持多设备挂载(通过7位地址区分)而成为数字传感器的首选接口。

I2C读取传感器的标准流程包括:总线初始化、设备地址探测、寄存器写入(配置传感器)、寄存器读取(获取数据)。以SHT30温湿度传感器为例,其I2C地址为0x44(ADDR引脚接地)或0x45(ADDR引脚接VCC)。读取数据时,需先向传感器发送测量命令0x2C06(高重复性测量),等待约15ms后,再读取6个字节的数据(温度高字节、温度低字节、CRC校验、湿度高字节、湿度低字节、CRC校验)。

// ESP32 I2C 读取SHT30示例 (Arduino框架)
#include <Wire.h>

#define SHT30_ADDR 0x44

void setup() {
  Wire.begin(21, 22);  // SDA=GPIO21, SCL=GPIO22
  Serial.begin(115200);
}

void loop() {
  // 发送测量命令
  Wire.beginTransmission(SHT30_ADDR);
  Wire.write(0x2C);
  Wire.write(0x06);
  Wire.endTransmission();
  delay(15);  // 等待测量完成

  // 读取6字节数据
  Wire.requestFrom(SHT30_ADDR, 6);
  if (Wire.available() == 6) {
    uint8_t data[6];
    for (int i = 0; i < 6; i++) data[i] = Wire.read();
    
    // 解析温度 (16bit)
    uint16_t temp_raw = (data[0] << 8) | data[1];
    float temperature = -45.0f + 175.0f * temp_raw / 65535.0f;
    
    // 解析湿度 (16bit)
    uint16_t hum_raw = (data[3] << 8) | data[4];
    float humidity = 100.0f * hum_raw / 65535.0f;
    
    Serial.printf("Temp: %.2f C, Hum: %.2f %%\n", temperature, humidity);
  }
  delay(1000);
}

I2C总线上的多个设备通过地址区分,但需注意地址冲突问题。例如,MPU6050的地址为0x68(AD0接地)或0x69(AD0接VCC),若总线上挂载两个MPU6050,需将其中一个的AD0引脚接VCC以区分。此外,I2C总线需要上拉电阻(通常为4.7kΩ至10kΩ),ESP32内部已集成上拉电阻,但若总线过长或设备过多,建议外部添加上拉电阻以保证信号质量。

4.4 SPI接口读取方法

SPI(Serial Peripheral Interface)是一种四线制高速同步串行总线,由SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)组成。相比I2C,SPI的通信速率更高(可达数十MHz),且无地址概念,通过CS引脚独立选通设备。对于需要高速率、大数据量传输的传感器(如高采样率IMU、高分辨率ADC),SPI是更优选择。

ESP32拥有4个SPI控制器(SPI0-SPI3),其中SPI0和SPI1用于Flash存储,用户可使用SPI2(HSPI)和SPI3(VSPI)。在Arduino框架中,默认使用VSPI(GPIO18-SCLK、GPIO23-MOSI、GPIO19-MISO、GPIO5-CS)。SPI通信的关键参数包括时钟极性(CPOL)和时钟相位(CPHA),不同传感器要求不同的模式(Mode 0-3)。例如,ICM20602 IMU支持SPI Mode 0(CPOL=0, CPHA=0)和Mode 3(CPOL=1, CPHA=1)。

// ESP32 SPI 读取ICM20602加速度示例 (Arduino框架)
#include <SPI.h>

#define ICM_CS 5
#define ICM_ADDR_ACCEL_XOUT_H 0x3B

void setup() {
  SPI.begin(18, 19, 23, 5);  // SCLK=18, MISO=19, MOSI=23, CS=5
  pinMode(ICM_CS, OUTPUT);
  digitalWrite(ICM_CS, HIGH);
  Serial.begin(115200);
  
  // 唤醒传感器并设置量程
  writeReg(0x6B, 0x00);  // PWR_MGMT_1: 唤醒
  writeReg(0x1C, 0x00);  // ACCEL_CONFIG: ±2g
}

void loop() {
  uint8_t data[6];
  readRegs(ICM_ADDR_ACCEL_XOUT_H, data, 6);
  
  int16_t ax = (data[0] << 8) | data[1];
  int16_t ay = (data[2] << 8) | data[3];
  int16_t az = (data[4] << 8) | data[5];
  
  float ax_g = ax / 16384.0f;  // ±2g量程灵敏度
  float ay_g = ay / 16384.0f;
  float az_g = az / 16384.0f;
  
  Serial.printf("Accel: %.3f, %.3f, %.3f g\n", ax_g, ay_g, az_g);
  delay(10);
}

void writeReg(uint8_t reg, uint8_t val) {
  digitalWrite(ICM_CS, LOW);
  SPI.transfer(reg & 0x7F);  // 写操作最高位为0
  SPI.transfer(val);
  digitalWrite(ICM_CS, HIGH);
}

void readRegs(uint8_t reg, uint8_t *buf, uint8_t len) {
  digitalWrite(ICM_CS, LOW);
  SPI.transfer(reg | 0x80);  // 读操作最高位为1
  for (uint8_t i = 0; i < len; i++) buf[i] = SPI.transfer(0x00);
  digitalWrite(ICM_CS, HIGH);
}

SPI通信的时序要求严格,CS引脚必须在整个传输过程中保持低电平,且SCLK的极性和相位必须与传感器匹配。若读取数据出现全0或全1,首先检查CS引脚是否正常拉低、SPI模式是否正确、以及MISO/MOSI是否接反。此外,SPI总线不宜过长(建议不超过10cm),否则信号反射会导致数据错误。

4.5 实战案例:多传感器融合的环境监测节点

下面通过一个完整的案例,展示如何在一个ESP32节点上同时采集温湿度(I2C)、IMU(SPI)和模拟光敏传感器(ADC),并通过BLE将数据发送至手机端。该案例模拟智能家居中的环境感知节点,为后续边缘AI推理(如人体活动识别、环境舒适度评估)提供数据基础。

硬件连接:

  • SHT30:SDA→GPIO21,SCL→GPIO22,VCC→3.3V,GND→GND
  • ICM20602:SCLK→GPIO18,MISO→GPIO19,MOSI→GPIO23,CS→GPIO5,VCC→3.3V,GND→GND
  • 光敏电阻分压电路:光敏电阻一端接3.3V,另一端接GPIO36(ADC1_CH0),并在GPIO36与GND之间接10kΩ下拉电阻

软件设计要点:

  1. 初始化I2C和SPI总线,分别配置SHT30和ICM20602的工作模式
  2. 配置ADC1通道0为12位分辨率、11dB衰减
  3. 在主循环中,以不同频率采集三类传感器:温湿度10Hz、IMU 100Hz、光照1Hz
  4. 将数据封装为结构体,通过BLE GATT特征值通知发送

关键代码片段:

typedef struct {
  float temperature;
  float humidity;
  float accel_x, accel_y, accel_z;
  float gyro_x, gyro_y, gyro_z;
  uint16_t light_raw;
} sensor_data_t;

sensor_data_t sensor_data;

void collect_sensors() {
  // 读取SHT30 (I2C)
  readSHT30(&sensor_data.temperature, &sensor_data.humidity);
  
  // 读取ICM20602 (SPI)
  readICM(&sensor_data.accel_x, &sensor_data.accel_y, &sensor_data.accel_z,
          &sensor_data.gyro_x, &sensor_data.gyro_y, &sensor_data.gyro_z);
  
  // 读取光敏 (ADC)
  sensor_data.light_raw = adc1_get_raw(ADC1_CHANNEL_0);
}

测试结果与调优:在室内环境下,SHT30温度读数稳定在25.3±0.2°C,湿度为52%±1%RH;ICM20602静止时加速度计输出为(0.01, 0.02, 0.99)g,符合预期;光敏电阻在室内光照下ADC原始值约为1800-2200。通过BLE调试工具观察,数据包间隔稳定,未出现丢包现象。

案例复盘:该案例中遇到的主要问题是ICM20602的SPI读取偶尔出现数据跳变。排查发现是SPI时钟频率过高(默认40MHz)导致信号完整性下降。将SPI时钟降至10MHz后问题解决。经验是:SPI时钟频率并非越高越好,需根据传感器数据手册和实际布线情况选择合适频率。另外,ADC通道的采样值存在约±10 LSB的噪声,通过软件滤波(移动平均)后噪声降至±3 LSB。

4.6 常见误区与总结

在ESP32传感器采集中,开发者常陷入以下误区:

  • 忽视传感器上电稳定时间:许多传感器(如SHT30、MAX30102)在上电后需要数十毫秒至数百毫秒才能稳定工作,若立即读取会得到错误数据。应在初始化后添加适当延时。
  • I2C地址错误:部分传感器有多个可选地址,需仔细核对硬件连接(如AD0引脚电平)与代码中的地址是否一致。
  • ADC量程配置不当:使用默认0dB衰减(0-1.1V)去读取0-3.3V的模拟信号,导致数据截断。应根据信号范围选择合适的衰减倍数。
  • SPI模式不匹配:不同传感器对CPOL/CPHA的要求不同,配置错误会导致数据全为0或乱码。
  • 忽略数据校验:部分传感器(如SHT30)提供CRC校验字节,用于检测传输错误。在可靠性要求高的场景中应启用CRC校验。

总结而言,ESP32传感器采集的核心在于:正确理解传感器数据手册中的接口时序与数据格式,合理配置ESP32外设参数,并通过实际测试验证数据准确性。在BLE边缘AI项目中,建议将传感器驱动封装为独立模块,提供统一的读取接口,这样既能提高代码复用性,也便于后续更换传感器型号时最小化对上层AI模型的影响。