8.2 KiB
8.2 KiB
EDC 通讯协议
概述
本协议用于 MPU 与 MCU (ESP32-C5) 之间的数据交互,支持 I2C 与 UART 双通道冗余传输。
| 属性 | 值 |
|---|---|
| 帧头 | 0xAA 0x55 |
| 字节序 | 大端 (Big-Endian) |
| CRC | CRC-16/MODBUS (多项式 0x8005) |
| I2C 模式 | ESP32 从机, 地址 0x2A |
| UART 波特率 | 115200, 8N1 |
1. 数据模型
Point = { x: uint16, y: uint16, angle_x10: uint16 }
Fragment = { source: Point, target: Point }
Cycle = { cycle_id: uint16, frag_count: uint8 (2~4), fragments: Fragment[] }
- Point: 单个坐标点,
angle_x10 = 角度 × 10(如450= 45.0°) - 角度语义:
source.angle = 0表示保持当前朝向;target.angle为搬运 过程中的相对转角, 归一化到[0, 360.0)度 (如 -20° 编码为 3400)。 MCU 执行 fragment 时在 ±270° 行程内为 target 选择与当前位置最近的等价角 (mod 360°), 就近旋转, 避免把 -20° 当成 +340° 绕远路 - Fragment: 一次运动片段, 从
source移动到target - Cycle: 一个执行周期, 包含 2~4 个 Fragment
2. 帧格式
┌────────┬────────┬────────┬────────┬───────────────────┬──────────────┐
│ SOF1 │ SOF2 │ CMD │ LEN │ DATA (N bytes) │ CRC16 (LE) │
│ 0xAA │ 0x55 │ 1B │ 1B │ variable │ 2B │
└────────┴────────┴────────┴────────┴───────────────────┴──────────────┘
◄──────────── CRC 计算范围 (CMD + LEN + DATA) ────────────►
- SOF: 帧头, 固定
0xAA 0x55 - CMD: 命令字 (见 §3)
- LEN: DATA 区字节数
- DATA: 有效载荷
- CRC: CRC-16/MODBUS, 低位在前
3. 命令字
| 命令 | 值 | 方向 | 说明 |
|---|---|---|---|
CYCLE_DATA |
0x01 |
MPU → MCU | 下发 Cycle 数据 |
CYCLE_DONE |
0x02 |
MCU → MPU | Cycle 执行完成 (MPU 同时终止任务计时) |
CYCLE_REQUEST |
0x03 |
MCU → MPU | 请求下一个 Cycle |
RECOGNIZE |
0x04 |
MCU → MPU | 触发视觉识别 |
TASK_START |
0x05 |
MCU → MPU | 任务开始 (按钮按下即发, 与 RECOGNIZE 区分; MPU 据此开始计时) |
ACK |
0x80 |
双向 | 通用应答 (预留) |
NACK |
0xFF |
双向 | 错误 (预留) |
4. 帧类型详解
4.1 CYCLE_DATA (MPU → MCU)
SOF(2) + CMD=0x01 + LEN + DATA + CRC(2)
DATA:
┌──────────────┬────────────┬─────────────────────────┐
│ cycle_id │ frag_count │ fragments │
│ uint16 (BE) │ uint8 │ N × 12B │
│ 2B │ 1B │ 12~48B │
└──────────────┴────────────┴─────────────────────────┘
LEN = 3 + frag_count × 12
Fragment (12B):
┌──────────┬──────────┬───────────────┬──────────┬──────────┬───────────────┐
│ src.x │ src.y │ src.angle_x10 │ dst.x │ dst.y │ dst.angle_x10 │
│ uint16 │ uint16 │ uint16 │ uint16 │ uint16 │ uint16 │
│ 2B │ 2B │ 2B │ 2B │ 2B │ 2B │
└──────────┴──────────┴───────────────┴──────────┴──────────┴───────────────┘
示例 (2 个 Fragment, cycle_id=1):
AA 55 01 1B 00 01 02
00 64 00 C8 01 C2 01 2C 01 90 02 58 ← Fragment 0
00 96 01 2C 03 84 01 F4 02 BC 04 B0 ← Fragment 1
XX XX ← CRC16
解析:
| 字段 | 值 | 含义 |
|---|---|---|
| cycle_id | 0x0001 = 1 |
第 1 个 Cycle |
| frag_count | 0x02 = 2 |
2 个 Fragment |
| F0 src | (100, 200), 45.0° | 示例: 抓取点 (角度字段保持当前) |
| F0 dst | (300, 400), 60.0° | 示例: 放置点 + 相对转角 60.0° |
| F1 src | (150, 300), 90.0° | 示例: 抓取点 (角度字段保持当前) |
| F1 dst | (500, 700), 120.0° | 示例: 放置点 + 相对转角 120.0° |
4.2 CYCLE_DONE (MCU → MPU)
SOF(2) + CMD=0x02 + LEN=2 + cycle_id(2B, BE) + CRC(2)
总长度: 8 字节
示例 (cycle_id=1 完成):
AA 55 02 02 00 01 XX XX
4.3 CYCLE_REQUEST (MCU → MPU)
SOF(2) + CMD=0x03 + LEN=0 + CRC(2)
总长度: 6 字节
示例:
AA 55 03 00 XX XX
5. CRC-16/MODBUS
| 参数 | 值 |
|---|---|
| 多项式 | 0x8005 (逆序 0xA001) |
| 初始值 | 0xFFFF |
| 输入反射 | 是 |
| 输出反射 | 是 |
| 异或输出 | 0x0000 |
| 校验范围 | CMD + LEN + DATA |
参考实现 (C)
uint16_t edc_crc16(const uint8_t *data, size_t len)
{
uint16_t crc = 0xFFFF;
for (size_t i = 0; i < len; i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++) {
if (crc & 0x0001)
crc = (crc >> 1) ^ 0xA001;
else
crc >>= 1;
}
}
return crc;
}
6. 传输通道
6.1 I2C (冗余接收)
- ESP32 作为 I2C 从机, 地址
0x2A(7-bit) - MPU 作为 I2C 主机
- MPU 将完整帧直接
Write到从机地址 - ESP32 通过 ISR 回调接收并 CRC 校验
- 默认引脚: SDA=GPIO2, SCL=GPIO3
6.2 UART (双向)
- RX: 接收 CYCLE_DATA (冗余备份)
- TX: 发送 CYCLE_DONE / CYCLE_REQUEST
- 默认引脚: TX=GPIO11, RX=GPIO12 (UART0)
- 波特率: 115200, 8N1
- 注: ESP32-C5 日志控制台使用 USB Serial/JTAG, UART0 专用于本协议
6.3 冗余策略
同一帧 CYCLE_DATA 同时通过 I2C 和 UART 发送, 任一通道先收到有效帧即采纳, 通过 edc_proto_stat_t.last_channel 可判断来源。
7. 交互流程
MPU MCU (ESP32-C5)
│ │
│──── CYCLE_DATA (I2C+UART) ────────►│ 下发 Cycle
│ │
│ │ 执行 fragments[0..N]
│ │
│◄──── CYCLE_DONE (UART) ───────────│ 上报完成
│ │
│──── CYCLE_DATA (I2C+UART) ────────►│ 下发下一个...
│ │
— 或 —
│ │
│ (超时无数据) │
│ │
│◄──── CYCLE_REQUEST (UART) ────────│ 主动请求
8. MCU 端 API
#include "protocol.h"
// 初始化 (I2C 从机 + UART)
edc_proto_init(NULL);
// 方式一: 手动轮询
edc_cycle_t cycle;
if (edc_proto_poll(&cycle) > 0) {
// 处理 cycle.fragments[0..frag_count-1]
edc_proto_send_done(cycle.cycle_id);
}
// 方式二: FreeRTOS 任务 + 回调
xTaskCreate(edc_proto_task, "proto", 4096, NULL, 5, NULL);
void edc_proto_on_cycle(const edc_cycle_t *cycle, edc_channel_t ch) {
// ch: EDC_CH_I2C 或 EDC_CH_UART
// 执行完调用:
edc_proto_send_done(cycle->cycle_id);
}
// 超时请求
edc_proto_send_request();
// 查看统计
edc_proto_stat_t stat;
edc_proto_get_stats(&stat);
// stat.i2c.cycles_ok, stat.uart.cycles_err, stat.done_sent ...
9. 帧总长速查
| frag_count | DATA 长度 | 帧总长 (SOF+CMD+LEN+DATA+CRC) |
|---|---|---|
| 2 | 3 + 24 = 27 | 2 + 1 + 1 + 27 + 2 = 33 |
| 3 | 3 + 36 = 39 | 2 + 1 + 1 + 39 + 2 = 45 |
| 4 | 3 + 48 = 51 | 2 + 1 + 1 + 51 + 2 = 57 |
| DONE | 2 | 8 |
| REQUEST | 0 | 6 |