# 串口通信协议 ## 概述 - **物理层**: UART2,引脚 TX=B15 / RX=B16 - **波特率**: 115200 bps - **数据位**: 8,无校验,1 停止位(默认 UART 配置) - **字节序**: 多字节数值均采用**小端序 (little-endian)** - **校验方式**: 累加和校验(checksum = 帧内校验位之前所有字节之和,截断为 uint8_t) - **软件 FIFO**: 16 字节环形缓冲区,UART RX 中断中写入,主循环 `command_task()` 中解析 ## 工作模式 系统有三种工作模式,通过命令帧切换: | 模式 | 枚举值 | 说明 | 使能的控制环 | |---|---|---|---| | 自动模式 | `COMMAND_MODE_AUTO` (0) | 视觉追踪 / 全闭环 | 电机速度环 + 角度环 + 位置环 | | 手动模式 | `COMMAND_MODE_MANUAL` (1) | 手动遥控 | 电机速度环 + 角度环 | | 调试模式 | `COMMAND_MODE_DEBUG` (2) | PID 参数整定 | 无(所有控制环关闭) | --- ## 数据帧定义 ### 1. 命令帧 `0xA7`(3 字节) 切换工作模式。 ``` ┌────────┬───────┬──────────┐ │ SOF │ cmd │ checksum │ │ 0xA7 │ 1 B │ 1 B │ └────────┴───────┴──────────┘ ``` | 偏移 | 长度 | 字段 | 类型 | 说明 | |---|---|---|---|---| | 0 | 1 | SOF | uint8_t | 帧头,固定值 `0xA7` | | 1 | 1 | cmd | uint8_t | 命令码 | | 2 | 1 | checksum | uint8_t | `(0xA7 + cmd) & 0xFF` | **cmd 取值:** | 值 | 功能 | 对应函数 | |---|---|---| | `0x01` | 切换到自动模式 | `SwitchToAutoMode()` | | `0x02` | 切换到手动模式 | `SwitchToManualMode()` | | `0x03` | 切换到调试模式 | `SwitchToDebugMode()` | **示例(切换手动模式):** `A7 02 A9` --- ### 2. 手动控制帧 `0xA6`(8 字节) 仅在**手动模式**下生效,用于实时控制底盘运动。 ``` ┌────────┬────────────────────┬─────────────────────┬──────────┐ │ SOF │ motor_speed_base │ angle_velocity │ checksum │ │ 0xA6 │ int16_t (2 B) │ float (4 B) │ 1 B │ └────────┴────────────────────┴─────────────────────┴──────────┘ ``` | 偏移 | 长度 | 字段 | 类型 | 说明 | |---|---|---|---|---| | 0 | 1 | SOF | uint8_t | 帧头,固定值 `0xA6` | | 1 | 2 | motor_speed_base | int16_t (LE) | 基础直行速度,范围 -50~+50 | | 3 | 4 | angle_velocity | float (LE) | 角度速率,单位 °/s | | 7 | 1 | checksum | uint8_t | `(0xA6 + 前 6 字节之和) & 0xFF` | **控制算法:** ``` motor_speed_left_ref = motor_speed_base - speed_dif motor_speed_right_ref = motor_speed_base + speed_dif ``` 其中 `speed_dif` 由角度环 PID 根据 `yaw_ref` 与当前 `imu_angle.yaw` 计算得出。手动模式下 `yaw_ref` 以 `angle_velocity` 速率持续累加: ``` yaw_ref += angle_velocity * dt (dt = 0.02s) ``` --- ### 3. 调试调参帧 `0xA8`(38 字节) 仅在**调试模式**下生效,一次性更新所有 PID 参数(Kp / Ki / Kd)。 ``` ┌────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐ │ SOF │ Kp_speed │ Ki_speed │ Kd_speed │ Kp_yaw │ Ki_yaw │ Kd_yaw │ Kp_pos │ Ki_pos │ Kd_pos │ checksum │ │ 0xA8 │ float(4) │ float(4) │ float(4) │ float(4) │ float(4) │ float(4) │ float(4) │ float(4) │ float(4) │ 1 B │ └────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘ ``` | 偏移 | 长度 | 字段 | 类型 | 说明 | |---|---|---|---|---| | 0 | 1 | SOF | uint8_t | 帧头,固定值 `0xA8` | | 1 | 4 | Kp_speed | float (LE) | 电机速度环比例系数(左右共用) | | 5 | 4 | Ki_speed | float (LE) | 电机速度环积分系数 | | 9 | 4 | Kd_speed | float (LE) | 电机速度环微分系数 | | 13 | 4 | Kp_yaw | float (LE) | 角度环比例系数 | | 17 | 4 | Ki_yaw | float (LE) | 角度环积分系数 | | 21 | 4 | Kd_yaw | float (LE) | 角度环微分系数 | | 25 | 4 | Kp_position | float (LE) | 位置环比例系数 | | 29 | 4 | Ki_position | float (LE) | 位置环积分系数 | | 33 | 4 | Kd_position | float (LE) | 位置环微分系数 | | 37 | 1 | checksum | uint8_t | `(0xA8 + 前 36 字节之和) & 0xFF` | **各环默认参数:** | 控制环 | Kp | Ki | Kd | 输出限幅 | 采样周期 | 优化环节 | |---|---|---|---|---|---|---| | 电机速度环(左/右) | 300.0 | 150.0 | 0.0 | ±3000 (MOTOR_MAX) | 10ms | — | | 角度环 | 1.0 | 0.0 | 0.0 | ±25 (°/s) | 20ms | 偏航角误差修正 | | 位置环 | 0.5 | 0.0 | 0.0 | ±90 (°/s) | 20ms | — | > **注意**:调试模式下所有控制任务已关闭,电机停转。修改参数后需切换到自动或手动模式才能观察效果。修改的参数直接写入 PID 实例的 Kp/Ki/Kd 字段,不清除积分等运行时状态。 --- ### 4. 角度速率前馈帧 `0xA9`(6 字节)— MCU → PC 由 MCU 经 DMA 主动发送,上报位置环计算的角度速率,供云台等设备做前馈控制。 ``` ┌────────┬─────────────────────┬──────────┐ │ SOF │ angle_velocity │ checksum │ │ 0xA9 │ float (4 B) │ 1 B │ └────────┴─────────────────────┴──────────┘ ``` | 偏移 | 长度 | 字段 | 类型 | 说明 | |---|---|---|---|---| | 0 | 1 | SOF | uint8_t | 帧头,固定值 `0xA9` | | 1 | 4 | angle_velocity | float (LE) | 角度速率,单位 °/s | | 5 | 1 | checksum | uint8_t | `(0xA9 + 前 4 字节之和) & 0xFF` | **发送触发条件**:位置环控制任务使能时,每 20ms(50Hz)在 `position_control_task()` 中调用 `command_send_angle_velocity()`。 --- ## 通信流程 ``` ┌──────────┐ ┌──────────┐ │ PC │ ──── 0xA7 cmd ────────→ │ MSPM0 │ │ (上位机) │ ←─── 0xA9 feedback ──── │ (MCU) │ │ │ ──── 0xA6 manual ─────→ │ │ │ │ ──── 0xA8 debug ──────→ │ │ └──────────┘ └──────────┘ ``` **典型操作流程:** 1. **上电默认**:自动模式,位置环+角度环+速度环全部使能 2. **切换到手动模式**:发送 `A7 02 A9`,然后周期发送 `0xA6` 控制帧驱动底盘 3. **PID 调参**:发送 `A7 03 AA` 进入调试模式(电机停转),发送 `0xA8` 调参帧更新参数,再发 `A7 01 A8` 或 `A7 02 A9` 切换回运行模式观察效果 ## 模式切换副作用 每次切换模式时,系统会执行以下操作: | 操作 | 自动模式 | 手动模式 | 调试模式 | |---|---|---|---| | 设置 `current_mode` | AUTO (0) | MANUAL (1) | DEBUG (2) | | 清除 PID 运行时状态 | 全部 4 个 PID | 速度环+角度环 | — | | `yaw_ref` | = 当前 `imu_angle.yaw` | = 当前 `imu_angle.yaw` | — | | `angle_velocity` | 归零 | 归零 | — | | `motor_speed_ref` | 归零 | 归零 | — | | `motor_speed_base` | = 10 (AUTO_MODE_BASE_SPEED) | 归零 | — | | 电机 PWM | 归零 | 归零 | 归零 | | 速度环使能 | ✅ | ✅ | ❌ | | 角度环使能 | ✅ | ✅ | ❌ | | 位置环使能 | ✅ | ❌ | ❌ | | 蜂鸣器 | 100ms | 200ms | 300ms | ## C 语言帧构造示例 ```c #include #include // --- 构造 0xA7 命令帧 --- void build_cmd_frame(uint8_t *buf, uint8_t cmd) { buf[0] = 0xA7; buf[1] = cmd; buf[2] = (uint8_t)(0xA7 + cmd); // 发送 buf, 长度 3 } // --- 构造 0xA6 手动控制帧 --- void build_manual_frame(uint8_t *buf, int16_t speed, float yaw_rate) { buf[0] = 0xA6; memcpy(&buf[1], &speed, 2); // int16_t, 小端 memcpy(&buf[3], &yaw_rate, 4); // float, 小端 uint8_t sum = 0xA6; for (int i = 1; i < 7; i++) sum += buf[i]; buf[7] = sum; // 发送 buf, 长度 8 } // --- 构造 0xA8 调试调参帧 --- void build_debug_frame(uint8_t *buf, float kp_s, float ki_s, float kd_s, float kp_y, float ki_y, float kd_y, float kp_p, float ki_p, float kd_p) { buf[0] = 0xA8; memcpy(&buf[1], &kp_s, 4); memcpy(&buf[5], &ki_s, 4); memcpy(&buf[9], &kd_s, 4); memcpy(&buf[13], &kp_y, 4); memcpy(&buf[17], &ki_y, 4); memcpy(&buf[21], &kd_y, 4); memcpy(&buf[25], &kp_p, 4); memcpy(&buf[29], &ki_p, 4); memcpy(&buf[33], &kd_p, 4); uint8_t sum = 0xA8; for (int i = 1; i < 37; i++) sum += buf[i]; buf[37] = sum; // 发送 buf, 长度 38 } ```