176 lines
6.5 KiB
Markdown
176 lines
6.5 KiB
Markdown
|
|
激光雷达管理模块 (Laser Manager) 🚀
|
|||
|
|
===========================
|
|||
|
|
|
|||
|
|
本模块专为 **STM32H7 + FreeRTOS** 环境打造,用于同时驱动和解析 4 路单点激光雷达(2 路连续流 STP-23L,2 路文本流 ATK-MS53L1M)。
|
|||
|
|
|
|||
|
|
采用 **DMA 环形缓冲 + 串口空闲中断 (IDLE) + RTOS 事件驱动** 架构,彻底消灭单字节中断引起的 CPU 拥堵,是适用于机器人底盘避障与路径规划的“企业级”底层驱动。
|
|||
|
|
✨ 核心特性
|
|||
|
|
------
|
|||
|
|
|
|||
|
|
1. **零中断阻塞**:所有数据均由硬件 DMA 搬运,CPU 仅在数据接收过半/完成或触发 IDLE 空闲时被唤醒。
|
|||
|
|
|
|||
|
|
2. **免 Cache 物理隔离**:基于 MPU 划分专用 `.dma_buffer` 特区,彻底杜绝 Cortex-M7 的 Cache 一致性导致的数据乱码或 USB 掉线问题。
|
|||
|
|
|
|||
|
|
3. **极速解析算法**:
|
|||
|
|
|
|||
|
|
* **STP 协议**:采用 O(1) 环形指针流式提取,**0 内存搬运 (`memmove`)**。
|
|||
|
|
|
|||
|
|
* **ATK 协议**:采用 O(1) 字符特征匹配,彻底抛弃耗时的 `strstr` 和 `strtol` 标准库函数。
|
|||
|
|
|
|||
|
|
4. **高质量数据输出**:内置 **滑动中值滤波 (Median Filter)**,有效剔除玻璃反光、灰尘引起的突发测距尖峰,提供平滑稳定的控制级数据。
|
|||
|
|
|
|||
|
|
5. **安全可靠**:
|
|||
|
|
|
|||
|
|
* 采用 `taskENTER_CRITICAL()` 保护快照读取,杜绝多线程数据撕裂。
|
|||
|
|
|
|||
|
|
* 严格区分中断上下文 (`FROM_ISR`) 与任务上下文的临界区调用。
|
|||
|
|
|
|||
|
|
* 内置 `HAL_UART_ErrorCallback`,在遭受电磁干扰触发 ORE/FE 错误时自动重启 DMA,永久防死机。
|
|||
|
|
|
|||
|
|
* * *
|
|||
|
|
|
|||
|
|
⚙️ 移植与配置指南
|
|||
|
|
----------
|
|||
|
|
|
|||
|
|
### 第一步:STM32CubeMX 硬件配置
|
|||
|
|
|
|||
|
|
1. **MPU 设置 (极其重要)**
|
|||
|
|
|
|||
|
|
* 进入 `Cortex-M7` -> `MPU Control`
|
|||
|
|
|
|||
|
|
* Enable MPU
|
|||
|
|
|
|||
|
|
* **Region 1**: Base Address = **`0x30000000`**, Size = **`32KB`**
|
|||
|
|
|
|||
|
|
* Access Permission: **`ALL ACCESS PERMITTED`**
|
|||
|
|
|
|||
|
|
* Cacheable: **`Disable`** (彻底关闭该区域的 D-Cache)
|
|||
|
|
|
|||
|
|
2. **串口与 DMA 设置**
|
|||
|
|
|
|||
|
|
* **STP 雷达** (如 USART2, UART4 - 230400bps):添加 RX DMA,模式选 **`Circular`** (数据宽度 Byte)。优先级建议设为 **`High`**。
|
|||
|
|
|
|||
|
|
* **ATK 雷达** (如 USART3, USART6 - 115200bps):添加 RX DMA,模式选 **`Circular`**。优先级设为 **`Medium`**。
|
|||
|
|
|
|||
|
|
* **NVIC**:必须开启这 4 个串口的**全局中断 (Global Interrupt)**。
|
|||
|
|
|
|||
|
|
### 第二步:链接脚本 (.ld) 配置
|
|||
|
|
|
|||
|
|
打开工程的链接脚本(如 `STM32H743XX_FLASH.ld`),在 `RAM_D2` 区域添加 `.dma_buffer` 段定义:
|
|||
|
|
|
|||
|
|
代码段
|
|||
|
|
/* 给雷达 DMA 专门划分的免 Cache 区域 */
|
|||
|
|
.dma_buffer (NOLOAD) :
|
|||
|
|
{
|
|||
|
|
. = ALIGN(32);
|
|||
|
|
*(.dma_buffer)
|
|||
|
|
*(.dma_buffer*)
|
|||
|
|
. = ALIGN(32);
|
|||
|
|
} >RAM_D2
|
|||
|
|
|
|||
|
|
### 第三步:中断回调接管
|
|||
|
|
|
|||
|
|
确保在 `usart.c` 或 `main.c` 中 **删除或注释掉** 旧的串口接收回调,因为 `laser_manager.c` 底部已经重写了以下函数并接管了整个链路:
|
|||
|
|
|
|||
|
|
* `HAL_UART_RxCpltCallback`
|
|||
|
|
|
|||
|
|
* `HAL_UART_RxHalfCpltCallback`
|
|||
|
|
|
|||
|
|
* `HAL_UARTEx_RxEventCallback` (用于处理 IDLE)
|
|||
|
|
|
|||
|
|
* `HAL_UART_ErrorCallback` (用于自动恢复错误)
|
|||
|
|
|
|||
|
|
* * *
|
|||
|
|
|
|||
|
|
💻 API 使用说明
|
|||
|
|
-----------
|
|||
|
|
|
|||
|
|
### 1. 数据结构概览
|
|||
|
|
|
|||
|
|
C
|
|||
|
|
typedef struct {
|
|||
|
|
uint16_t distance_mm; // 经过中值滤波处理后平滑的距离值 (推荐用于避障控制)
|
|||
|
|
uint16_t raw_distance_mm; // 传感器吐出的原始跳动距离值 (仅供调试观察)
|
|||
|
|
uint8_t valid; // 数据是否有效 (1:有效, 0:无效)
|
|||
|
|
uint8_t online; // 雷达是否在线 (未超时)
|
|||
|
|
uint8_t fault_code; // 故障码 (0x00:正常, 0x80:掉线超时)
|
|||
|
|
uint32_t update_tick_ms; // 最后一次数据刷新的系统 tick
|
|||
|
|
} laser_simple_data_t;
|
|||
|
|
|
|||
|
|
### 2. 初始化与任务启动
|
|||
|
|
|
|||
|
|
在 FreeRTOS 调度器启动前(或统一的应用初始化任务中)调用初始化接口。它会自动配置并开启 DMA,同时创建一个高优先级的解析守护任务。
|
|||
|
|
|
|||
|
|
C
|
|||
|
|
#include "laser/laser_manager.h"
|
|||
|
|
|
|||
|
|
// 在 main.c 或 app_tasks.c 初始化阶段调用
|
|||
|
|
LASER_SIMPLE_Init();
|
|||
|
|
|
|||
|
|
### 3. 数据轮询与读取 (应用层)
|
|||
|
|
|
|||
|
|
在你的应用周期任务中(如 `100ms` 测试任务或控制任务),调用 `Poll` 并获取快照指针:
|
|||
|
|
|
|||
|
|
C
|
|||
|
|
void AppTasks_RunLaserTestTask_Impl(void *argument)
|
|||
|
|
{
|
|||
|
|
const laser_simple_snapshot_t *snap;
|
|||
|
|
uint32_t print_divider = 0;
|
|||
|
|
|
|||
|
|
LASER_SIMPLE_Init();
|
|||
|
|
|
|||
|
|
for(;;)
|
|||
|
|
{
|
|||
|
|
// 1. 触发状态机巡检 (更新在线状态/超时)
|
|||
|
|
LASER_SIMPLE_Poll(HAL_GetTick());
|
|||
|
|
|
|||
|
|
// 2. 获取绝对安全、无撕裂的全局数据快照
|
|||
|
|
snap = LASER_SIMPLE_GetSnapshot();
|
|||
|
|
|
|||
|
|
// 3. 降频打印 (例如每 500ms 打印一次)
|
|||
|
|
if (++print_divider >= 5)
|
|||
|
|
{
|
|||
|
|
print_divider = 0;
|
|||
|
|
printf("F_STP [Filter:%4umm, Raw:%4umm] Valid:%d Fault:%02X\r\n",
|
|||
|
|
snap->ch[LASER_CH_FRONT_STP].distance_mm,
|
|||
|
|
snap->ch[LASER_CH_FRONT_STP].raw_distance_mm,
|
|||
|
|
snap->ch[LASER_CH_FRONT_STP].valid,
|
|||
|
|
snap->ch[LASER_CH_FRONT_STP].fault_code);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
osDelay(100); // 维持高频巡检节拍 (切勿大于雷达设定的 Timeout 时间)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
### 4. 便捷测距接口
|
|||
|
|
|
|||
|
|
如果你的小车只需要知道“前方/后方离障碍物最近是多少”,可以直接调用辅助接口:
|
|||
|
|
|
|||
|
|
C
|
|||
|
|
// 获取前/后两路雷达中,最近且有效的平滑距离值 (毫米)
|
|||
|
|
uint16_t front_min_dist = LASER_SIMPLE_GetFrontNearest();
|
|||
|
|
uint16_t rear_min_dist = LASER_SIMPLE_GetRearNearest();
|
|||
|
|
|
|||
|
|
* * *
|
|||
|
|
|
|||
|
|
❓ 常见问题 (Troubleshooting)
|
|||
|
|
------------------------
|
|||
|
|
|
|||
|
|
**Q: 烧录程序后,USB 虚拟串口无法识别 (或电脑提示无法识别的 USB 设备)?**
|
|||
|
|
|
|||
|
|
> **A:** 请检查 MPU 配置是否生效,以及链接脚本中是否正确加入了 `.dma_buffer`。如果 DMA 数组被编译器分配到了带有 D-Cache 的内存中,USB 外设会因为内存一致性冲突而死机。
|
|||
|
|
|
|||
|
|
**Q: 雷达数据一直是 0,且终端不停输出 FaultCode `0x80`?**
|
|||
|
|
|
|||
|
|
> **A:** `0x80` 代表超时掉线。请检查:
|
|||
|
|
>
|
|||
|
|
> 1. 雷达硬件连线(RX/TX 是否接反)。
|
|||
|
|
>
|
|||
|
|
> 2. `CubeMX` 中 4 个串口是否正确开启了**全局中断**(未开启中断将无法唤醒解析任务)。
|
|||
|
|
>
|
|||
|
|
> 3. 波特率是否匹配(STP: `230400`,ATK: `115200`)。
|
|||
|
|
|
|||
|
|
**Q: 想要调整中值滤波的平滑程度?**
|
|||
|
|
|
|||
|
|
> **A:** 在 `laser_manager.c` 顶部修改 `#define FILTER_WIN_SZ 3U`。改为 `5U` 会更平滑,但会增加几毫秒的系统响应延迟。通常 `3` 是最佳平衡点。
|