Files
origincar_controller/doc/uart_protocol_v2_timestamp.md
cyy_mac 93ca37e36c 完善阿克曼控制与高速串口遥测
- 校正舵机中位、转向符号和阿克曼后轮差速模型\n- 增加航向角速度辅助及遥控通道调试开关\n- 将速度环提升至 200Hz,并按实际 dt 计算 PI 积分\n- 将 IMU 启动校准缩短为 2 秒\n- 为 USART3 增加 DMA 发送和 MCU 采样时间戳
2026-08-12 18:51:48 +08:00

112 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 下位机 UART 发送协议 v2带采样时间戳
本文件描述 STM32 下位机通过 **USART3ROS 口,波特率 921600** 周期发送给上位机的
传感器数据帧格式。v2 在原 24 字节帧的基础上,于**帧尾追加**了会话 ID 与两个采样
时间戳字段,用于上位机做 IMU / 轮速 / 雷达的时间对齐。
- 对应源码:[HARDWARE/usartx.c](../HARDWARE/usartx.c)、[HARDWARE/usartx.h](../HARDWARE/usartx.h)
- 时间源实现:[HARDWARE/timer.c](../HARDWARE/timer.c) 的 `TIM7_Init()` / `mcu_time_us()`
- 设计背景与总体原则见 [时间戳.md](时间戳.md)
## 1. 帧总览
| 项目 | 值 |
| ---- | ---- |
| 帧头 `FRAME_HEADER` | `0x7B` |
| 帧尾 `FRAME_TAIL` | `0x7D` |
| 帧长 `SEND_DATA_SIZE` | **44 字节**v1 为 24 字节) |
| 发送周期 | 200 Hz`data_task`USART3 非阻塞 DMA 发送) |
| 字节序 | 多字节字段一律 **大端**(高字节在前) |
| 校验 | 单字节 XOR覆盖 byte 0..41 |
设计约束byte 0..21 与 v1 完全一致,新增字段全部放在校验位之前的尾部,
使只读取旧字段的解析器仍然兼容。
## 2. 字节布局
| 偏移 | 长度 | 字段 | 类型 | 说明 |
| ---- | ---- | ---- | ---- | ---- |
| 0 | 1 | Frame_Header | u8 | 帧头 `0x7B` |
| 1 | 1 | Flag_Stop | u8 | 软件失能标志1=停车) |
| 2 | 2 | X_speed | int16 | X 轴线速度,单位 mm/s大端 |
| 4 | 2 | Y_speed | int16 | Y 轴线速度,单位 mm/s大端 |
| 6 | 2 | Z_speed | int16 | Z 轴角速度,单位 mm/s等效大端 |
| 8 | 2 | Accel_X | int16 | 加速度计 XROS 坐标),大端 |
| 10 | 2 | Accel_Y | int16 | 加速度计 YROS 坐标),大端 |
| 12 | 2 | Accel_Z | int16 | 加速度计 ZROS 坐标),大端 |
| 14 | 2 | Gyro_X | int16 | 陀螺仪 XROS 坐标),大端 |
| 16 | 2 | Gyro_Y | int16 | 陀螺仪 YROS 坐标),大端 |
| 18 | 2 | Gyro_Z | int16 | 陀螺仪 ZROS 坐标),大端 |
| 20 | 2 | Power_Voltage | int16 | 电池电压×1000大端 |
| **22** | **4** | **session_id** | **uint32** | 本次 MCU 启动会话 ID大端 |
| **26** | **8** | **speed_sample_time_us** | **uint64** | 编码器采样时刻μs大端 |
| **34** | **8** | **imu_sample_time_us** | **uint64** | IMU 采样时刻μs大端 |
| 42 | 1 | Checksum | u8 | byte 0..41 的 XOR |
| 43 | 1 | Frame_Tail | u8 | 帧尾 `0x7D` |
加粗行为 v2 相对 v1 的新增字段。
## 3. 时间源
时间戳由自由运行的 **TIM7** 提供,见 [HARDWARE/timer.c](../HARDWARE/timer.c)
- TIM7 挂在 APB1定时器时钟 84 MHz预分频 84 → **1 MHz 计数1 tick = 1 μs**
- 16 位计数器每 65.536 ms 回绕一次,更新中断把高 32 位软件计数 `g_time_high` 加一。
- `mcu_time_us()` 返回 `(g_time_high << 16) | CNT`,即**自上电以来的单调递增微秒数**
可用位宽 48 位(约 8.9 年不回绕)。
- TIM2~TIM5 被四路编码器占用、TIM8 被航模遥控输入捕获占用,故选空闲的 TIM7。
- TIM7 更新中断优先级设为 5数值大于 `configMAX_SYSCALL_INTERRUPT_PRIORITY`
对应的抢占门限ISR 内只做自增,不调用任何 FreeRTOS API。
> 注意:`mcu_time_us()` 只增不减,绝不因校时而跳变。上位机负责维护
> `T_host = a·t_mcu + b` 的时钟映射(见 [时间戳.md](时间戳.md) 第 7、8 节)。
## 4. 两个时间戳分别打在哪里
v1 只有"发送时刻"隐含语义,无法表达 IMU100 Hz与轮速200 Hz是**异步采集**的。
v2 在各自真实采样点打点,写入全局变量,`data_transition()` 打包时快照:
| 时间戳 | 打点位置 | 采样率 |
| ---- | ---- | ---- |
| `speed_sample_time_us` | [balance.c](../BALANCE/balance.c) `Get_Velocity_Form_Encoder()` 读编码器锁存计数器的瞬间 | 200 Hz |
| `imu_sample_time_us` | [MPU6050.c](../HARDWARE/MPU6050/MPU6050.c) `MPU6050_task` I2C 读取前 | 100 Hz |
因此一帧内同时带两个时间戳:上位机应把加速度/角速度对齐到 `imu_sample_time_us`
把 X/Y/Z 速度对齐到 `speed_sample_time_us`,而不是共用一个发送时刻。
> IMU 打点在 I2C 突发读取之前,是在没有 DRDY数据就绪中断条件下能取到的、
> 最接近物理采样的时刻。若日后接入 MPU 的 DRDY 引脚中断,可把打点移到中断里进一步减小抖动。
## 5. session_id
- 定义在 [usartx.c](../HARDWARE/usartx.c)`g_session_id`
- 当前实现为**固定值 `0xA5A5A5A5`**。
- 用途:上位机据此判断下位机是否重启过(重启后 `mcu_time_us()` 从 0 重新计时,
时钟映射 `a, b` 必须重新拟合)。
- 待办:如需真正区分每次上电,应在启动时用熵源(如未初始化 RAM、启动 tick 等)
生成一次性随机值。当前固定值只能表明"协议版本一致",不能区分不同启动会话。
## 6. 校验与解析建议
- 校验位 `buffer[42] = XOR(buffer[0..41])`,与 v1 的计算方式相同,只是范围从 22 扩到 42。
- 上位机解析步骤:
1. 同步帧头 `0x7B`
2. 读满 44 字节;
3. 校验 `buffer[43] == 0x7D``XOR(buffer[0..41]) == buffer[42]`
4. 按上表大端解出各字段。
- 多字节字段务必按大端手动组装,不要直接 `memcpy` 结构体(编译器填充/对齐/端序差异)。
## 7. 其他发送通道
| 通道 | 现状 |
| ---- | ---- |
| USART3ROS 主口DMA | 已用 `SEND_DATA_SIZE`,自动发送完整 44 字节 |
| USART1 / USART5 | 已改用 `SEND_DATA_SIZE`,发送完整 44 字节 |
| CAN | 当前未被调用;`CAN_SEND()` 仍按 8×3=24 字节分包,**不含时间戳字段**。如需经 CAN 传时间戳需另行扩展分包 |
## 8. 上位机适配提醒
USART3 收到的帧已从 24 字节变为 **44 字节**。上位机ROS 端)解析代码必须同步更新:
帧长、校验范围、以及尾部三个新字段的读取。否则会因帧长不符导致解析错位。