# 存储访问协议 ## 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 中进一步配置解释类型。 - 参数页可在寄存器/变量配置中进一步设置公式、单位、范围和显示类型。