# Path_Following **Repository Path**: xiaopengyouGU/Path_Following ## Basic Information - **Project Name**: Path_Following - **Description**: 这是一个简单的开源智能小车循迹项目,采取分层与模块化的设计思想实现。功能包括增量式PID、步进闭环定位、BootLoader(LTM)、IAP在线升级、循迹与差速控制等。本项目与LTM-Monitor上位机配套使用。 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-10 - **Last Updated**: 2026-11-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # STM32G474 智能小车固件 面向 STM32G474RBT3 的智能小车固件,包含 12 路灰度循迹、双轮差速控制、两轴步进云台、BNO08x 航向采集、MaxiCam 视觉打卡、定点里程计、上位机通信,以及带 A/B 分区和失败回滚的 UART IAP。 ## 1. 系统概览 | 项目 | 配置 | | --- | --- | | MCU | STM32G474RBT3,Cortex-M4F,128 KB Flash / 96 KB SRAM | | 系统时钟 | 24 MHz HSE → PLL → 170 MHz | | 实时控制 | TIM6,1 kHz,NVIC 优先级 0 | | 任务计时 | SysTick,1 ms,NVIC 优先级 15 | | 上位机链路 | USART1,115200 bps,LTM 帧协议 | | 云台反馈 | 两路 AS5600,12 bit | | 车体航向 | BNO08x UART-RVC | | 固件升级 | USART1 → LTM → UDS PDU,BootLoader 支持 A/B 分区 | 应用主要完成以下工作: - 采集 12 路灰度传感器,计算横向偏差并执行循迹控制; - 通过编码器测速,对左右轮分别执行速度闭环; - 使用 AS5600 反馈控制两轴步进云台; - 接收上位机运动命令,周期上报运行状态和曲线数据; - 通过独立 BootLoader 在 A/B 分区之间切换固件。 ## 2. 软件架构 ```mermaid flowchart TB HOST["上位机 / IAP 客户端"] subgraph APP["Application"] MAIN["car/main
初始化、命令分发、周期上报"] CAR["car
业务入口、参数检查、单位换算"] LTM["components/protocol
LTM 帧编解码"] CTRL["tasks/control_tasks
控制循环、状态与故障处理"] SCHED["tasks/sched_task
毫秒任务调度"] ALGO["components/algo
PLL 测速、PID、轨迹规划"] ODOM["components/algo/odometry
Q12 位姿、Q15 航向"] MISSION["tasks/mission
打卡与返航状态机"] DEV["devices
车轮、灰度、云台、IMU、通信"] VISION["devices/vision
MaxiCam USART3 DMA"] BSP["drivers/BSP
定时器、PWM、编码器、UART、DMA、GPIO、I2C"] HAL["STM32G474 HAL / CMSIS"] end HOST <-->|USART1 / LTM| MAIN MAIN --> CAR MAIN <--> LTM MAIN --> DEV CAR --> CTRL CAR --> SCHED CAR --> MISSION CTRL --> ALGO MISSION --> CTRL MISSION --> ODOM MISSION --> VISION CTRL --> DEV SCHED --> DEV DEV --> BSP BSP --> HAL TIM["TIM6 中断
1 kHz"] --> CTRL TICK["SysTick 中断
1 ms"] --> SCHED ``` 各层职责如下: | 目录 | 职责 | | --- | --- | | `src/app/car/` | 应用入口和业务门面。向上位机暴露 `car_set_speed()`、`car_set_angle()`、`car_start()` 等接口,并完成物理单位与内部控制量之间的转换。 | | `src/app/tasks/` | 实时控制、任务调度和状态机。`control_tasks.c` 是底盘与云台控制的核心。 | | `src/app/components/algo/` | 可复用算法,包括 PLL 测速、定点 PID 和步进轨迹规划。控制热路径主要使用 Q15/Q24 定点运算。 | | `src/app/components/algo/odometry/` | 定点轮式里程计。位置使用 Q12,航向使用标准 Q15,并在 IMU 无效时使用轮速差降级。 | | `src/app/devices/` | 设备语义层。对任务层提供轮速、灰度偏差、云台位置和 IMU 状态,不直接暴露 HAL 寄存器。 | | `src/app/devices/vision/` | MaxiCam 串口帧解析和测量快照,不包含运动决策。 | | `src/app/drivers/` | 板级资源与中断处理,包括 BSP 统一初始化、A/B 槽提交、TIM1 PWM、TIM2/TIM3 编码器、TIM4/TIM17 步进脉冲、USART、DMA、GPIO 和软件 I2C。 | | `src/app/common/` | 公共类型、控制参数和固定点换算宏。 | | `src/app/tasks/mission/` | 打卡与返航任务状态机,50 Hz 运行,通过现有控制接口驱动底盘。 | | `src/boot/` | 独立 BootLoader 工程,包括 UDS 服务、LTM 传输、Flash 端口、主机升级工具和主机侧测试。 | | `third_party/` | STM32G474 HAL、CMSIS 和启动文件。 | 依赖方向保持单向: ```text main / car -> tasks / protocol -> components / devices -> BSP -> STM32 HAL ``` 控制策略集中在 `tasks` 和 `components/algo`,硬件差异收敛在 `drivers/BSP`。移植到同类平台时,原则上只需要替换 BSP、启动文件和链接脚本。 ## 3. 运行时结构 系统没有引入 RTOS,实时性和低速设备访问分开处理。 | 执行上下文 | 周期 | 主要工作 | | --- | ---: | --- | | TIM6 中断,优先级 0 | 1 kHz | 执行 `control_tasks_run()` | | 循迹外环 | 200 Hz | 灰度采样、偏差解算、PD、左右轮目标更新 | | 轮速内环 | 1 kHz | 编码器快照、PLL 测速、PI、PWM 占空比更新 | | 云台前馈 | 1 kHz | 轨迹规划器推进、速度前馈叠加位置 P 修正 | | 状态监控 | 1 kHz | 停机判定、角度快照、步进故障检查 | | SysTick 中断,优先级 15 | 1 ms | 任务表倒计时 | | 主循环任务 | 100 Hz | `dev_step_poll()` 读取 AS5600,`control_tasks_axis_poll()` 更新位置 P | | 视觉解析 | 50 Hz | `dev_vision_poll()` 消费 USART3 DMA 数据 | | 任务规划 | 50 Hz | `car_poll()` 节拍调用 `mission_task()`,并向应用层返回 `car_event_t` | | 主循环 | 条件触发 | LTM 收包、命令执行、状态文本和 10 路曲线数据上报 | USART 接收中断优先级为 4。BNO08x 使用 USART2 DMA 接收 19 字节定长帧,并通过 IDLE 中断收尾;超过 200 ms 没有有效帧时,IMU 数据标记为失效。 两路 AS5600 通过软件 I2C 读取,因此放在主循环的 100 Hz 任务中执行。1 kHz 中断只使用最近一次已完成的位置快照,避免软件 I2C 阻塞实时控制。 ## 4. 控制链路 ### 4.1 循迹与底盘 ```text 12 路灰度掩码 -> 检查命中位连续性 -> 计算横向偏差 -> 中心死区 -> atan 几何角度 -> PD 差速量 -> 左右轮目标转速 -> 200 Hz 加速度限幅 -> 1 kHz 轮速 PI -> 8 kHz PWM ``` 灰度模块只接受连续点亮段。全亮、全灭和断点数据会被判为无效。丢线后,系统保持最后一次有效偏差,并根据丢线前的转向方向执行原地找线。 左右轮使用 TIM2/TIM3 正交编码器。编码器计数经 PLL 得到 `count/s`,再转换为 Q24 圈/秒;两侧速度 PI 输出 Q15 占空比,由 TIM1 的两路 8 kHz PWM 驱动。 `car_set_mode(mode, arg)` 是底盘统一命令入口: - `Mode_Open_Loop`:直接设置左右轮占空比; - `Mode_Speed`:设置左右轮目标转速; - `Mode_Line_Following`:取消任务并进入普通循迹; - `Mode_Mission`:启动单圈打卡,`arg` 为打卡数量,范围 1..255; - `Mode_Mission_Fail`:中止打卡,转入失败返航; - `Mode_Uturn`:按 BNO 航向闭环原地掉头; - `Mode_Stop`:停机。 底层控制任务只接收开环、速度和循迹等基础模式;任务、返航和 U-turn 由 car 层分派。 ### 4.2 状态机 ```mermaid stateDiagram-v2 [*] --> Init Init --> Idle: Init_Done Idle --> Running: Start Running --> Stop: 两轮低于停机阈值 Stop --> Running: Start Idle --> Error: Fault Running --> Error: Fault Stop --> Error: Fault Error --> Idle: Fault_Clear ``` 停机不是立即把状态设为 `Stop`。发送 Stop 后,系统先关闭 PWM、清零控制和 PID 状态,等两轮速度都低于 1 RPM,再由监控任务把状态切换到 `Stop`。 步进驱动器进入错误状态后,App 会锁存首个错误并停止云台。当前主机命令中没有开放 `Fault_Clear`。 ### 4.3 两轴云台 ```text 目标角度 -> 12 bit 编码器计数 -> 单圈最短路径 -> 在线轨迹规划 -> 速度前馈 -> 100 Hz 位置 P 修正 -> 双向 pps -> TIM4 / TIM17 脉冲输出 ``` 云台位置以 AS5600 单圈计数为基准,跨圈位置通过相邻采样的最小差值展开。轨迹规划器按 1 ms 推进,当前配置为梯形加减速,最大速度 100 RPM,加减速时间 200 ms。 位置 P 使用 4 count 死区,避免编码器静止抖动导致持续修正。首次上电时,规划器必须等 AS5600 产生有效样本后才以真实位置启动,避免从 0 位置拉起云台。 ### 4.4 打卡任务与定点里程计 规划层通过 CMake 选项 `APP_MISSION_ENABLED` 控制,默认开启;需要临时旁路时传入 `-DAPP_MISSION_ENABLED=OFF`。 打卡任务运行在独立的 `mission_task()` 中,不改变原循迹环和速度环: ```text 循迹 -> 目标达到距离阈值 -> 减速 -> 车体按中心 X 修正,丢失后相对 ±90° 搜索 -> 对准 -> 低速插入 -> 停车 2 s -> 后退 -> 重新捕获灰线 ``` 上位机通过 `F=` 下发本次任务的打卡总数,同时将下发瞬间定义为任务坐标原点。打卡任务只执行一圈;全部打卡成功后自动返航,未完成指定数量也会在回到原点附近后停机。云台在任务中只保持使能,不参与搜索或对准。 `KFAIL` 会中止任务。失败流程先发送 `check failed!!!`,停车并转向返程方向,然后恢复正常循迹返航;如果短暂丢线,由循迹控制器处理。卡尔曼 X/Y 用于回到起点附近,距任务原点 60 mm 内停机并发送 `return home`。 里程计全部使用定点数: - 位置和累计路程使用 Q12,单位为米; - BNO08x 车体航向转换为 Q15 整圆角; - 轮径 65 mm,轮距 105 mm,编码器 56000 count/rev; - 航向统一使用 `-180°..180° ↔ -32768..32767`,所有相对角运算后都做整圆归一化; - 不再保存二维航迹表,任务层只使用实时 X/Y 与最后一次在线位置。 当前版本使用 BNO yaw 作为航向观测、编码器差速作为无 IMU 时的降级估计,没有引入完整 EKF。对于 1.5 m × 2 m 场地,这种方式运算量更低,也便于直接离线检查。后续如果需要输出位置置信度,可以在现有 `lt_odometry` 接口后增加二维 EKF,不影响任务层。 `mission_param_def.h` 中的视觉像素阈值、插入距离和后退距离需要在实车上标定。方向安装不一致时,优先调整 `MISSION_YAW_SIGN` 和 `VISION_X_SIGN`,不要在任务状态机里临时取反。 打卡任务中的 yaw 云台只使能驱动器、停止脉冲和位置规划器,不主动摆动。目标搜索由车体完成;如果实际接线中 yaw/pitch 轴编号互换,通过 `STEP_AXIS_YAW` 统一修正。 ## 5. 上位机通信 LTM 数据帧格式如下: | 字段 | 长度 | 说明 | | --- | ---: | --- | | Header | 2 B | `0xA5B9`,小端发送为 `B9 A5` | | Data type | 1 B | 数据类型 | | Length | 1 B | 负载长度,最大 128 B | | Payload | N B | 业务数据 | | CRC | 2 B | Modbus CRC16 | 文本命令: | 命令 | 作用 | | --- | --- | | `V=,` | 进入速度模式并设置左右轮目标,单位 RPM | | `O=,` | 进入开环模式并设置左右轮占空比,单位 % | | `Y=` | 设置云台 yaw 目标 | | `P=` | 设置云台 pitch 目标 | | `F` | 取消任务规划,进入持续普通循迹 | | `F=` | 配置一圈 n 点打卡任务(n=1..255),位置作为本次任务原点 | | `KFAIL` | 中止打卡并返航 | | `S` | 请求停机 | | `I` | 输出 IMU 诊断信息 | | `H` 或 `?` | 输出命令帮助 | 二进制定义包括 Start、Stop 和 Reset。`V`、`O`、`F` 只修改模式和目标值,不代替 Start。模式切换仅在 `Idle` 或 `Stop` 状态有效,轮速目标仅在 `Running` 或 `Stop` 状态有效;运行中切换模式需要先停机。 周期曲线帧使用 `Data_Channel_ALL`,负载为 10 个单精度浮点数: ```text [Kalman X(m), Kalman Y(m), 左轮转速, 右轮转速, 循迹偏差, MaixCAM 中心 X(px), MaixCAM 中心 Y(px), yaw, pitch, 车体 yaw] ``` 运行状态会以文本周期输出,包括状态、模式、错误码和系统 tick,便于在不接入上位机绘图工具时直接排查问题。 MaxiCam 使用 `B9 A5 | 08 | 12 | 18-byte payload | CRC16 low/high` 二进制帧。载荷依次为: ```text sub_id, seq, status, flags, center_x, center_y, major, distance_mm, quality, station_id, aligned_frames ``` 多字节字段均为小端,`center_x/center_y` 单位为 0.1 px。CRC 使用 Modbus CRC16,覆盖帧头和载荷。解析结果保留最近一帧,超过 150 ms 没有新帧即判为失效。`C` 命令可查看帧数、解析错误、CRC 错误和 DMA 环形缓冲溢出情况。 打卡成功、全部完成、错误和返回原点由 `car_poll()` 翻译成 `car_event_t`。应用层只根据 car 事件输出文本,不占用 `Data_User_Defined` 或 IAP 自定义通道: ```text checkpoint success: / check failed!!! return home return home, checked all!!! ``` ## 6. BootLoader 与 A/B 升级 ### 6.1 Flash 分区 | 区域 | 起始地址 | 大小 | | --- | --- | ---: | | BootLoader | `0x08000000` | 20 KB | | Boot 元数据 | `0x08005000` | 2 KB | | App Slot A | `0x08005800` | 42 KB | | App Slot B | `0x08010000` | 42 KB | App 和 BootLoader 共用同一份分区定义。SRAM 顶部 4 字节 `0x20017FFC` 用作软跳转标记,初始栈顶固定在 `0x20017FF8`,满足 8 字节栈对齐要求。 ### 6.2 升级流程 当前固件启用 UART IAP,CAN-FD/ISO-TP 代码已保留,但 `BL_ENABLE_UDS` 为 0。UART 通道将 UDS PDU 封装在 LTM 的 `Data_User_Defined` 帧中。 ```text Reset -> BootLoader -> 6 秒升级窗口 -> 0x10 进入编程会话 -> 0x27 安全访问 -> 0x34 请求下载 -> 0x36 分块传输 -> 0x37 回读 CRC 并标记 PENDING -> 0x11 复位 -> 启动新固件 -> bsp_hal_init() 内部提交当前槽为 VALID ``` 下载目标始终是当前活动分区的另一槽,已确认的 App 不被覆盖。Boot 元数据记录 `active`、`boot`、两槽状态、镜像长度和 CRC。 新镜像以 `PENDING` 状态启动。App 运行到 `bsp_hal_init()` 后提交当前槽为 `VALID`。如果新镜像连续 3 次未能提交,BootLoader 会清除该槽的待验证状态并回到原活动分区。 App 收到 Reset 命令后重新进入 BootLoader。若 6 秒内没有升级命令且已有有效 App,BootLoader 会自动跳转。 主机升级工具位于 `src/boot/host/upgrade_client.py`,BootLoader 的主机侧功能测试和压力测试位于 `src/boot/host/test/`。 ## 7. 构建与烧录 构建依赖: - CMake 3.20 或更高版本; - `arm-none-eabi-gcc` 工具链; - MinGW Makefiles 生成器; - Python 3; - J-Link(仅烧录时需要)。 常用命令: ```bash python script.py -b python script.py -b --boot python script.py -R --app python script.py -R --boot python script.py -j --boot python script.py -c ``` CMake 缓存统一放在根目录 `build/`: ```text build/ ├── app/ # 主应用 └── boot/ # BootLoader ``` 构建产物: ```text bin/path_following_A.hex bin/path_following_B.hex build/app/path_following_g474_A.elf build/app/path_following_g474_B.elf build/boot/bin/bootloader.hex ``` 首次烧录应先写 BootLoader,再写 App A。`python script.py -R --app` 会清除 A/B 元数据,避免旧 CRC 记录阻止 J-Link 直接烧录的开发镜像。 J-Link 路径可通过 `JLINK_PATH` 环境变量指定,也可以传给脚本的 `--jlink` 参数。 ## 8. 硬件资源 | 功能 | 引脚 / 外设 | | --- | --- | | 左轮编码器 | PA0/PA1,TIM2_CH1/CH2 | | 右轮编码器 | PA6/PA7,TIM3_CH1/CH2 | | 左轮 PWM / 方向 | PA8 TIM1_CH1;PB12/PB13 | | 右轮 PWM / 方向 | PA11 TIM1_CH4;PB14/PB15 | | 云台轴 1 脉冲 / DIR / EN | PB6 TIM4_CH1;PC0 / PC1 | | 云台轴 2 脉冲 / DIR / EN | PB9 TIM17_CH1;PC2 / PC3 | | AS5600 A | PC4/PC5,软件 I2C | | AS5600 B | PC6/PC7,软件 I2C | | 上位机 / IAP | USART1,PA9/PA10,115200 bps | | BNO08x | USART2,PB3/PB4 | | MaxiCam | USART3,PB10/PB11,115200 bps | | 运行 / 错误 LED | PC11 / PC12 | 12 路灰度的完整引脚表见 [`docs/pins_g474.md`](docs/pins_g474.md)。 软件中 `STEP_AXIS_YAW` 对应下层步进轴 0(TIM4/PB6),`STEP_AXIS_PITCH` 对应上层步进轴 1(TIM17/PB9);遥测和 `Y=`/`P=` 指令使用同一组轴常量。 实车线束中步进与 AS5600 的编号是交叉的:步进轴 0 对应 AS5600 通道 1,步进轴 1 对应 AS5600 通道 0。`dev_step.c` 的轴配置已按实物接线映射,不能用两个相同的数组下标直接对应。 ## 9. 调试与标定 `I` 命令可查看 IMU 有效标志、yaw、接收长度、帧头匹配和校验统计。 以下参数与具体车体强相关,换板或调整机械结构后需要重新标定: - 左右编码器方向; - 左右电机方向; - 两路 AS5600 安装方向; - 云台机械限位; - 灰度传感器间距和中心位置; - 轮速 PI、循迹 PD 和云台位置 P 参数。 当前云台限位仍是占位配置 `0..4095`,正式使用前必须根据实际机械行程修改。