# 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 执行完成 | | `CYCLE_REQUEST` | `0x03` | MCU → MPU | 请求下一个 Cycle | | `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) ```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 ```c #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** |