# 存储访问协议

## 1. 协议目标

本协议用于上位机通过蓝牙透传链路按字节访问从机 MCU 的存储区域。协议为单主单从模型，不包含从机地址字段，不属于 Modbus RTU。

小程序中该协议与标准 Modbus、Bootloader 升级协议平级。标准 Modbus 继续使用原有从机地址、功能码和 Modbus CRC；存储访问协议统一使用本文定义的 `CMD + ADDR + LEN + DATA + CRC` 格式，`0x0F info` 也按只读区域处理。

## 2. 基本字节序

除 CRC 算法内部计算外，协议字段均为大端序。

```text
16 位地址: ADDR_H ADDR_L
16 位长度: LEN_H  LEN_L
CRC 输出 : CRC_H  CRC_L
```

CRC 使用 `CRC16-CCITT-FALSE`：

```text
多项式: 0x1021
初值  : 0xFFFF
反转  : 输入不反转，输出不反转
异或  : 0x0000
顺序  : 高字节在前
```

## 3. CMD 定义

每帧第 1 字节为 `CMD`。

```text
bit7      ERR    异常标志
bit6      RW     读写标志
bit5~bit0 AREA   存储区域编号
```

含义如下：

| 位 | 值 | 含义 |
|---|---:|---|
| ERR | 0 | 正常请求或正常响应 |
| ERR | 1 | 异常响应 |
| RW | 0 | 读操作 |
| RW | 1 | 写操作 |

命令生成规则：

```text
读命令 CMD = AREA
写命令 CMD = 0x40 | AREA
异常 CMD_ERR = CMD | 0x80
```

## 4. AREA 定义

| AREA | 名称 | 读 | 写 | 用途 |
|---:|---|---|---|---|
| `0x01` | data | 支持 | 支持 | 内部直接寻址 RAM 区 |
| `0x02` | idata | 支持 | 支持 | 内部间接寻址 RAM 区 |
| `0x03` | xdata | 支持 | 支持 | 外部数据空间或扩展 RAM |
| `0x04` | code | 支持 | 禁止 | 程序存储区 |
| `0x0F` | info | 支持 | 禁止 | 连接后同步 code 区关键数据地址与长度 |

`0x0F info` 是一个只读信息区域，不直接承载完整 `Modbus_Code_Info_t`。小程序连接设备后先通过 `0x0F` 按地址和长度读取 4 字节关键数据，再使用 `AREA=0x04 code` 读取真正的 `Modbus_Code_Info_t`。

## 5. 帧格式

### 5.0 info 同步请求/响应

`info` 同步请求与普通读请求一致，地址和长度单位均为字节。当前定义固定读取 `ADDR=0x0000`、`LEN=0x0004`：

```text
0F 00 00 00 04 CRC_H CRC_L
```

回复与普通读响应一致，仍包含本次读取的 `ADDR` 与 `LEN`，随后 4 字节数据为 code 区内信息块的地址和长度：

```text
0F 00 00 00 04 CODE_ADDR_H CODE_ADDR_L CODE_LEN_H CODE_LEN_L CRC_H CRC_L
```

响应中的前两个字节地址 `00 00` 和长度 `00 04` 是本次 `info` 区读取的地址与长度。`CODE_ADDR` 和 `CODE_LEN` 是返回数据，表示 code 区内 `Modbus_Code_Info_t` 的字节地址和字节长度。

### 5.1 读请求

```text
CMD ADDR_H ADDR_L LEN_H LEN_L CRC_H CRC_L
```

长度固定 7 字节。

### 5.2 写请求

```text
CMD ADDR_H ADDR_L LEN_H LEN_L DATA... CRC_H CRC_L
```

长度为 `7 + LEN` 字节。

### 5.3 正常读响应

```text
CMD ADDR_H ADDR_L LEN_H LEN_L DATA... CRC_H CRC_L
```

长度为 `7 + LEN` 字节。

### 5.4 正常写响应

```text
CMD ADDR_H ADDR_L LEN_H LEN_L CRC_H CRC_L
```

长度固定 7 字节。

### 5.5 异常响应

```text
CMD_ERR EXCEPTION_CODE CRC_H CRC_L
```

长度固定 4 字节。

## 6. 异常码

| 异常码 | 名称 | 说明 |
|---:|---|---|
| `0x01` | ILLEGAL_COMMAND | 非法命令 |
| `0x02` | ILLEGAL_AREA | 非法区域 |
| `0x03` | ILLEGAL_ADDRESS | 非法地址 |
| `0x04` | ILLEGAL_LENGTH | 非法长度 |
| `0x05` | WRITE_PROTECT | 写保护 |
| `0x06` | DEVICE_BUSY | 设备忙 |
| `0x07` | FORMAT_ERROR | 格式错误 |
| `0x08` | ACCESS_DENIED | 访问拒绝 |
| `0x09` | INTERNAL_ERROR | 内部错误 |
| `0x0A` | ALIGNMENT_ERROR | 对齐错误 |
| `0x0B` | RANGE_OVERFLOW | 地址范围溢出 |
| `0x0C` | UNSUPPORTED_OPERATION | 不支持的操作 |

## 7. info 同步流程

连接后同步 code 区关键数据时，上位机先读取 `AREA=0x0F info` 的只读 4 字节数据，再访问 `AREA=0x04 code` 读取真实数据。

### 7.1 读取 info 区关键数据

读请求：

```text
0F 00 00 00 04 CRC_H CRC_L
```

从机返回：

```text
0F 00 00 00 04 CODE_ADDR_H CODE_ADDR_L CODE_LEN_H CODE_LEN_L CRC_H CRC_L
```

返回数据字段含义：

| 字段 | 长度 | 说明 |
|---|---:|---|
| CODE_ADDR | 2 字节 | `Modbus_Code_Info_t` 在 code 区内的字节地址 |
| CODE_LEN | 2 字节 | `Modbus_Code_Info_t` 的字节长度 |

### 7.2 使用 code 区读取完整信息块

上位机根据 `info` 返回数据继续读取：

```text
AREA = 0x04
ADDR = CODE_ADDR
LEN  = CODE_LEN
```

如果 `CODE_LEN` 超过当前链路最大包长，小程序按字节地址自动分片。例如最大帧长为 64 字节时，单帧读响应可承载的数据长度为：

```text
64 - 7 = 57 字节
```

小程序会依次读取：

```text
CODE_ADDR + 0x0000 ~ CODE_ADDR + 0x0038
CODE_ADDR + 0x0039 ~ CODE_ADDR + 0x0071
...
```

每个分片均使用 `AREA=0x04 code`，地址为 code 区内真实字节地址。

## 8. Modbus_Code_Info_t 布局

`Modbus_Code_Info_t` 位于 `info` 返回数据给出的 code 区地址，布局如下：

```c
typedef struct
{
    uint16_t byte_len;
    uint8_t cave_freq;
    uint8_t ref_volt;
    uint16_t amp_gain;
    uint16_t rs_shunt;
    float bus_div;
    float along_div;
    uint8_t chip_model[8];
    uint8_t model[16];

    uint16_t struct_count;
    uint16_t struct_entry_len;
    struct
    {
        uint16_t byte_addr;
        uint16_t byte_len;
        uint8_t mem_type;
        uint8_t type_name[MODBUS_CODE_INFO_STRUCT_NAME_BYTE_LEN];
    } struct_table[MODBUS_CODE_INFO_STRUCT_COUNT];
} Modbus_Code_Info_t;
```

固定头长度为 44 字节：

| 偏移 | 字段 | 长度 |
|---:|---|---:|
| 0 | byte_len | 2 |
| 2 | cave_freq | 1 |
| 3 | ref_volt | 1 |
| 4 | amp_gain | 2 |
| 6 | rs_shunt | 2 |
| 8 | bus_div | 4 |
| 12 | along_div | 4 |
| 16 | chip_model | 8 |
| 24 | model | 16 |
| 40 | struct_count | 2 |
| 42 | struct_entry_len | 2 |
| 44 | struct_table | `struct_count * struct_entry_len` |

`struct_table` 中 `mem_type` 使用同一套 AREA 编号：

| mem_type | 结构体实例所在区域 |
|---:|---|
| `0x01` | data |
| `0x02` | idata |
| `0x03` | xdata |
| `0x04` | code |

`struct_table` 不应把结构体实例标为 `0x0F`，因为 `0x0F info` 只用于同步 code 区信息块地址和长度，不作为普通变量读写区域。

## 9. 小程序同步后的处理

小程序读取完整 `Modbus_Code_Info_t` 后执行以下步骤：

1. 解析硬件固定信息。
2. 解析 `struct_count` 和 `struct_entry_len`。
3. 遍历 `struct_table`。
4. 根据每一项的 `mem_type`、`byte_addr`、`byte_len`、`type_name` 创建结构体组。
5. 后续读取结构体实例时，改用对应的真实区域：
   - `0x01 data`
   - `0x02 idata`
   - `0x03 xdata`
   - `0x04 code`
6. 如果导入了 C 结构体定义，小程序会根据 `type_name` 自动补全字段、位域、数组和数据类型。

示例：

```text
struct_table[0]:
  byte_addr = 0x2000
  byte_len  = 64
  mem_type  = 0x03
  type_name = motor_runtime_t
```

则后续读取该结构体实例时发送：

```text
03 20 00 00 40 CRC_H CRC_L
```

含义为读取 `xdata` 区 `0x2000` 开始的 64 字节。

## 10. 实现约束

1. `code` 和 `info` 区默认只读。
2. `LEN` 单位始终为字节，不是 Modbus 寄存器。
3. 上位机必须校验响应 CMD、ADDR、LEN 和 CRC。
4. 异常响应固定 4 字节。
5. `0x0F info` 只用于连接同步信息块地址和长度，请求/回复均按普通只读帧处理，不作为普通变量读写区域。
6. 如果固件更新了 `Modbus_Code_Info_t` 布局，应同步更新本文档和小程序解析器。
