第三章 传感器数据采集:I2C/SPI/UART通信协议、超声波模块驱动编写、激光雷达点云数据解析、IMU数据滤波
各位同学,欢迎来到第三章。这一章我们聊聊传感器数据采集。说实话,这部分是避障系统的「眼睛」和「耳朵」。数据采不准,后面路径规划再漂亮也是白搭。我见过太多项目,算法调得天花乱坠,结果传感器数据一塌糊涂——飞控直接炸机。
这一章我们聚焦四个核心点:通信协议、超声波驱动、激光雷达点云、IMU滤波。每个点我都会结合项目经验来讲。准备好了吗?我们开始。
3.1 通信协议:I2C、SPI、UART 怎么选?
先问个问题:为什么无人机上要用三种不同的通信协议?
答案很简单——没有一种协议是万能的。我早期做飞控时,总想着「一个协议打天下」,结果发现I2C挂太多设备就卡死,SPI线太长就丢数据,UART速率上不去。后来学乖了,该用哪个用哪个。
3.1.1 I2C:适合短距离、低速、多设备
I2C只有两根线:SCL(时钟)和SDA(数据)。所有设备挂在同一条总线上,靠地址区分。优点是接线少,缺点是速率慢(标准模式100kHz,快速模式400kHz)。
我在项目中遇到过:用I2C挂了三个传感器——气压计、磁力计、温度计。结果发现磁力计和气压计地址冲突了。嗯,I2C设备地址是7位,理论上128个地址,但很多厂家默认地址一样。解决办法?要么换地址引脚,要么加I2C多路复用器。
3.1.2 SPI:高速、全双工、点对点
SPI有四根线:MOSI、MISO、SCLK、CS。每个设备一个片选引脚。速率可以跑到几十MHz,适合激光雷达、IMU这类高速传感器。
避坑指南:我曾经在SPI线上接了10cm的杜邦线,结果数据全是乱码。为什么?SPI对信号完整性要求高,长线会导致反射和串扰。后来换成双绞线,问题解决。记住:SPI线长不要超过20cm,最好用屏蔽线。
3.1.3 UART:简单、远距离、异步
UART只需要TX和RX两根线。没有时钟线,靠波特率同步。适合GPS模块、数传电台这类设备。
UART有个坑:波特率必须匹配。我见过有人把GPS设成115200,飞控设成9600,结果数据全是乱码。嗯,这种低级错误我也犯过。
| 协议 | 线数 | 速率 | 距离 | 典型设备 |
|---|---|---|---|---|
| I2C | 2 | 100kHz-400kHz | 几十厘米 | 气压计、磁力计 |
| SPI | 4+ | 10MHz-80MHz | 20cm以内 | IMU、激光雷达 |
| UART | 2 | 9600-921600bps | 几米 | GPS、超声波 |
3.2 超声波模块驱动编写
超声波模块,说白了就是「发个声波,听个回声」。HC-SR04是最常见的,便宜、皮实,但精度一般。
驱动逻辑很简单:
- Trig引脚拉高10us,触发发射
- Echo引脚等待高电平
- 测量高电平持续时间
- 距离 = 时间 × 声速 / 2
代码实现(STM32 HAL库):
// 超声波测距函数
float ultrasonic_get_distance(void) {
uint32_t pulse_time = 0;
// 触发发射
HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET);
delay_us(10);
HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET);
// 等待Echo高电平,超时1ms
uint32_t timeout = 1000;
while(HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) == GPIO_PIN_RESET) {
if(timeout-- == 0) return -1.0f; // 超时返回-1
}
// 测量高电平宽度
timeout = 10000; // 最大测量距离约1.7m
while(HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) == GPIO_PIN_SET) {
pulse_time++;
delay_us(1);
if(timeout-- == 0) return -1.0f;
}
// 计算距离(单位:cm)
float distance = (float)pulse_time * 0.034 / 2.0f;
return distance;
}
3.3 激光雷达点云数据解析
激光雷达比超声波高级多了。它发射激光束,测量反射时间,得到精确的距离值。单线雷达(如RPLIDAR A1)每秒能测几千个点,形成一圈点云。
点云数据通常通过UART或USB传输。数据格式各家不同,但核心思路一致:
- 每个数据包包含起始标志、角度、距离、信号质量
- 角度范围0-360度,分辨率通常0.5度或1度
- 距离单位毫米,有效范围0.15m-12m
解析代码片段(以RPLIDAR为例):
typedef struct {
float angle; // 角度(度)
float distance; // 距离(毫米)
uint8_t quality; // 信号质量(0-255)
} LidarPoint_t;
// 解析一帧数据
int parse_lidar_frame(uint8_t *buffer, int len, LidarPoint_t *points) {
int point_count = 0;
int i = 0;
while(i < len - 5) {
// 查找起始标志 0xFA 0xA5
if(buffer[i] == 0xFA && buffer[i+1] == 0xA5) {
// 解析角度和距离(具体格式参考数据手册)
uint16_t raw_angle = (buffer[i+2] << 8) | buffer[i+3];
uint16_t raw_dist = (buffer[i+4] << 8) | buffer[i+5];
points[point_count].angle = raw_angle / 64.0f;
points[point_count].distance = raw_dist;
points[point_count].quality = buffer[i+6];
point_count++;
i += 7; // 跳到下一个数据点
} else {
i++;
}
}
return point_count;
}
我踩过的坑:激光雷达在强光下会失效。有一次在正午阳光下测试,雷达直接「瞎了」,距离全报0。后来加了遮光罩,问题解决。另外,雷达旋转时会产生振动,影响IMU数据。建议用橡胶垫减震。
3.4 IMU数据滤波
IMU(惯性测量单元)包含加速度计和陀螺仪。原始数据噪声很大,直接使用会导致飞控震荡。滤波是必须的。
常用的滤波方法:
- 低通滤波:简单、计算量小,适合加速度计
- 互补滤波:融合加速度计和陀螺仪,适合姿态估计
- 卡尔曼滤波:精度高,但计算量大,适合高端飞控
我个人习惯用互补滤波,性价比最高。代码实现:
// 互补滤波,融合加速度计和陀螺仪
void complementary_filter(float acc_angle, float gyro_rate, float dt, float *angle) {
float alpha = 0.98f; // 权重系数
// 陀螺仪积分
float gyro_angle = *angle + gyro_rate * dt;
// 互补融合
*angle = alpha * gyro_angle + (1.0f - alpha) * acc_angle;
}
alpha值怎么调?我一般从0.98开始试。alpha越大,越信任陀螺仪(动态响应快);alpha越小,越信任加速度计(静态精度高)。
3.5 本章知识体系
下面这张图展示了传感器数据采集的整体流程。从物理层到应用层,每一层都有坑,每一层都要细心。
从图中可以看到,数据从物理层一路向上,经过传感器采集、数据处理,最终服务于避障路径规划。每一层都有坑,每一层都要细心。
好了,第三章的内容就到这里。传感器数据采集是避障系统的基石,基础打牢了,后面才能飞得稳。下一章我们聊聊路径规划的核心算法——A*和Dijkstra,敬请期待。
公众号:蓝海资料掘金营,微信deep3321