Files
edc-mcu/components/protocol/PROTOCOL.md
T
kirl af4f0edc08 修复协议解析器, 打通与 MPU 的双向通信
- protocol: 修复 edc_cycle_parse_byte 状态机死代码 (switch 恒 return, 收帧逻辑不可达), 并加 LEN 未就绪保护
- protocol: UART 默认引脚改为 GPIO11/12 (UART0)
- sdkconfig.defaults: 控制台切至 USB Serial/JTAG, 避免与协议 UART0 冲突
- web_ctrl: 新增协议状态 API 与 Cycle 快照; on_cycle 回 DONE 并按 cycle_id 去重
- main: 接入协议初始化与接收任务, 更新接线注释
- 实测: MPU 每秒下发 Cycle, I2C/UART 双通道 12/12 收, DONE 12/12 回
2026-07-29 23:24:31 +08:00

256 lines
7.4 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°)
- **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° | |
| F1 src | (150, 300), 90.0° | |
| F1 dst | (500, 700), 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** |