# 存储访问协议

## 1. 协议定位

存储访问协议用于上位机通过蓝牙透传链路按字节访问从机 MCU 的存储区域。

- 存储访问协议使用 `CMD + ADDR + LEN + DATA + CRC16-CCITT-FALSE`。
- 复位使用 `bit6=1` 的特殊指令帧；当前仅定义复位特殊指令。
- CodeInfo 同步先读取普通区域 `area=0x00` 的 codeinfo 描述符，再按描述符地址位宽读取 TLV 信息块。

## 2. 字节序与 CRC

CRC 输出固定为低字节在前。除 CRC 外，协议中的地址、长度和多字节数据字段都按 CodeInfo 描述符里的 `ENDIAN_MARK` 自动选择大端或小端；未同步或未声明时按设置页“默认大端模式”开关处理。

```text
ENDIAN_MARK = 55 AA: 32 位地址 ADDR_3 ADDR_2 ADDR_1 ADDR_0，16 位长度 LEN_H LEN_L
ENDIAN_MARK = AA 55: 32 位地址 ADDR_0 ADDR_1 ADDR_2 ADDR_3，16 位长度 LEN_L LEN_H
CRC 输出固定        : CRC_L CRC_H
```

CRC 使用 `CRC16-CCITT-FALSE`：

| 参数 | 值 |
|---|---|
| 多项式 | `0x1021` |
| 初值 | `0xFFFF` |
| 输入反转 | 否 |
| 输出反转 | 否 |
| 结果异或 | `0x0000` |
| 输出顺序 | 低字节在前 |

CRC 覆盖除最后两个 CRC 字节外的整帧内容。

## 3. CMD 定义

每帧第 1 字节为 `CMD`。

```text
bit7      ERR       故障标志，仅回帧出现
bit6      SPECIAL   特殊指令标志位
bit5~bit4 RSV       保留，普通读写保持 0
bit3      RW        普通读写标志，0=读，1=写
bit2~bit0 AREA      普通读写区域码
```

| 位 | 值 | 含义 |
|---|---:|---|
| ERR | 0 | 请求或正常响应 |
| ERR | 1 | 故障响应 |
| SPECIAL | 0 | 普通存储访问 |
| SPECIAL | 1 | 特殊指令，`bit0~bit5` 表示特殊指令码 |
| RSV | 0 | 当前版本固定为 0 |
| RW | 0 | 读操作 |
| RW | 1 | 写操作 |

普通内存命令生成规则：

```text
读请求   : CMD = AREA
写请求   : CMD = 0x08 | AREA
异常响应 : CMD_ERR = CMD | 0x80
```

特殊指令生成规则：

```text
特殊请求 : CMD = 0x40 | SPECIAL_CODE
异常响应 : CMD_ERR = CMD | 0x80
```

当前仅定义 `SPECIAL_CODE=0x01` 表示复位，因此复位请求 `CMD=0x41`，复位异常响应 `CMD=0xC1`。

## 4. AREA 定义

| AREA | 名称 | 读 | 写 | 用途 |
|---:|---|---|---|---|
| `0x00` | CODEINFO | 支持 | 禁止 | 同步时读取一次，返回 TLV 起始地址与长度 |
| `0x01` | DATA | 支持 | 支持 | 内部直接寻址 RAM 区 |
| `0x02` | IDATA | 支持 | 支持 | 内部间接寻址 RAM 区 |
| `0x03` | XDATA | 支持 | 支持 | 外部数据空间或扩展 RAM |
| `0x04` | CODE | 支持 | 禁止 | 程序存储区，16 位地址 |
| `0x05..0x06` | 保留 | 禁止 | 禁止 | 保留 |
| `0x07` | ADDR32 | 支持 | 支持 | 32 位统一地址模式 |

写 `CODEINFO` 或 `CODE` 区必须返回写保护异常。

## 5. 普通内存访问帧

### 5.1 读请求

```text
AREA=0x07: CMD ADDR_3 ADDR_2 ADDR_1 ADDR_0 LEN_H LEN_L CRC_L CRC_H
AREA=0x01..0x04: CMD ADDR_H ADDR_L LEN_H LEN_L CRC_L CRC_H
```

上方帧格式以 `ENDIAN_MARK=55 AA` 的大端设备为例；如果 `ENDIAN_MARK=AA 55`，`ADDR` 和 `LEN` 字节顺序反转，CRC 仍为 `CRC_L CRC_H`。

长度分别为 9 字节或 7 字节。

### 5.2 写请求

```text
AREA=0x07: CMD ADDR_3 ADDR_2 ADDR_1 ADDR_0 LEN_H LEN_L DATA... CRC_L CRC_H
AREA=0x01..0x04: CMD ADDR_H ADDR_L LEN_H LEN_L DATA... CRC_L CRC_H
```

长度分别为 `9 + LEN` 或 `7 + LEN` 字节。

### 5.3 正常读响应

```text
AREA=0x07: CMD ADDR_3 ADDR_2 ADDR_1 ADDR_0 LEN_H LEN_L DATA... CRC_L CRC_H
AREA=0x01..0x04: CMD ADDR_H ADDR_L LEN_H LEN_L DATA... CRC_L CRC_H
```

长度分别为 `9 + LEN` 或 `7 + LEN` 字节。

响应必须回显请求中的 `CMD`、`ADDR`、`LEN`。

### 5.4 正常写响应

```text
AREA=0x07: CMD ADDR_3 ADDR_2 ADDR_1 ADDR_0 LEN_H LEN_L CRC_L CRC_H
AREA=0x01..0x04: CMD ADDR_H ADDR_L LEN_H LEN_L CRC_L CRC_H
```

长度分别为 9 字节或 7 字节。

响应必须回显请求中的 `CMD`、`ADDR`、`LEN`。

### 5.5 异常响应

```text
CMD_ERR EXCEPTION_CODE CRC_L CRC_H
```

长度固定 4 字节。`CMD_ERR` 为原请求 `CMD | 0x80`。

## 6. 特殊指令帧

特殊指令使用 `CMD bit6=1`，`bit0~bit5` 表示特殊指令码。当前仅定义 `0x01` 复位。

```text
请求: CMD DATA... CRC_L CRC_H
响应: CMD DATA... CRC_L CRC_H
异常: CMD_ERR EXCEPTION_CODE CRC_L CRC_H
```

特殊指令执行失败时直接返回异常帧，成功响应不额外携带状态字节。

| 指令码 | CMD | 名称 | 请求 DATA | 成功响应 DATA | 说明 |
|---:|---:|---|---|---|---|
| `0x01` | `0x41` | RESET | 无 | 无 | 复位 |

## 7. 异常码

| 异常码 | 名称 | 说明 |
|---:|---|---|
| `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 | 不支持的操作 |

## 8. CodeInfo 同步流程

CodeInfo 同步分两步：

1. 读取普通区域 `area=0x00 CODEINFO` 的描述符，响应 DATA 为 11 字节。
2. 按描述符返回的 TLV 起始地址、`len16`、地址位宽和最大包长，读取完整 CodeInfo TLV 信息块。

### 8.1 读取 CODEINFO 描述符

请求：

```text
00 CRC_L CRC_H
```

从机成功响应：

```text
00 TLV_ADDR(4B) TLV_LEN(2B) ADDR_WIDTH MAX_PACKET(2B) ENDIAN_MARK_0 ENDIAN_MARK_1 CRC_L CRC_H
```

响应 DATA 字段：

| 字段 | 长度 | 说明 |
|---|---:|---|
| `TLV_ADDR` | 4 | CodeInfo TLV 信息块起始字节地址；`ADDR_WIDTH=16` 时读取帧只携带低 16 位 |
| `TLV_LEN` | 2 | CodeInfo TLV 信息块的字节长度 |
| `ADDR_WIDTH` | 1 | 读取 CodeInfo 信息块本体时使用的地址位宽，只允许 `16` 或 `32` |
| `MAX_PACKET` | 2 | 从机允许的最大完整协议帧长度，包含 CMD/ADDR/LEN/DATA/CRC；为 0 表示未声明 |
| `ENDIAN_MARK` | 2 | 目标内存值字节序标记；按字节读取为 `55 AA` 表示大端，`AA 55` 表示小端 |

描述符区域 `CODEINFO` 只读，不支持写入。`ENDIAN_MARK` 按原始两字节同步标记读取：`55 AA` 表示大端，`AA 55` 表示小端。上位机先识别该标记，再按标记解析描述符里的 `TLV_ADDR/TLV_LEN/MAX_PACKET`，以及后续普通读写帧的 `ADDR/LEN`、CodeInfo 内存入口中的 `byte_addr/byte_len/elem_byte_len/elem_count/dim_len` 和目标内存多字节数据。TLV `TYPE`、`LEN`、`area`、`name_len`、`def_name_len`、`var_name_len`、`dim_count` 为单字节字段，不涉及大小端。

### 8.2 读取完整 CodeInfo

上位机继续发送普通读请求：

```text
ADDR_WIDTH = 32: MODE = 0x07 ADDR32
ADDR_WIDTH = 16: MODE = 0x04 CODE
ADDR = TLV_ADDR
LEN  = TLV_LEN
```

如果 `TLV_LEN` 超过当前 BLE 包长可承载的数据长度，小程序按协议自动分片读取。

最大数据长度计算：

```text
ADDR_WIDTH=32: 读响应单帧数据上限 = max_frame_bytes - 9
ADDR_WIDTH=16: 读响应单帧数据上限 = max_frame_bytes - 7
```

## 9. CodeInfo TLV 信息块

CodeInfo TLV 信息块位于 `CODEINFO` 描述符给出的地址。字段是否存在由 TLV `TYPE` 决定，信息块总长度由描述符返回的 `TLV_LEN` 决定。

TLV 项连续排列，直到累计长度等于 `TLV_LEN`：

```text
TYPE LEN VALUE...
```

| 字段 | 长度 | 说明 |
|---|---:|---|
| `TYPE` | 1 | TLV 类型 |
| `LEN` | 1 | `VALUE` 字节长度，单项最大 255 字节 |
| `VALUE` | `LEN` | 类型对应的数据 |

上位机解析到未知 `TYPE` 时跳过该项。任何 TLV 项声明长度超过 `TLV_LEN` 剩余字节时，整段 CodeInfo 视为格式错误。`TLV_LEN` 由描述符使用 16 位字段给出，因此整段 CodeInfo 可以超过 255 字节，只是单个 TLV 项不能超过 255 字节。

### 9.1 内存入口 TLV

CodeInfo 内存入口的地址宽度由 CODEINFO 描述符中的 `ADDR_WIDTH` 决定，不再由 TLV `TYPE` 决定。

当 `ADDR_WIDTH=16` 时，所有内存入口 `VALUE` 以前缀 `area8 + byte_addr16 + byte_len16` 开始：

| 字段 | 长度 | 说明 |
|---|---:|---|
| `area` | 1 | 目标存储区域：`0x01 DATA`、`0x02 IDATA`、`0x03 XDATA`、`0x04 CODE` |
| `byte_addr` | 2 | 区域内字节地址，按 `ENDIAN_MARK` 解析 |
| `byte_len` | 2 | 变量、结构体或数组总字节数，按 `ENDIAN_MARK` 解析 |

当 `ADDR_WIDTH=32` 时，所有内存入口 `VALUE` 以前缀 `byte_addr32 + byte_len16` 开始，目标区域固定为 `ADDR32`，不再携带 `area` 字段。

| 字段 | 长度 | 说明 |
|---|---:|---|
| `byte_addr` | 4 | 统一 32 位字节地址，按 `ENDIAN_MARK` 解析 |
| `byte_len` | 2 | 变量、结构体或数组总字节数，按 `ENDIAN_MARK` 解析 |

下面用 `ENTRY_PREFIX` 表示上述二选一前缀。16 位地址前缀长度为 5 字节，32 位地址前缀长度为 6 字节。

TYPE 直接定义入口形态和基础数据类型，不再额外携带元素类型字段。未在下表定义的 TYPE 均可由项目自定义；通用上位机解析器只保留原始 TLV 项并跳过业务解析。

基础变量：

| TYPE | 名称 | 字节数 | VALUE 格式 |
|---:|---|---:|---|
| `0x01` | raw 变量 | `byte_len` 决定 | `ENTRY_PREFIX + var_name_len8 + var_name` |
| `0x02` | int8_t 变量 | 1 | `ENTRY_PREFIX + var_name_len8 + var_name` |
| `0x03` | uint8_t 变量 | 1 | `ENTRY_PREFIX + var_name_len8 + var_name` |
| `0x04` | int16_t 变量 | 2 | `ENTRY_PREFIX + var_name_len8 + var_name` |
| `0x05` | uint16_t 变量 | 2 | `ENTRY_PREFIX + var_name_len8 + var_name` |
| `0x06` | int32_t 变量 | 4 | `ENTRY_PREFIX + var_name_len8 + var_name` |
| `0x07` | uint32_t 变量 | 4 | `ENTRY_PREFIX + var_name_len8 + var_name` |
| `0x08` | float32 变量 | 4 | `ENTRY_PREFIX + var_name_len8 + var_name` |
| `0x09` | double 变量 | 8 | `ENTRY_PREFIX + var_name_len8 + var_name` |
| `0x0A` | int64_t 变量 | 8 | `ENTRY_PREFIX + var_name_len8 + var_name` |
| `0x0B` | uint64_t 变量 | 8 | `ENTRY_PREFIX + var_name_len8 + var_name` |
| `0x0C` | int128_t 变量 | 16 | `ENTRY_PREFIX + var_name_len8 + var_name` |
| `0x0D` | uint128_t 变量 | 16 | `ENTRY_PREFIX + var_name_len8 + var_name` |
| `0x0E` | int256_t 变量 | 32 | `ENTRY_PREFIX + var_name_len8 + var_name` |
| `0x0F` | uint256_t 变量 | 32 | `ENTRY_PREFIX + var_name_len8 + var_name` |

数组变量：

```text
ARRAY_VALUE =
ENTRY_PREFIX
elem_byte_len16
elem_count16
dim_count8
dim_len16[dim_count]
var_name_len8
var_name
```

| TYPE | 名称 | 元素字节数 | 说明 |
|---:|---|---:|---|
| `0x10` | raw 数组 | `elem_byte_len` 决定 | 原始字节数组 |
| `0x11` | int8_t 数组 | 1 | 有符号 8 位整数数组 |
| `0x12` | uint8_t 数组 | 1 | 无符号 8 位整数数组 |
| `0x13` | int16_t 数组 | 2 | 有符号 16 位整数数组 |
| `0x14` | uint16_t 数组 | 2 | 无符号 16 位整数数组 |
| `0x15` | int32_t 数组 | 4 | 有符号 32 位整数数组 |
| `0x16` | uint32_t 数组 | 4 | 无符号 32 位整数数组 |
| `0x17` | float32 数组 | 4 | IEEE-754 float 数组 |
| `0x18` | double 数组 | 8 | IEEE-754 double 数组 |
| `0x19` | int64_t 数组 | 8 | 有符号 64 位整数数组 |
| `0x1A` | uint64_t 数组 | 8 | 无符号 64 位整数数组 |
| `0x1B` | int128_t 数组 | 16 | 有符号 128 位整数数组 |
| `0x1C` | uint128_t 数组 | 16 | 无符号 128 位整数数组 |
| `0x1D` | int256_t 数组 | 32 | 有符号 256 位整数数组 |
| `0x1E` | uint256_t 数组 | 32 | 无符号 256 位整数数组 |
| `0x1F` | 文本数组 | 1 | 文本字节数组，上位机按 UTF-8、GBK、ASCII 顺序自动解析 |

enum 与结构体入口：

| TYPE | 名称 | VALUE 格式 | 小程序处理 |
|---:|---|---|---|
| `0x20` | enum 变量 | `ENTRY_PREFIX + def_name_len8 + def_name + var_name_len8 + var_name` | 创建 enum 变量组，按 `def_name` 匹配 `typedef enum` |
| `0x21` | 结构体实例 | `ENTRY_PREFIX + def_name_len8 + def_name + var_name_len8 + var_name` | 创建结构体组，按 `def_name` 匹配 `typedef struct` |
| `0x22` | enum 数组 | `ENTRY_PREFIX + elem_byte_len16 + elem_count16 + dim_count8 + dim_len16[dim_count] + def_name_len8 + def_name + var_name_len8 + var_name` | 按元素展开，并按 `def_name` 匹配 `typedef enum` |
| `0x23` | 结构体数组 | `ENTRY_PREFIX + elem_byte_len16 + elem_count16 + dim_count8 + dim_len16[dim_count] + def_name_len8 + def_name + var_name_len8 + var_name` | 导入结构体定义后按 `var[i].field` 或 `var[i][j].field` 展开 |

基础变量 TLV 的 `LEN`：

```text
ADDR_WIDTH=16: LEN = 0x06 + var_name_len
ADDR_WIDTH=32: LEN = 0x07 + var_name_len
```

enum 变量 TLV 的 `LEN`：

```text
ADDR_WIDTH=16: LEN = 0x07 + def_name_len + var_name_len
ADDR_WIDTH=32: LEN = 0x08 + def_name_len + var_name_len
```

结构体实例 TLV 的 `LEN`：

```text
ADDR_WIDTH=16: LEN = 0x07 + def_name_len + var_name_len
ADDR_WIDTH=32: LEN = 0x08 + def_name_len + var_name_len
```

数组变量 TLV 的 `LEN`：

```text
基础数组:
ADDR_WIDTH=16: LEN = 0x0B + 2 * dim_count + var_name_len
ADDR_WIDTH=32: LEN = 0x0C + 2 * dim_count + var_name_len

enum/结构体数组:
ADDR_WIDTH=16: LEN = 0x0C + 2 * dim_count + def_name_len + var_name_len
ADDR_WIDTH=32: LEN = 0x0D + 2 * dim_count + def_name_len + var_name_len
```

数组入口约束：

- `byte_len = elem_byte_len * elem_count`。
- `dim_len[0] * dim_len[1] * ... * dim_len[dim_count - 1] = elem_count`。
- 基础数组和文本数组不携带 `def_name`。
- enum 数组必须携带 enum typedef 定义名。
- 结构体数组必须携带 struct typedef 定义名。
- 多维数组按 C 语言连续存储顺序展开，即最后一维变化最快。
- 文本变量除 ASCII 单字符外都应使用文本数组；文本数组的 `elem_byte_len` 固定为 1，`byte_len` 为整个文本缓冲区字节数。

多维数组寻址规则：

```text
2D: linear_index = i * dim_len[1] + j
3D: linear_index = i * dim_len[1] * dim_len[2] + j * dim_len[2] + k
通用: linear_index = Σ index[n] * Π dim_len[n+1..dim_count-1]
element_addr = byte_addr + linear_index * elem_byte_len
struct_field_addr = byte_addr + linear_index * elem_byte_len + field_byte_offset
```

### 9.2 板卡信息 TLV

板卡信息从 `0x40` 开始递增，全部为可选项。某块板不存在对应硬件信息时，不需要放该 TLV；小程序卡片和转换公式只使用实际同步到的字段。

| TYPE | 名称 | LEN | VALUE 格式 | 显示/用途 |
|---:|---|---:|---|---|
| `0x40` | `cave_freq` | 1 | `uint8_t` | 载波频率，单位 KHz |
| `0x41` | `ref_volt` | 1 | `uint8_t` | 基准电压实际值乘 10，显示时除以 10，单位 V |
| `0x42` | `amp_gain` | 1 | `uint8_t` | 运放倍数，无单位 |
| `0x43` | `rs_shunt` | 2 | `uint16_t` | 采样电阻，单位 mΩ，按 `ENDIAN_MARK` 解析 |
| `0x44` | `bus_div` | 4 | `float32` | 母线电压分压比，按 `ENDIAN_MARK` 解析 |
| `0x45` | `along_div` | 4 | `float32` | 模拟输入电压分压比，按 `ENDIAN_MARK` 解析 |
| `0x46` | `chip_model` | 可变 | 文本字节串 | 芯片型号，建议 0 结尾或 0 填充；上位机按 UTF-8、GBK、ASCII 顺序自动解析 |
| `0x47` | `model` | 可变 | 文本字节串 | 电机型号，建议最多 30 字节；上位机按 UTF-8、GBK、ASCII 顺序自动解析 |

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

小程序读取完整 CodeInfo TLV 信息块后：

1. 按 `TYPE LEN VALUE` 遍历 TLV 项，未知类型跳过。
2. 解析 `0x40` 起的可选板卡信息，更新通讯页 CodeInfo 卡片；不存在的字段不显示、不进入转换公式上下文。
3. 先根据 CODEINFO 描述符 `ADDR_WIDTH` 确定内存入口地址格式：16 位入口解析 `area8 + byte_addr16`，32 位入口解析 `byte_addr32` 且区域视为 `ADDR32`。
4. 遇到 `0x01..0x0F` 基础变量入口创建单变量组，按 TYPE 确定显示和写入类型；raw 类型只显示原始字节。
5. 遇到 `0x10..0x1F` 基础数组入口创建数组组；普通数值数组按元素展开，文本数组按整段文本字段显示。
6. 遇到 `TYPE=0x20` 创建 enum 变量组，按 `def_name` 匹配 `typedef enum`，读回值按枚举项显示。
7. 遇到 `TYPE=0x21` 创建结构体组；结构体定义未导入时按字节占位，导入后按 `def_name` 与结构体定义名匹配并补全字段。
8. 遇到 `TYPE=0x22` 创建 enum 数组组，按元素展开并按 `def_name` 匹配 enum。
9. 遇到 `TYPE=0x23` 创建结构体数组组，导入 struct 后展开为 `var[i].field`、`var[i][j].field` 等。
10. 根据描述符 `ENDIAN_MARK` 自动确定普通帧 `ADDR/LEN`、CodeInfo 内存入口多字节字段以及目标内存多字节值字节序：`55 AA` 为大端，`AA 55` 为小端；单字节值不受影响，CRC 始终低字节在前。
11. 数组中的多字节元素按元素逐个应用 `ENDIAN_MARK`，不对整段数组字节整体反转。
12. 如果当前已有同地址、同区域、同名称、同长度的结构体组，并且已经导入过结构体定义，则保留当前导入结构，只更新来源地址、区域、轮询开关等状态。
13. 如果存在相同变量名但区域或 `byte_addr` 不同的入口，应视为不同参数组。

## 11. 示例

### 11.1 读取 XDATA 中 64 字节结构体

以下示例使用 `ENDIAN_MARK=55 AA` 的大端设备；小端设备的 `ADDR/LEN` 字节顺序相反，CRC 仍固定低字节在前。

```text
AREA = 0x03 XDATA
ADDR = 0x2000
LEN = 0x0040
读请求 = 03 20 00 00 40 CRC_L CRC_H
正常响应 = 03 20 00 00 40 DATA(64B) CRC_L CRC_H
```

### 11.2 读取 CodeInfo

假设描述符返回：

```text
TLV_ADDR = 0x00123456
TLV_LEN  = 0x0120
ADDR_WIDTH = 32
MAX_PACKET = 0x0040
ENDIAN_MARK = 55 AA
```

先读取 `CODEINFO` 描述符：

```text
00 CRC_L CRC_H
```

再按 `ADDR_WIDTH=32` 读取 TLV 信息块：

```text
07 00 12 34 56 01 20 CRC_L CRC_H
```

`0x07` 表示 32 位统一地址模式。

### 11.3 CodeInfo TLV 示例

16 位 XDATA 结构体实例：

```text
TYPE = 0x21
LEN = 0x23
VALUE = 03 20 00 00 40 0F 4D 6F 74 6F 72 5F 52 75 6E 74 69 6D 65 5F 74 0D 6D 6F 74 6F 72 5F 72 75 6E 74 69 6D 65
含义 = XDATA 0x2000 / 64 bytes / typedef Motor_Runtime_t / 变量 motor_runtime
```

16 位 XDATA enum 变量：

```text
TYPE = 0x20
LEN = 0x18
VALUE = 03 21 00 00 02 09 52 75 6E 4D 6F 64 65 5F 74 08 72 75 6E 5F 6D 6F 64 65
含义 = XDATA 0x2100 / 2 bytes / typedef RunMode_t / 变量 run_mode
```

32 位 `uint16_t speed_ref` 变量：

```text
TYPE = 0x05
LEN = 0x10
VALUE = 00 00 21 00 00 02 09 73 70 65 65 64 5F 72 65 66
含义 = ADDR32 0x00002100 / 2 bytes / uint16_t speed_ref
```

16 位 XDATA `uint16_t adc_table[2][3]`：

```text
TYPE = 0x14
LEN = 0x18
VALUE = 03 21 00 00 0C 00 02 00 06 02 00 02 00 03 09 61 64 63 5F 74 61 62 6C 65
含义 = XDATA 0x2100 / 12 bytes / uint16_t adc_table[2][3]
展开 = adc_table[0][0], adc_table[0][1], adc_table[0][2], adc_table[1][0], adc_table[1][1], adc_table[1][2]
```

16 位 XDATA `Motor_t motors[2][3]`：

```text
TYPE = 0x23
LEN = 0x1D
VALUE = 03 22 00 00 30 00 08 00 06 02 00 02 00 03 07 4D 6F 74 6F 72 5F 74 06 6D 6F 74 6F 72 73
含义 = XDATA 0x2200 / 48 bytes / typedef Motor_t / 变量 motors[2][3] / sizeof(Motor_t)=8
导入结构体后展开 = motors[0][0].field, motors[0][1].field, ... motors[1][2].field
```

16 位 XDATA 文本数组 `char model[7]`：

```text
TYPE = 0x1F
LEN = 0x12
VALUE = 03 23 00 00 07 00 01 00 07 01 00 07 05 6D 6F 64 65 6C
含义 = XDATA 0x2300 / 7 bytes / 文本缓冲区 model；上位机按 UTF-8、GBK、ASCII 顺序解析读回字节
```

## 12. 实现约束

1. 普通内存读写帧的 `LEN` 单位始终为字节，字段固定 16 位，有效范围为 `1..0xFFFF`；CodeInfo TLV 项内的 `LEN` 为 1 字节，只表示单项 `VALUE` 长度。
2. `ADDR` 单位始终为字节；`AREA=0x07` 时地址字段为 32 位，`AREA=0x01..0x04` 时地址字段为 16 位；`AREA=0x00` 为 CODEINFO 短描述符请求；`AREA=0x05..0x06` 保留。
3. 普通读写响应必须回显请求的 `CMD`、`ADDR`、`LEN`。
4. 上位机必须校验响应 `CMD`、`ADDR`、`LEN`、CRC，并确认数据长度等于 `LEN`。
5. `CODEINFO` 和 `CODE` 区只读，写入必须返回 `WRITE_PROTECT`。
6. 特殊指令正常响应不携带 `STATUS`，执行失败必须返回异常帧。
7. CodeInfo 同步必须先读 `area=0x00 CODEINFO` 描述符，再按描述符返回的 `TLV_ADDR/TLV_LEN/ADDR_WIDTH/MAX_PACKET` 读取信息块；`ADDR_WIDTH=16` 使用 `area=0x04 CODE`，`ADDR_WIDTH=32` 使用 `area=0x07 ADDR32`，内存入口地址宽度同样由描述符 `ADDR_WIDTH` 决定。
8. 固件更新 CodeInfo TLV 类型或内存入口格式时，必须同步更新本文档和小程序解析器。

## 13. 读写位宽说明

普通内存读写协议仍只传输“字节地址 + 字节长度 + 字节数据”。变量显示、编辑位宽和数组展开由 CodeInfo TLV 元数据决定：

- `TYPE=0x01..0x0F` 的基础变量由 TYPE 和 `byte_len` 决定显示/编辑类型。
- `TYPE=0x10..0x1F` 的基础数组由 TYPE、`elem_byte_len`、`elem_count` 和 `dim_len[]` 决定元素展开方式；文本数组按整段文本缓冲区显示。
- `TYPE=0x20` 的 enum 变量按 `def_name` 匹配导入的 C enum 定义，列表右侧显示十进制值，左侧显示原始十六进制和匹配到的定义/枚举项。
- `TYPE=0x21` 的结构体实例由导入的 C 结构体定义决定字段类型；未导入前按字节占位。
- `TYPE=0x22` 的 enum 数组按元素展开并匹配 enum 定义。
- `TYPE=0x23` 的结构体数组导入 C 定义后按 `var[i].field`、`var[i][j].field` 等形式展开。
- raw 类型只显示原始字节，写入前需要在 UI 中进一步配置解释类型。
- 参数页可在寄存器/变量配置中进一步设置公式、单位、范围和显示类型。
