Files
edc-mcu/components/protocol/PROTOCOL.md
T
2026-08-03 04:09:26 +08:00

262 lines
8.2 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.
# 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)
```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** |