STM32c8t6与激光雷达的串口通信实战(一)
1. 认识思岚A1激光雷达
第一次拿到思岚A1激光雷达时,我完全被这个小巧的设备惊艳到了。它只有手掌大小,重量不到200克,却能在12米范围内实现360度全方位扫描。这种二维激光雷达在机器人导航、环境建模等领域特别实用,比如扫地机器人就是靠类似技术来构建房间地图的。
A1采用激光三角测距原理,简单来说就是发射红外激光,通过接收反射光来计算距离。它的核心部件包括激光发射器、接收器和旋转电机。实测下来,在室内环境下精度可以达到±2cm,完全够用。最让我惊喜的是它的采样率,在标准模式下每秒能采集8000多个数据点,扫描频率最高可达10Hz。
这个雷达有三种工作模式:
- 标准模式:平衡性能和功耗
- 高性能模式:提升扫描频率
- 低功耗模式:延长续航时间
注意:激光雷达工作时会发射不可见的红外激光,虽然功率很低,但建议不要直视激光发射口。
2. 硬件连接指南
2.1 接口说明
A1激光雷达的接口非常简单,主要就5个引脚:
- 5V供电:接STM32的5V输出
- GND:接地
- TX:雷达发送数据线
- RX:雷达接收数据线
- PWM:电机控制(可选)
我用的STM32C8T6核心板正好有多个USART接口,这里选择USART2来连接雷达。具体接线如下:
雷达5V -> STM32 5V 雷达GND -> STM32 GND 雷达TX -> STM32 PA3(USART2_RX) 雷达RX -> STM32 PA2(USART2_TX)2.2 电源注意事项
刚开始调试时,我犯了个低级错误——直接用STM32的3.3V给雷达供电。结果雷达电机转不起来,数据也不稳定。后来查手册才发现,A1需要5V供电,电机启动时瞬时电流能达到500mA。所以建议:
- 使用独立5V电源
- 或者在STM32电源处加个大电容
- 最好给电机供电线路加个开关
3. 串口通信配置
3.1 STM32串口初始化
在CubeMX里配置USART2参数:
- 波特率:115200(与雷达默认一致)
- 数据位:8位
- 停止位:1位
- 无校验位
- 硬件流控制:禁用
生成的初始化代码大概长这样:
huart2.Instance = USART2; huart2.Init.BaudRate = 115200; huart2.Init.WordLength = UART_WORDLENGTH_8B; huart2.Init.StopBits = UART_STOPBITS_1; huart2.Init.Parity = UART_PARITY_NONE; huart2.Init.Mode = UART_MODE_TX_RX; huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart2.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart2) != HAL_OK) { Error_Handler(); }3.2 数据接收处理
雷达数据是持续发送的,需要用中断方式接收。我推荐使用DMA+空闲中断的组合,效率最高。配置步骤:
- 在CubeMX中启用USART2的全局中断和DMA
- 添加空闲中断回调函数
- 在main.c中开启接收:
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE); HAL_UART_Receive_DMA(&huart2, rx_buffer, BUFFER_SIZE);实测下来,这种方式的CPU占用率几乎为零,特别适合需要同时处理其他任务的场景。
4. 数据包解析实战
4.1 数据包结构
A1雷达的数据包有点特殊,它采用自定义协议。每个数据包包含:
- 起始标志(0xAA 0x55)
- 包长度
- 数据内容
- CRC校验
我花了整整一天时间才搞明白校验算法,这里分享下关键代码:
uint8_t check_sum(uint8_t *data, uint8_t len) { uint8_t sum = 0; for(int i=0; i<len; i++){ sum += data[i]; } return sum; }4.2 角度和距离计算
雷达返回的原始数据需要转换才有意义。角度计算要注意:
- 原始数据是0-36000的整数,实际角度=原始值/100.0
- 距离单位是毫米,直接除以1000得到米
这里有个坑:角度数据是累积的,需要做差值计算才能得到瞬时角度变化。我的解决方案是用环形缓冲区存储历史数据。
5. 调试技巧与常见问题
5.1 调试工具推荐
调试串口通信时,这几个工具特别有用:
- 逻辑分析仪:抓取实际通信波形
- 串口助手:验证基础通信
- J-Scope:实时查看STM32内存数据
我习惯先用USB转TTL模块直接连接电脑,确认雷达本身工作正常,再接入STM32调试。
5.2 常见故障排查
遇到最多的问题就是收不到数据,通常检查这几个点:
- 电源电压是否稳定(用万用表实测)
- 波特率是否匹配(误差不能超过3%)
- 接线是否正确(TX/RX是否交叉)
- 地线是否共地
有一次我遇到数据时有时无的情况,最后发现是杜邦线接触不良。改用焊接后问题立马解决。所以重要项目建议直接焊接,别用杜邦线。
6. 实际应用示例
6.1 简单测距显示
先实现个基础功能:在OLED上显示前方最近物体的距离。核心代码如下:
void update_display(void) { float min_dist = 999; for(int i=0; i<point_count; i++){ if(points[i].distance < min_dist){ min_dist = points[i].distance; } } sprintf(str, "Dist: %.2fm", min_dist); OLED_ShowString(0,0,(uint8_t*)str); }6.2 简单避障算法
基于雷达数据实现避障其实很简单,主要逻辑:
- 将360度分为几个扇形区
- 计算每个区域的最小距离
- 找出最安全的移动方向
#define SECTOR_NUM 8 float sector_dist[SECTOR_NUM]; void obstacle_avoidance(void) { // 清空区域距离 for(int i=0; i<SECTOR_NUM; i++){ sector_dist[i] = 999; } // 更新各区域最小距离 for(int i=0; i<point_count; i++){ int sector = points[i].angle / (360/SECTOR_NUM); if(points[i].distance < sector_dist[sector]){ sector_dist[sector] = points[i].distance; } } // 找出最远区域 int safest = 0; for(int i=1; i<SECTOR_NUM; i++){ if(sector_dist[i] > sector_dist[safest]){ safest = i; } } // 根据safest值控制电机转向 }7. 性能优化技巧
7.1 数据过滤
原始数据会有噪点,我常用的过滤方法:
- 距离突变过滤(相邻点距离差过大则丢弃)
- 强度过滤(信号强度低于阈值则丢弃)
- 移动平均滤波(对连续几个点取平均)
#define FILTER_WIN_SIZE 3 float moving_avg_filter(float new_val) { static float buffer[FILTER_WIN_SIZE]; static uint8_t index = 0; buffer[index] = new_val; index = (index + 1) % FILTER_WIN_SIZE; float sum = 0; for(int i=0; i<FILTER_WIN_SIZE; i++){ sum += buffer[i]; } return sum / FILTER_WIN_SIZE; }7.2 内存优化
STM32C8T6的RAM只有20KB,要省着用。我的经验:
- 使用uint16_t而不是float存储原始数据
- 对角度数据使用相对值存储
- 启用编译优化-O2
如果数据量实在太大,可以考虑只存储特定角度的数据,或者降低采样频率。
