0979866a7c
- 删除 protocol.h,帧定义和标定命令码统一迁入 comm_protocol.h - 新增 comm_protocol.c:上行组包(CoP/标定响应)、下行解析+msgq 分发 - ble_transport 剥离所有协议解析和参数 getter,仅保留字节收发+rx 回调注册 - main.c 初始化顺序:ble_transport → comm_protocol → adv_start → cal_init - 更新 README 架构图和项目结构表,补充标定流程章节 - 更新 BALANCE_BOARD_PROTOCOL.md 增加标定命令/响应帧格式文档
303 lines
11 KiB
Markdown
303 lines
11 KiB
Markdown
# 平衡板协议说明
|
||
|
||
本文说明当前固件里的数据包格式和 BLE 通讯方式。
|
||
|
||
## 1. BLE 通讯方式
|
||
|
||
平衡板工作在 BLE `Peripheral` 角色,通过 Nordic UART Service(NUS)与基站通信。
|
||
|
||
- 上行方向:平衡板 -> 基站,使用 NUS `Notify`
|
||
- 下行方向:基站 -> 平衡板,使用 NUS `Write`
|
||
- 广播内容:包含设备名和 NUS 128-bit UUID
|
||
|
||
### 1.1 NUS UUID
|
||
|
||
| 项目 | UUID | 方向 | 说明 |
|
||
|------|------|------|------|
|
||
| Service | `6E400001-B5A3-F393-E0A9-E50E24DCCA9E` | - | NUS 主服务 |
|
||
| TX Characteristic | `6E400003-B5A3-F393-E0A9-E50E24DCCA9E` | 平衡板 -> 基站 | Notify,上报 CoP 和编码器数据 |
|
||
| RX Characteristic | `6E400002-B5A3-F393-E0A9-E50E24DCCA9E` | 基站 -> 平衡板 | Write,下发阻力参数和控制命令 |
|
||
|
||
### 1.2 当前连接行为
|
||
|
||
- 连接建立后,平衡板会请求 `7.5-15 ms` 连接区间(`min=6, max=12`)。
|
||
- 所有数据通过 NUS 二进制帧发送,不走文本协议。
|
||
|
||
## 2. 数据包总览
|
||
|
||
当前协议使用双帧头 `0xAA 0x55` 作为包起始标记。所有多字节字段按 Little Endian 编码。
|
||
|
||
每帧末尾附加 1 字节 CRC-8/MAXIM 校验(poly=`0x31`, init=`0x00`, refin=true, refout=true, xorout=`0x00`)。校验范围为帧头之后、CRC 字节之前的所有字段(即跳过 `sync0`/`sync1`)。调用方式:`crc8(data + 2, len - 3, 0x31, 0x00, true)`。
|
||
|
||
| 类型名 | 值 | 方向 | 长度 | 频率 | 说明 |
|
||
|--------|----|------|------|------|------|
|
||
| `PROTO_TYPE_COP` | `0x01` | 平衡板 -> 基站 | `11` | 50 Hz | 压力中心帧 |
|
||
| `PROTO_TYPE_ENCODER` | `0x02` | 平衡板 -> 基站 | `13` | TBD | 电机编码器帧(预留) |
|
||
| `PROTO_TYPE_RESISTANCE` | `0x10` | 基站 -> 平衡板 | `16` | 按需 | 阻力参数帧 |
|
||
| `PROTO_TYPE_SPOTTER` | `0x11` | 基站 -> 平衡板 | `9` | 按需 | 保护模式帧 |
|
||
| `PROTO_TYPE_HEARTBEAT` | `0x20` | 基站 -> 平衡板 | `5` | ~1 Hz | 心跳帧 |
|
||
| `PROTO_TYPE_CAL_CMD` | `0x30` | 基站 -> 平衡板 | `10` | 按需 | 标定命令帧 |
|
||
| `PROTO_TYPE_CAL_RESP` | `0x31` | 平衡板 -> 基站 | `14` | 按需 | 标定响应帧 |
|
||
|
||
## 3. 上行数据包
|
||
|
||
### 3.1 压力中心帧 (CoP)
|
||
|
||
CoP 帧固定 11 字节,结构如下:
|
||
|
||
| 字节偏移 | 字段 | 类型 | 值/范围 | 说明 |
|
||
|----------|------|------|---------|------|
|
||
| `0` | `sync0` | `uint8` | `0xAA` | 帧头第 1 字节 |
|
||
| `1` | `sync1` | `uint8` | `0x55` | 帧头第 2 字节 |
|
||
| `2` | `type` | `uint8` | `0x01` | CoP 包类型 |
|
||
| `3` | `flags` | `uint8` | 见下表 | 状态标志位 |
|
||
| `4-5` | `cop_x` | `int16 LE` | - | 压力中心 X 坐标 (cm) |
|
||
| `6-7` | `cop_y` | `int16 LE` | - | 压力中心 Y 坐标 (cm) |
|
||
| `8-9` | `force` | `int16 LE` | - | 总重量 (kg) |
|
||
| `10` | `crc` | `uint8` | - | CRC-8/MAXIM 校验 |
|
||
|
||
#### flags 位定义
|
||
|
||
| 位 | 名称 | 说明 |
|
||
|----|------|------|
|
||
| bit0 | `COP_FLAG_FORCE_VALID` | `1` 表示总力超过阈值,CoP 坐标有意义 |
|
||
| bit1-7 | 预留 | 填 `0` |
|
||
|
||
#### CoP 解算公式
|
||
|
||
传感器布局(俯视):
|
||
|
||
```text
|
||
S4 (FL) -------- S1 (FR)
|
||
| center |
|
||
S3 (BL) -------- S2 (BR)
|
||
```
|
||
|
||
传感器坐标(单位:cm,板面中心为原点):
|
||
|
||
| 传感器 | 位置 | X | Y |
|
||
|--------|------|---|---|
|
||
| S1 | 右前 (FR) | `+10` | `+10` |
|
||
| S2 | 右后 (BR) | `+10` | `-10` |
|
||
| S3 | 左后 (BL) | `-10` | `-10` |
|
||
| S4 | 左前 (FL) | `-10` | `+10` |
|
||
|
||
计算公式:
|
||
|
||
```text
|
||
F_total = F0 + F1 + F2 + F3
|
||
|
||
CoP_X = (F0 * x0 + F1 * x1 + F2 * x2 + F3 * x3) / F_total
|
||
CoP_Y = (F0 * y0 + F1 * y1 + F2 * y2 + F3 * y3) / F_total
|
||
```
|
||
|
||
当 `F_total < COP_MIN_FORCE_THRESHOLD` 时,CoP 坐标无意义,`flags.bit0 = 0`,`cop_x` 和 `cop_y` 填 `0`。
|
||
|
||
#### 示例
|
||
|
||
下面是一个 CoP 帧示例(CoP 有效,cop_x=3 cm, cop_y=5 cm, force=75 kg):
|
||
|
||
```text
|
||
AA 55 01 01 03 00 05 00 4B 00 XX
|
||
```
|
||
|
||
含义如下:
|
||
|
||
- `AA 55`:双帧头
|
||
- `01`:CoP 包
|
||
- `01`:flags = `force_valid`
|
||
- `03 00`:`cop_x = 3` cm(LE int16)
|
||
- `05 00`:`cop_y = 5` cm(LE int16)
|
||
- `4B 00`:`force = 75` kg(LE uint16)
|
||
- `XX`:CRC-8/MAXIM(对字节 `[2..9]` 计算)
|
||
|
||
### 3.2 电机编码器帧(预留)
|
||
|
||
编码器帧固定 13 字节,结构如下。该帧目前仅在协议中预留定义,固件暂不实现。
|
||
|
||
| 字节偏移 | 字段 | 类型 | 值/范围 | 说明 |
|
||
|----------|------|------|---------|------|
|
||
| `0` | `sync0` | `uint8` | `0xAA` | 帧头第 1 字节 |
|
||
| `1` | `sync1` | `uint8` | `0x55` | 帧头第 2 字节 |
|
||
| `2` | `type` | `uint8` | `0x02` | 编码器包类型 |
|
||
| `3` | `flags` | `uint8` | `0` | 预留 |
|
||
| `4-7` | `cable_length` | `float32 LE` | - | 拉索长度 (mm) |
|
||
| `8-11` | `velocity` | `float32 LE` | - | 拉索速度 (mm/s) |
|
||
| `12` | `crc` | `uint8` | - | CRC-8/MAXIM 校验 |
|
||
|
||
## 4. 下行数据包
|
||
|
||
### 4.1 阻力参数帧
|
||
|
||
阻力参数帧固定 16 字节,用于基站向平衡板下发电机阻力控制参数。
|
||
|
||
| 字节偏移 | 字段 | 类型 | 值/范围 | 说明 |
|
||
|----------|------|------|---------|------|
|
||
| `0` | `sync0` | `uint8` | `0xAA` | 帧头第 1 字节 |
|
||
| `1` | `sync1` | `uint8` | `0x55` | 帧头第 2 字节 |
|
||
| `2` | `type` | `uint8` | `0x10` | 阻力参数包类型 |
|
||
| `3-6` | `K` | `float32 LE` | - | 刚度 (N/m) |
|
||
| `7-10` | `B` | `float32 LE` | - | 阻尼 (Ns/m) |
|
||
| `11-14` | `Tau` | `float32 LE` | - | 时间常数 (s) |
|
||
| `15` | `crc` | `uint8` | - | CRC-8/MAXIM 校验 |
|
||
|
||
#### 示例
|
||
|
||
```text
|
||
AA 55 10 00 00 C8 42 00 00 48 41 CD CC 4C 3E XX
|
||
```
|
||
|
||
含义如下:
|
||
|
||
- `AA 55 10`:阻力参数帧头
|
||
- `00 00 C8 42`:`K = 100.0` N/m
|
||
- `00 00 48 41`:`B = 12.5` Ns/m
|
||
- `CD CC 4C 3E`:`Tau = 0.2` s
|
||
- `XX`:CRC-8/MAXIM
|
||
|
||
### 4.2 Spotter Mode 帧
|
||
|
||
Spotter 帧固定 9 字节,用于启用或关闭保护模式。
|
||
|
||
| 字节偏移 | 字段 | 类型 | 值/范围 | 说明 |
|
||
|----------|------|------|---------|------|
|
||
| `0` | `sync0` | `uint8` | `0xAA` | 帧头第 1 字节 |
|
||
| `1` | `sync1` | `uint8` | `0x55` | 帧头第 2 字节 |
|
||
| `2` | `type` | `uint8` | `0x11` | Spotter 包类型 |
|
||
| `3-6` | `threshold` | `float32 LE` | - | 保护力阈值 (N) |
|
||
| `7` | `enable` | `uint8` | `0` 或 `1` | `0` = 关闭, `1` = 启用 |
|
||
| `8` | `crc` | `uint8` | - | CRC-8/MAXIM 校验 |
|
||
|
||
#### 示例
|
||
|
||
启用 Spotter Mode,阈值 500 N:
|
||
|
||
```text
|
||
AA 55 11 00 00 FA 43 01 XX
|
||
```
|
||
|
||
### 4.3 心跳帧
|
||
|
||
心跳帧固定 5 字节,基站以约 1 Hz 频率发送,用于活性检测。
|
||
|
||
| 字节偏移 | 字段 | 类型 | 值/范围 | 说明 |
|
||
|----------|------|------|---------|------|
|
||
| `0` | `sync0` | `uint8` | `0xAA` | 帧头第 1 字节 |
|
||
| `1` | `sync1` | `uint8` | `0x55` | 帧头第 2 字节 |
|
||
| `2` | `type` | `uint8` | `0x20` | 心跳包类型 |
|
||
| `3` | `counter` | `uint8` | `0-255` | 滚动计数 |
|
||
| `4` | `crc` | `uint8` | - | CRC-8/MAXIM 校验 |
|
||
|
||
#### 示例
|
||
|
||
```text
|
||
AA 55 20 05 XX
|
||
```
|
||
|
||
含义:心跳包,计数 `5`,`XX` 为 CRC。
|
||
|
||
## 5. 标定协议
|
||
|
||
标定协议通过下行标定命令帧(`0x30`)和上行标定响应帧(`0x31`)完成传感器在线标定。标定分两级:L1 单通道零点/增益标定,L2 多点网格空间修正。
|
||
|
||
### 5.1 标定命令帧(下行)
|
||
|
||
标定命令帧固定 10 字节,由基站发送给平衡板:
|
||
|
||
| 字节偏移 | 字段 | 类型 | 值/范围 | 说明 |
|
||
|----------|------|------|---------|------|
|
||
| `0` | `sync0` | `uint8` | `0xAA` | 帧头第 1 字节 |
|
||
| `1` | `sync1` | `uint8` | `0x55` | 帧头第 2 字节 |
|
||
| `2` | `type` | `uint8` | `0x30` | 标定命令包类型 |
|
||
| `3` | `subcmd` | `uint8` | 见下表 | 子命令码 |
|
||
| `4` | `target` | `uint8` | `0-3` / `0-8` | 通道号或网格点号 |
|
||
| `5-8` | `param` | `float32 LE` | - | 参数(如已知质量 kg) |
|
||
| `9` | `crc` | `uint8` | - | CRC-8/MAXIM 校验 |
|
||
|
||
#### `subcmd` 子命令码定义
|
||
|
||
| 子命令码 | 名称 | target 含义 | param 含义 | 说明 |
|
||
|----------|------|-------------|------------|------|
|
||
| `0x01` | `START_L1` | - | - | 进入 L1 标定模式 |
|
||
| `0x02` | `TARE_CH` | 通道号 (0-3) | - | 对指定通道执行零点标定 |
|
||
| `0x03` | `GAIN_CH` | 通道号 (0-3) | 已知质量 (kg) | 对指定通道执行增益标定 |
|
||
| `0x04` | `COMMIT_L1` | - | - | 提交 L1 标定数据到 NVS |
|
||
| `0x05` | `ABORT` | - | - | 中止当前标定流程 |
|
||
| `0x10` | `START_L2` | - | - | 进入 L2 网格标定模式 |
|
||
| `0x11` | `RECORD_GRID` | 网格点号 (0-8) | - | 记录当前网格点 CoP 误差 |
|
||
| `0x12` | `COMMIT_L2` | - | - | 提交 L2 标定数据到 NVS |
|
||
| `0x20` | `ERASE` | - | - | 擦除所有标定数据 |
|
||
| `0x21` | `QUERY` | - | - | 查询当前标定状态 |
|
||
|
||
#### 示例
|
||
|
||
对通道 2 执行增益标定,已知质量 10.0 kg:
|
||
|
||
```text
|
||
AA 55 30 03 02 00 00 20 41 XX
|
||
```
|
||
|
||
- `AA 55 30`:标定命令帧头
|
||
- `03`:subcmd = `GAIN_CH`
|
||
- `02`:target = 通道 2
|
||
- `00 00 20 41`:param = `10.0` kg(float32 LE)
|
||
- `XX`:CRC-8/MAXIM
|
||
|
||
### 5.2 标定响应帧(上行)
|
||
|
||
标定响应帧固定 14 字节,平衡板在执行标定命令后回复:
|
||
|
||
| 字节偏移 | 字段 | 类型 | 值/范围 | 说明 |
|
||
|----------|------|------|---------|------|
|
||
| `0` | `sync0` | `uint8` | `0xAA` | 帧头第 1 字节 |
|
||
| `1` | `sync1` | `uint8` | `0x55` | 帧头第 2 字节 |
|
||
| `2` | `type` | `uint8` | `0x31` | 标定响应包类型 |
|
||
| `3` | `status` | `uint8` | 见下表 | 结果状态码 |
|
||
| `4` | `subcmd` | `uint8` | - | 对应的子命令回显 |
|
||
| `5-12` | `data` | `uint8[8]` | - | 响应数据(上下文相关) |
|
||
| `13` | `crc` | `uint8` | - | CRC-8/MAXIM 校验 |
|
||
|
||
#### 状态码定义
|
||
|
||
| 状态码 | 名称 | 说明 |
|
||
|--------|------|------|
|
||
| `0x00` | `OK` | 命令执行成功 |
|
||
| `0x01` | `ERR_STATE` | 状态机不允许此操作 |
|
||
| `0x02` | `ERR_NVS` | NVS 读写失败 |
|
||
| `0x03` | `ERR_PARAM` | 参数非法 |
|
||
|
||
#### data 字段含义
|
||
|
||
`data[8]` 内容取决于子命令:
|
||
|
||
| 子命令 | data 内容 | 说明 |
|
||
|--------|-----------|------|
|
||
| `TARE_CH` | `data[0..3]` = 零点 ADC 值 (int32 LE) | 通道去皮后的零点偏移 |
|
||
| `GAIN_CH` | `data[0..3]` = 增益 (float32 LE) | kg/count 换算系数 |
|
||
| `QUERY` | `data[0]` = L1 zero valid, `data[1]` = L1 gain valid, `data[2]` = L2 valid | 各级标定有效性标志 |
|
||
| 其他 | 全零 | 无附加数据 |
|
||
|
||
#### 示例
|
||
|
||
通道 2 增益标定成功,增益 = 5.322e-5 kg/count:
|
||
|
||
```text
|
||
AA 55 31 00 03 XX XX XX XX 00 00 00 00 XX
|
||
```
|
||
|
||
- `AA 55 31`:标定响应帧头
|
||
- `00`:status = `OK`
|
||
- `03`:subcmd = `GAIN_CH`(回显)
|
||
- 前 4 字节 data:增益值 float32 LE
|
||
- 后 4 字节 data:填 0
|
||
- 末字节:CRC-8/MAXIM
|
||
|
||
## 6. 当前实现约束
|
||
|
||
- CoP 帧固定 11 字节,可在最小 ATT MTU 23(NUS 有效负载 20 字节)下传输。
|
||
- 50 Hz CoP 上行流隐式充当平衡板到基站的活性检测,因此心跳仅为下行。
|
||
- 传感器通道到物理位置的映射:MUX `{0x01, 0x23, 0x45, 0x67}` → FL/FR/BR/BL。
|
||
- `BOARD_HALF_WIDTH_CM = 10`,`BOARD_HALF_LENGTH_CM = 10`(后续可能调整)。
|
||
- `ADC_TO_FORCE_SCALE` 当前为占位值 `1.0f`,待 10 kg 砝码标定后修正。
|
||
- ARM Cortex-M33 为小端架构,`float32` 在 packed struct 中天然符合 Little Endian 要求。
|