Files
2026-08-03 04:09:26 +08:00

8.2 KiB
Raw Permalink Blame History

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