4、传感器数据采集:使用STM32 HAL库驱动超声波、红外、IMU等传感器,实现数据读取与滤波

在机器人实战项目中,传感器是系统感知物理世界的唯一窗口。无论是避障所需的超声波与红外测距,还是姿态控制所需的IMU(惯性测量单元)数据,其采集质量直接决定了上层决策与电机控制的精度。本章将围绕STM32 HAL库,系统讲解超声波、红外、IMU三类典型传感器的驱动原理、HAL库工程实现方法、数据滤波策略以及联调中的常见问题。我们将从传感器信号特性出发,逐步深入到定时器输入捕获、ADC多通道采样、I2C/SPI总线读取等HAL库核心外设应用,并给出可复用的工程代码框架。

STM32多传感器数据采集与滤波链路 超声波 HC-SR04 Trig/ Echo 定时器输入捕获 红外测距 GP2Y0A21 模拟电压输出 ADC多通道采样 IMU MPU6050 I2C总线 加速度+陀螺仪 STM32F407 HAL库 HAL_TIM_IC_CaptureCallback HAL_ADC_ConvCpltCallback HAL_I2C_Mem_Read DMA + 中断 + 轮询 数据就绪标志位 滤波与融合 滑动平均滤波 中值滤波 低通滤波(一阶) 互补滤波(IMU) 输出稳定数据 关键点:传感器信号特性差异大,需分别采用定时器捕获、ADC采样、I2C读取,并统一数据接口

上图展示了本章的核心工程链路:三类传感器通过不同的物理接口与STM32连接,在HAL库抽象层上分别采用输入捕获、ADC转换和I2C读取方式获取原始数据,随后进入统一的滤波与融合模块,最终输出可供控制算法使用的稳定数据。下面我们将逐一拆解每个环节的实现细节。

4.1 传感器信号特性与HAL库外设选型

在编写驱动代码之前,必须理解三类传感器的信号本质。超声波HC-SR04输出的是脉冲宽度信号,其高电平持续时间与距离成正比,典型测量范围2cm-400cm,精度约3mm。红外传感器GP2Y0A21输出的是模拟电压,电压与距离呈非线性反比关系,有效范围10cm-80cm。IMU传感器MPU6050通过I2C接口输出数字量,包含三轴加速度和三轴角速度原始数据,内部自带16位ADC和数字低通滤波。

传感器输出信号STM32外设HAL库关键API数据率
HC-SR04脉冲宽度(10us-20ms)定时器输入捕获HAL_TIM_IC_Start_IT20Hz
GP2Y0A21模拟电压(0.4V-3.1V)ADC1 通道HAL_ADC_Start_DMA100Hz
MPU6050I2C数字量(16bit)I2C1HAL_I2C_Mem_Read100Hz

外设选型的关键在于匹配信号特性与硬件资源。超声波测距需要精确测量脉冲宽度,因此使用定时器的输入捕获功能,将Echo引脚连接到定时器通道,通过捕获上升沿和下降沿来计算高电平持续时间。红外传感器输出模拟信号,使用ADC连续采样模式配合DMA传输,可以减轻CPU负担。IMU使用I2C总线,注意STM32的I2C时钟频率配置为400kHz快速模式,并处理总线错误恢复。

4.2 超声波传感器驱动:定时器输入捕获实现

超声波驱动分为触发和回波测量两个阶段。触发阶段,向Trig引脚发送至少10us的高电平脉冲;回波阶段,测量Echo引脚高电平持续时间。使用HAL库时,我们采用定时器输入捕获中断方式,在回调函数中记录捕获值。

// 超声波初始化
void Ultrasonic_Init(TIM_HandleTypeDef *htim) {
    HAL_TIM_IC_Start_IT(htim, TIM_CHANNEL_1);
    __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
}

// 输入捕获回调
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
    static uint32_t rising_time = 0;
    if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
        if (__HAL_TIM_GET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1) == TIM_INPUTCHANNELPOLARITY_RISING) {
            rising_time = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
            __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
        } else {
            uint32_t falling_time = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
            uint32_t pulse_width = falling_time - rising_time;
            distance_cm = pulse_width * 0.034 / 2; // 声速340m/s
            __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
        }
    }
}

上述代码中,需要注意定时器溢出处理。当脉冲宽度超过定时器周期时,需要开启定时器更新中断,在溢出回调中累加计数。实际项目中,建议将定时器预分频设置为84-1,计数周期设为65535,这样在84MHz时钟下,定时器分辨率为1us,最大可测量约65ms的脉冲宽度,对应距离约11米,完全覆盖超声波量程。

经验提示:超声波测量存在盲区(约2cm),且对被测物体表面材质敏感。在机器人避障场景中,建议将超声波安装在离地面10cm以上位置,并倾斜15度安装以减少地面反射干扰。多次测量取中值可以有效抑制偶发尖峰误差。

4.3 红外传感器驱动:ADC多通道采样与标定

红外传感器输出模拟电压,需要通过ADC转换为数字量。STM32的ADC支持多通道扫描模式,可以同时采集多个红外传感器。使用DMA传输可以自动将转换结果存储到内存数组,无需CPU干预。

// ADC多通道DMA配置
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ADC_CHANNEL_4;  // 红外1接PC4
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);

sConfig.Channel = ADC_CHANNEL_5;  // 红外2接PC5
sConfig.Rank = 2;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);

HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, 2);

// 电压转距离(非线性标定)
float voltage = adc_buffer[0] * 3.3f / 4095.0f;
float distance_cm = 27.86f / (voltage - 0.42f); // 拟合公式

红外传感器的电压-距离关系是非线性的,通常使用反比例函数拟合。不同批次的传感器存在个体差异,建议在项目调试阶段进行多点标定。标定时,将障碍物放置在已知距离(如10cm、20cm、30cm、50cm、80cm),记录对应的ADC值,然后使用最小二乘法拟合曲线参数。

风险提示:红外传感器受环境光影响较大,在强光直射或黑暗环境下测量误差会显著增加。如果机器人需要在室外运行,建议增加遮光罩或改用激光测距传感器。此外,ADC采样时间不宜过短,84周期采样时间在84MHz时钟下约1us,对于低频模拟信号足够,但若存在高频噪声,可适当增加采样时间。

4.4 IMU传感器驱动:I2C读取与姿态解算基础

MPU6050内部集成了3轴加速度计和3轴陀螺仪,通过I2C接口输出原始数据。初始化时需要配置电源管理寄存器、采样率分频器、数字低通滤波器等参数。HAL库提供了简洁的I2C读写接口。

// MPU6050初始化
uint8_t init_data = 0x00;
HAL_I2C_Mem_Write(&hi2c1, 0xD0, 0x6B, 1, &init_data, 1, 100); // 退出睡眠模式
init_data = 0x07;
HAL_I2C_Mem_Write(&hi2c1, 0xD0, 0x19, 1, &init_data, 1, 100); // 采样率分频
init_data = 0x06;
HAL_I2C_Mem_Write(&hi2c1, 0xD0, 0x1A, 1, &init_data, 1, 100); // 数字低通滤波

// 读取加速度和陀螺仪数据
uint8_t raw_data[14];
HAL_I2C_Mem_Read(&hi2c1, 0xD0, 0x3B, 1, raw_data, 14, 100);
int16_t ax = (raw_data[0] << 8) | raw_data[1];
int16_t ay = (raw_data[2] << 8) | raw_data[3];
int16_t az = (raw_data[4] << 8) | raw_data[5];
int16_t gx = (raw_data[8] << 8) | raw_data[9];
int16_t gy = (raw_data[10] << 8) | raw_data[11];
int16_t gz = (raw_data[12] << 8) | raw_data[13];

读取到的原始数据需要转换为物理单位。加速度计默认量程为±2g,灵敏度为16384 LSB/g;陀螺仪默认量程为±250°/s,灵敏度为131 LSB/(°/s)。转换公式为:物理值 = 原始值 / 灵敏度。姿态解算通常使用互补滤波或Mahony算法,将加速度计的低频可信与陀螺仪的高频可信结合。

4.5 数据滤波策略:从原始数据到稳定信号

传感器原始数据往往包含噪声和尖峰干扰,直接用于控制会导致电机抖动。常用的滤波方法包括滑动平均滤波、中值滤波和一阶低通滤波。选择滤波策略需要权衡响应速度和噪声抑制效果。

滤波方法原理适用场景延迟计算量
滑动平均取最近N个样本平均值超声波、红外N/2个采样周期低
中值滤波取N个样本中间值尖峰干扰明显场景N/2个采样周期中
一阶低通y[n]=αx[n]+(1-α)y[n-1]IMU、红外1个采样周期极低

对于超声波传感器,由于存在偶发的多路径反射尖峰,推荐使用中值滤波,窗口大小取5。对于红外传感器,滑动平均滤波效果较好,窗口大小取10。对于IMU数据,一阶低通滤波配合互补滤波效果最佳,α值通常取0.1-0.3。

// 一阶低通滤波实现
float lowpass_filter(float input, float prev_output, float alpha) {
    return alpha * input + (1.0f - alpha) * prev_output;
}

// 滑动平均滤波实现
float moving_average(float new_sample, float *buffer, uint8_t size, uint8_t *index) {
    buffer[*index] = new_sample;
    *index = (*index + 1) % size;
    float sum = 0;
    for (uint8_t i = 0; i < size; i++) {
        sum += buffer[i];
    }
    return sum / size;
}
实战案例:在某轮式机器人避障项目中,我们同时使用超声波和红外传感器检测前方障碍物。初始阶段,超声波数据直接用于控制,导致机器人在接近光滑墙面时频繁抖动。分析发现,超声波在墙面倾斜角度大于15度时会产生错误回波,距离值在20cm到80cm之间跳变。解决方案是采用5点中值滤波,并将超声波与红外数据进行加权融合:当超声波数据在红外有效范围内时,以红外数据为主(权重0.7),超声波为辅(权重0.3);当超出红外范围时,完全信任超声波数据。融合后的距离值平滑稳定,机器人避障动作流畅,未再出现误判。

4.6 多传感器数据同步与时间戳管理

在机器人控制系统中,不同传感器的采样频率和数据延迟不同,直接使用会导致控制时序混乱。超声波采样率约20Hz,红外约100Hz,IMU约100Hz。为了统一数据时间基准,建议在主循环中设置一个50ms的控制周期,在每个周期开始时读取最新的传感器数据,并记录数据的时间戳。

typedef struct {
    float distance_ultrasonic;
    float distance_infrared;
    float accel_x, accel_y, accel_z;
    float gyro_x, gyro_y, gyro_z;
    uint32_t timestamp_ms;
} SensorData_t;

SensorData_t sensor_data;

void Sensor_Update(void) {
    sensor_data.distance_ultrasonic = ultrasonic_distance_filtered;
    sensor_data.distance_infrared = infrared_distance_filtered;
    sensor_data.accel_x = accel_x_filtered;
    // ... 其他数据
    sensor_data.timestamp_ms = HAL_GetTick();
}

时间戳管理对于后续的卡尔曼滤波或运动控制至关重要。如果使用RTOS,可以为每个传感器创建独立任务,通过消息队列将数据发送到控制任务。控制任务在接收到所有传感器数据后,根据时间戳进行数据对齐。

4.7 常见误区与调试技巧

在传感器驱动开发中,初学者常犯以下错误:一是超声波输入捕获未处理定时器溢出,导致远距离测量错误;二是ADC未配置DMA传输完成中断,导致读取到半更新的数据;三是I2C通信未处理总线忙状态,导致初始化失败;四是滤波参数设置不当,导致数据延迟过大或噪声抑制不足。

调试建议:使用逻辑分析仪或示波器观察传感器输出波形,确认硬件连接正确。在代码中增加调试打印,通过串口输出原始数据和滤波后数据,对比分析滤波效果。对于I2C设备,可以使用HAL_I2C_IsDeviceReady函数检测设备是否在线。对于ADC,注意检查DMA传输完成标志位,确保数据完整。

总结本章内容,传感器数据采集的核心在于理解信号特性、合理选择外设、正确配置HAL库参数,并通过滤波算法提升数据质量。在实际项目中,建议先逐个调试传感器,确认数据正确后再进行多传感器融合。下一阶段,我们将把这些传感器数据用于机器人的运动控制与决策算法中。