磁铁、舵机、网页控制、通信协议

This commit is contained in:
kirl committed 2026-07-29 21:11:16 +08:00
1 parent 230ad1254a
commit 40ee976236
17 files changed
+1879 -32

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
idf_component_register(SRCS "protocol.c"
INCLUDE_DIRS "."
REQUIRES driver esp_driver_i2c esp_driver_uart esp_driver_gpio freertos)
+254
View File
@@ -0,0 +1,254 @@
# 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=GPIO8, RX=GPIO9
- 波特率: 115200, 8N1
### 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** |
+490
View File
@@ -0,0 +1,490 @@
/**
* @file protocol.c
* @brief EDC 通讯协议实现 — CRC16 / 组帧 / 解析 / I2C / UART
*/
#include "protocol.h"
#include <string.h>
#include "esp_log.h"
#include "freertos/task.h"
static const char *TAG = "EDC_PROTO";
/* ================================================================
* CRC-16/MODBUS
*
* 多项式: 0x8005 (逆序 0xA001)
* 初始值: 0xFFFF
* 输入/输出: 反射
* 异或输出: 0x0000
* 校验范围: CMD + LEN + DATA
* ================================================================ */
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;
}
/* ================================================================
* 大端序 uint16_t 写入 / 读取
* ================================================================ */
static inline void write_u16_be(uint8_t *buf, uint16_t val)
{
buf[0] = (uint8_t)(val >> 8);
buf[1] = (uint8_t)(val);
}
static inline uint16_t read_u16_be(const uint8_t *buf)
{
return ((uint16_t)buf[0] << 8) | buf[1];
}
/* ================================================================
* Cycle 组帧
*
* DATA 区: cycle_id(2B,BE) + frag_count(1B) + fragments(N×12B)
* Fragment: src.x(2B) src.y(2B) src.angle(2B) dst.x(2B) dst.y(2B) dst.angle(2B)
* ================================================================ */
int edc_cycle_pack(const edc_cycle_t *cycle, uint8_t *out)
{
uint8_t count = cycle->frag_count;
if (count < EDC_FRAG_MIN) count = EDC_FRAG_MIN;
if (count > EDC_FRAG_MAX) count = EDC_FRAG_MAX;
uint8_t data_len = 3 + count * EDC_FRAG_BYTES; /* id(2) + cnt(1) + frags */
out[0] = EDC_PROTO_SOF1;
out[1] = EDC_PROTO_SOF2;
out[2] = EDC_CMD_CYCLE_DATA;
out[3] = data_len;
/* cycle_id (大端) */
uint8_t *p = &out[4];
write_u16_be(p, cycle->cycle_id);
p += 2;
/* frag_count */
*p++ = count;
/* fragments */
for (int i = 0; i < count; i++) {
const edc_fragment_t *f = &cycle->fragments[i];
write_u16_be(p, f->source.x); p += 2;
write_u16_be(p, f->source.y); p += 2;
write_u16_be(p, f->source.angle_x10); p += 2;
write_u16_be(p, f->target.x); p += 2;
write_u16_be(p, f->target.y); p += 2;
write_u16_be(p, f->target.angle_x10); p += 2;
}
/* CRC16 over [CMD, LEN, DATA] */
uint16_t crc = edc_crc16(&out[2], 2 + data_len);
*p++ = (uint8_t)(crc);
*p++ = (uint8_t)(crc >> 8);
return (int)(p - out); /* 2 + 1 + 1 + data_len + 2 */
}
/* ================================================================
* Cycle 解析 (逐字节)
* ================================================================ */
edc_parse_result_t edc_cycle_parse_byte(edc_frame_ctx_t *ctx, uint8_t byte,
edc_cycle_t *cycle)
{
switch (ctx->state) {
case EDC_PARSE_OK:
ctx->idx = 0;
ctx->state = EDC_PARSE_WAITING;
/* fall through */
case EDC_PARSE_WAITING:
default:
if (byte == EDC_PROTO_SOF1) {
ctx->buf[0] = byte;
ctx->idx = 1;
}
return EDC_PARSE_WAITING;
}
/* ---- 收帧中 (idx >= 1) ---- */
if (ctx->idx == 1) {
if (byte != EDC_PROTO_SOF2) {
ctx->idx = 0;
if (byte == EDC_PROTO_SOF1) { ctx->buf[0] = byte; ctx->idx = 1; }
return EDC_PARSE_WAITING;
}
ctx->buf[1] = byte;
ctx->idx = 2;
return EDC_PARSE_WAITING;
}
if (ctx->idx < EDC_PROTO_FRAME_MAX) {
ctx->buf[ctx->idx] = byte;
}
ctx->idx++;
/* 确定帧总长 */
if (ctx->idx == 4) {
uint8_t data_len = ctx->buf[3];
if (data_len < 3 || data_len > EDC_PROTO_FRAME_MAX - 6) {
ctx->idx = 0; ctx->state = EDC_PARSE_WAITING;
return EDC_PARSE_LEN_ERR;
}
ctx->len = 2 + 1 + 1 + data_len + 2; /* SOF+CMD+LEN+DATA+CRC */
}
/* 收满一帧 */
if (ctx->idx >= ctx->len) {
ctx->idx = ctx->len;
/* CRC 校验 */
uint16_t crc_calc = edc_crc16(&ctx->buf[2], 2 + ctx->buf[3]);
uint16_t crc_recv = ctx->buf[ctx->len - 2] | ((uint16_t)ctx->buf[ctx->len - 1] << 8);
if (crc_calc != crc_recv) {
ctx->idx = 0; ctx->state = EDC_PARSE_WAITING;
return EDC_PARSE_CRC_ERR;
}
/* 仅处理 CYCLE_DATA 命令 */
uint8_t cmd = ctx->buf[2];
if (cmd != EDC_CMD_CYCLE_DATA) {
ctx->idx = 0; ctx->state = EDC_PARSE_WAITING;
return EDC_PARSE_CMD_ERR;
}
/* 解析 DATA 区 */
const uint8_t *d = &ctx->buf[4];
cycle->cycle_id = read_u16_be(d); d += 2;
cycle->frag_count = *d; d += 1;
if (cycle->frag_count < EDC_FRAG_MIN || cycle->frag_count > EDC_FRAG_MAX) {
ctx->idx = 0; ctx->state = EDC_PARSE_WAITING;
return EDC_PARSE_LEN_ERR;
}
for (int i = 0; i < cycle->frag_count; i++) {
edc_fragment_t *f = &cycle->fragments[i];
f->source.x = read_u16_be(d); d += 2;
f->source.y = read_u16_be(d); d += 2;
f->source.angle_x10 = read_u16_be(d); d += 2;
f->target.x = read_u16_be(d); d += 2;
f->target.y = read_u16_be(d); d += 2;
f->target.angle_x10 = read_u16_be(d); d += 2;
}
cycle->timestamp_ms = 0;
ctx->state = EDC_PARSE_OK;
return EDC_PARSE_OK;
}
return EDC_PARSE_WAITING;
}
/* ================================================================
* Cycle 解析 (一次性)
* ================================================================ */
edc_parse_result_t edc_cycle_parse_buf(const uint8_t *data, size_t len,
edc_cycle_t *cycle)
{
edc_frame_ctx_t ctx = { .state = EDC_PARSE_WAITING };
edc_parse_result_t res;
for (size_t i = 0; i < len; i++) {
res = edc_cycle_parse_byte(&ctx, data[i], cycle);
if (res != EDC_PARSE_WAITING) return res;
}
return EDC_PARSE_WAITING;
}
/* ================================================================
* 统一收发器
* ================================================================ */
/* ---- 共享最新 Cycle ---- */
static edc_cycle_t s_latest_cycle;
static volatile bool s_cycle_updated;
static portMUX_TYPE s_lock = portMUX_INITIALIZER_UNLOCKED;
/* ---- 统计 ---- */
static edc_proto_stat_t s_stat;
/* ---- 通道使能 ---- */
static bool s_i2c_enabled;
static bool s_uart_enabled;
/* ---- I2C ---- */
static i2c_slave_dev_handle_t s_i2c_handle;
static edc_cycle_t s_i2c_rx_cycle;
static volatile bool s_i2c_rx_pending;
/* ---- UART ---- */
static uart_port_t s_uart_port;
static edc_frame_ctx_t s_uart_ctx;
/* ================================================================
* 内部: 存入共享 Cycle
* ================================================================ */
static void store_cycle(const edc_cycle_t *cycle, edc_channel_t ch)
{
portENTER_CRITICAL(&s_lock);
memcpy(&s_latest_cycle, cycle, sizeof(edc_cycle_t));
s_latest_cycle.timestamp_ms = xTaskGetTickCount() * portTICK_PERIOD_MS;
s_cycle_updated = true;
portEXIT_CRITICAL(&s_lock);
if (ch == EDC_CH_I2C) s_stat.i2c.cycles_ok++;
else if (ch == EDC_CH_UART) s_stat.uart.cycles_ok++;
s_stat.last_channel = ch;
s_stat.last_tick = xTaskGetTickCount();
}
/* ================================================================
* I2C 从机 接收
* ================================================================ */
static bool on_i2c_rx(i2c_slave_dev_handle_t handle,
const i2c_slave_rx_done_event_data_t *evt,
void *arg)
{
s_stat.i2c.bytes_total += evt->length;
edc_cycle_t cycle;
if (edc_cycle_parse_buf(evt->buffer, evt->length, &cycle) == EDC_PARSE_OK) {
memcpy(&s_i2c_rx_cycle, &cycle, sizeof(edc_cycle_t));
s_i2c_rx_cycle.timestamp_ms = xTaskGetTickCountFromISR() * portTICK_PERIOD_MS;
s_i2c_rx_pending = true;
}
return false;
}
static bool i2c_poll_once(void)
{
if (!s_i2c_rx_pending) return false;
edc_cycle_t cycle;
portENTER_CRITICAL(&s_lock);
memcpy(&cycle, &s_i2c_rx_cycle, sizeof(edc_cycle_t));
s_i2c_rx_pending = false;
portEXIT_CRITICAL(&s_lock);
store_cycle(&cycle, EDC_CH_I2C);
ESP_LOGD(TAG, "I2C cycle OK (id=%u frags=%u)", cycle.cycle_id, cycle.frag_count);
return true;
}
/* ================================================================
* UART 接收
* ================================================================ */
static bool uart_poll_once(void)
{
uint8_t byte;
bool got = false;
while (uart_read_bytes(s_uart_port, &byte, 1, pdMS_TO_TICKS(0)) > 0) {
s_stat.uart.bytes_total++;
edc_cycle_t cycle;
edc_parse_result_t res = edc_cycle_parse_byte(&s_uart_ctx, byte, &cycle);
if (res == EDC_PARSE_OK) {
store_cycle(&cycle, EDC_CH_UART);
got = true;
ESP_LOGD(TAG, "UART cycle OK (id=%u frags=%u)", cycle.cycle_id, cycle.frag_count);
} else if (res < 0) {
s_stat.uart.cycles_err++;
ESP_LOGW(TAG, "UART parse err: %d", res);
}
}
return got;
}
/* ================================================================
* 内部: 通过 UART 发送短帧
* ================================================================ */
static void uart_send_frame(uint8_t cmd, const uint8_t *data, uint8_t data_len)
{
uint8_t buf[EDC_PROTO_FRAME_MAX];
buf[0] = EDC_PROTO_SOF1;
buf[1] = EDC_PROTO_SOF2;
buf[2] = cmd;
buf[3] = data_len;
if (data && data_len) memcpy(&buf[4], data, data_len);
uint16_t crc = edc_crc16(&buf[2], 2 + data_len);
buf[4 + data_len] = (uint8_t)(crc);
buf[4 + data_len + 1] = (uint8_t)(crc >> 8);
int total = 4 + data_len + 2;
uart_write_bytes(s_uart_port, (const char *)buf, total);
}
/* ================================================================
* 初始化
* ================================================================ */
void edc_proto_init(const edc_proto_config_t *cfg)
{
edc_proto_config_t dft = {
.i2c_enable = true,
.i2c_port = EDC_I2C_PORT_DEFAULT,
.i2c_addr = EDC_I2C_SLAVE_ADDR,
.i2c_sda = EDC_I2C_SDA_DEFAULT,
.i2c_scl = EDC_I2C_SCL_DEFAULT,
.uart_enable = true,
.uart_port = EDC_UART_PORT_DEFAULT,
.uart_baudrate = EDC_UART_BAUDRATE,
.uart_rx = EDC_UART_RX_DEFAULT,
.uart_tx = EDC_UART_TX_DEFAULT,
};
if (cfg) dft = *cfg;
s_i2c_enabled = dft.i2c_enable;
s_uart_enabled = dft.uart_enable;
memset(&s_stat, 0, sizeof(s_stat));
/* I2C */
if (s_i2c_enabled) {
i2c_slave_config_t i2c_cfg = {
.i2c_port = dft.i2c_port,
.sda_io_num = dft.i2c_sda,
.scl_io_num = dft.i2c_scl,
.clk_source = I2C_CLK_SRC_DEFAULT,
.send_buf_depth = 0,
.receive_buf_depth = EDC_I2C_BUF_SIZE,
.slave_addr = dft.i2c_addr,
.addr_bit_len = I2C_ADDR_BIT_LEN_7,
.intr_priority = 0,
.flags.enable_internal_pullup = true,
};
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_cfg, &s_i2c_handle));
i2c_slave_event_callbacks_t cbs = {
.on_receive = on_i2c_rx,
.on_request = NULL,
};
ESP_ERROR_CHECK(i2c_slave_register_event_callbacks(s_i2c_handle, &cbs, NULL));
ESP_LOGI(TAG, "I2C slave OK (0x%02X SDA=%d SCL=%d)",
dft.i2c_addr, dft.i2c_sda, dft.i2c_scl);
}
/* UART */
if (s_uart_enabled) {
s_uart_port = dft.uart_port;
memset(&s_uart_ctx, 0, sizeof(s_uart_ctx));
s_uart_ctx.state = EDC_PARSE_WAITING;
uart_config_t uart_cfg = {
.baud_rate = dft.uart_baudrate,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
ESP_ERROR_CHECK(uart_driver_install(dft.uart_port,
EDC_UART_BUF_SIZE, EDC_UART_BUF_SIZE,
0, NULL, 0));
ESP_ERROR_CHECK(uart_param_config(dft.uart_port, &uart_cfg));
ESP_ERROR_CHECK(uart_set_pin(dft.uart_port,
dft.uart_tx, dft.uart_rx,
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
ESP_LOGI(TAG, "UART OK (port=%d baud=%d RX=%d TX=%d)",
dft.uart_port, dft.uart_baudrate, dft.uart_rx, dft.uart_tx);
}
}
/* ================================================================
* 轮询
* ================================================================ */
int edc_proto_poll(edc_cycle_t *cycle)
{
bool got = false;
if (s_i2c_enabled) got |= i2c_poll_once();
if (s_uart_enabled) got |= uart_poll_once();
if (got) {
portENTER_CRITICAL(&s_lock);
memcpy(cycle, &s_latest_cycle, sizeof(edc_cycle_t));
s_cycle_updated = false;
portEXIT_CRITICAL(&s_lock);
return 1;
}
return 0;
}
/* ================================================================
* 发送 DONE / REQUEST
* ================================================================ */
void edc_proto_send_done(uint16_t cycle_id)
{
if (!s_uart_enabled) return;
uint8_t data[2];
write_u16_be(data, cycle_id);
uart_send_frame(EDC_CMD_CYCLE_DONE, data, 2);
s_stat.done_sent++;
ESP_LOGI(TAG, "DONE sent (cycle_id=%u)", cycle_id);
}
void edc_proto_send_request(void)
{
if (!s_uart_enabled) return;
uart_send_frame(EDC_CMD_CYCLE_REQUEST, NULL, 0);
s_stat.request_sent++;
ESP_LOGI(TAG, "REQUEST sent");
}
/* ================================================================
* 统计
* ================================================================ */
void edc_proto_get_stats(edc_proto_stat_t *stat)
{
memcpy(stat, &s_stat, sizeof(edc_proto_stat_t));
}
/* ================================================================
* 弱回调
* ================================================================ */
__attribute__((weak))
void edc_proto_on_cycle(const edc_cycle_t *cycle, edc_channel_t ch)
{
(void)cycle; (void)ch;
}
/* ================================================================
* FreeRTOS 任务
* ================================================================ */
void edc_proto_task(void *pvParam)
{
edc_cycle_t cycle;
while (1) {
if (edc_proto_poll(&cycle) > 0) {
edc_channel_t ch;
portENTER_CRITICAL(&s_lock);
ch = s_stat.last_channel;
portEXIT_CRITICAL(&s_lock);
edc_proto_on_cycle(&cycle, ch);
}
vTaskDelay(pdMS_TO_TICKS(2));
}
}
+233
View File
@@ -0,0 +1,233 @@
/**
* @file protocol.h
* @brief EDC 通讯协议 — I2C / UART 双通道, Cycle 收发, CRC16 校验
*
* 顶层概念:
* Point = { x, y, angle_x10 }
* Fragment = { source: Point, target: Point }
* Cycle = { cycle_id, frag_count (2~4), fragments[] }
*
* 帧格式 (大端序):
* ┌──────┬──────┬──────┬──────┬───────────────────┬──────────┐
* │ SOF1 │ SOF2 │ CMD │ LEN │ DATA (N bytes) │ CRC16 │
* │ 0xAA │ 0x55 │ 1B │ 1B │ variable │ 2B (LE) │
* └──────┴──────┴──────┴──────┴───────────────────┴──────────┘
* ◄──────── CRC 计算范围 (CMD + LEN + DATA) ─────────►
*
* Cycle 数据帧 DATA 区:
* cycle_id(2B, BE) + frag_count(1B, 2~4) + fragments(N × 12B)
*
* Fragment (12B, 大端):
* src.x(2B) src.y(2B) src.angle(2B) dst.x(2B) dst.y(2B) dst.angle(2B)
*
* 交互流程:
* MPU ──[CYCLE_DATA]──► MCU (I2C + UART 冗余下发)
* MCU ──[CYCLE_DONE]──► MPU (UART 上报完成, 含 cycle_id)
* MCU ──[CYCLE_REQ]───► MPU (UART 请求下一 Cycle)
*/
#ifndef EDC_PROTOCOL_H
#define EDC_PROTOCOL_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "driver/gpio.h"
#include "driver/uart.h"
#include "driver/i2c_slave.h"
#ifdef __cplusplus
extern "C" {
#endif
/* ================================================================
* 协议常量
* ================================================================ */
#define EDC_PROTO_SOF1 0xAA
#define EDC_PROTO_SOF2 0x55
#define EDC_PROTO_FRAME_MAX 80 /* 最大帧长: 2+1+1+3+4*12+2 = 57 < 80 */
#define EDC_PROTO_FRAME_MIN 6 /* SOF(2) + CMD(1) + LEN(1) + CRC(2) */
#define EDC_FRAG_MIN 2 /* 每 Cycle 最少 fragment 数 */
#define EDC_FRAG_MAX 4 /* 每 Cycle 最多 fragment 数 */
#define EDC_FRAG_BYTES 12 /* 单个 fragment 字节数 (3×uint16×2) */
/* ---- I2C 默认 ---- */
#define EDC_I2C_PORT_DEFAULT 0
#define EDC_I2C_SLAVE_ADDR 0x2A
#define EDC_I2C_SDA_DEFAULT GPIO_NUM_2
#define EDC_I2C_SCL_DEFAULT GPIO_NUM_3
#define EDC_I2C_BUF_SIZE 256
/* ---- UART 默认 ---- */
#define EDC_UART_PORT_DEFAULT UART_NUM_0
#define EDC_UART_BAUDRATE 115200
#define EDC_UART_RX_DEFAULT GPIO_NUM_9
#define EDC_UART_TX_DEFAULT GPIO_NUM_8
#define EDC_UART_BUF_SIZE 512
/* ================================================================
* 命令字
* ================================================================ */
typedef enum {
EDC_CMD_CYCLE_DATA = 0x01, /* MPU → MCU: 下发 Cycle 数据 */
EDC_CMD_CYCLE_DONE = 0x02, /* MCU → MPU: Cycle 执行完成 */
EDC_CMD_CYCLE_REQUEST = 0x03, /* MCU → MPU: 请求下一个 Cycle */
EDC_CMD_ACK = 0x80, /* 通用应答 */
EDC_CMD_NACK = 0xFF, /* 错误 */
} edc_cmd_t;
/* ================================================================
* 数据结构
* ================================================================ */
/** 单个坐标点 (source 或 target) */
typedef struct {
uint16_t x;
uint16_t y;
uint16_t angle_x10; /* 角度 × 10, 如 450 = 45.0° */
} edc_point_t;
/** 一个运动片段: 从 source 移动到 target */
typedef struct {
edc_point_t source;
edc_point_t target;
} edc_fragment_t;
/** 一个 Cycle: 包含 2~4 个 fragment */
typedef struct {
uint16_t cycle_id;
uint8_t frag_count; /* 实际 fragment 数 (2~4) */
edc_fragment_t fragments[EDC_FRAG_MAX];
uint32_t timestamp_ms; /* 本地时间戳 (不随帧传输) */
} edc_cycle_t;
/** 帧来源通道 */
typedef enum {
EDC_CH_NONE = 0,
EDC_CH_I2C = 1,
EDC_CH_UART = 2,
} edc_channel_t;
/** 每通道统计 */
typedef struct {
uint32_t cycles_ok;
uint32_t cycles_err;
uint32_t bytes_total;
} edc_channel_stat_t;
/** 统一收发统计 */
typedef struct {
edc_channel_stat_t i2c;
edc_channel_stat_t uart;
edc_channel_t last_channel;
uint32_t last_tick;
uint32_t done_sent; /* 已发送 DONE 次数 */
uint32_t request_sent; /* 已发送 REQUEST 次数 */
} edc_proto_stat_t;
/** 帧解析状态 */
typedef enum {
EDC_PARSE_OK = 0,
EDC_PARSE_WAITING = 1,
EDC_PARSE_CRC_ERR = -1,
EDC_PARSE_LEN_ERR = -2,
EDC_PARSE_SOF_ERR = -3,
EDC_PARSE_CMD_ERR = -4,
} edc_parse_result_t;
/** 帧解析上下文 (逐字节喂入) */
typedef struct {
uint8_t buf[EDC_PROTO_FRAME_MAX];
uint8_t len;
uint8_t idx;
edc_parse_result_t state;
} edc_frame_ctx_t;
/* ================================================================
* 配置
* ================================================================ */
typedef struct {
bool i2c_enable;
int i2c_port;
uint16_t i2c_addr;
gpio_num_t i2c_sda;
gpio_num_t i2c_scl;
bool uart_enable;
uart_port_t uart_port;
int uart_baudrate;
gpio_num_t uart_rx;
gpio_num_t uart_tx;
} edc_proto_config_t;
/* ================================================================
* CRC16 / 组帧 / 逐字节解析 (底层)
* ================================================================ */
uint16_t edc_crc16(const uint8_t *data, size_t len);
/** 将 Cycle 序列化为协议字节流 (含帧头与 CRC) */
int edc_cycle_pack(const edc_cycle_t *cycle, uint8_t *out);
/** 逐字节解析器 (与旧版兼容, 内部使用 edc_cycle_parse_byte) */
edc_parse_result_t edc_cycle_parse_byte(edc_frame_ctx_t *ctx, uint8_t byte,
edc_cycle_t *cycle);
/** 从完整缓冲区一次性解析 Cycle */
edc_parse_result_t edc_cycle_parse_buf(const uint8_t *data, size_t len,
edc_cycle_t *cycle);
/* ================================================================
* 统一收发器 API (I2C 从机 + UART 双向)
* ================================================================ */
/**
* @brief 初始化协议栈 (I2C 从机 + UART)
* @param cfg 传 NULL 使用默认值
*/
void edc_proto_init(const edc_proto_config_t *cfg);
/**
* @brief 轮询双通道接收 + 返回最新 Cycle (非阻塞)
* @return >0: 有新 Cycle; 0: 无; <0: 错误
*/
int edc_proto_poll(edc_cycle_t *cycle);
/**
* @brief 发送 CYCLE_DONE (通过 UART)
* @param cycle_id 已完成的 Cycle ID
*/
void edc_proto_send_done(uint16_t cycle_id);
/**
* @brief 发送 CYCLE_REQUEST (通过 UART)
*/
void edc_proto_send_request(void);
/**
* @brief 获取统计信息
*/
void edc_proto_get_stats(edc_proto_stat_t *stat);
/* ---- FreeRTOS 任务 + 回调 ---- */
void edc_proto_task(void *pvParam);
/**
* @brief Cycle 回调 (弱符号, 用户实现)
* @param cycle 已校验的有效 Cycle
* @param ch 来源通道
*/
void edc_proto_on_cycle(const edc_cycle_t *cycle, edc_channel_t ch);
#ifdef __cplusplus
}
#endif
#endif /* EDC_PROTOCOL_H */