存储访问协议.md 16 KB

存储访问协议

1. 协议定位

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

小程序中该协议与标准 Modbus RTU、Bootloader 升级协议平级:

  • 标准 Modbus RTU 使用从机地址、功能码和 Modbus CRC。
  • 存储访问协议使用 CMD + ADDR + LEN + DATA + CRC16-CCITT-FALSE
  • 复位使用 bit6=1 的特殊指令帧;当前仅定义复位特殊指令。
  • CodeInfo 同步先读取普通区域 area=0x00 的 codeinfo 描述符,再按描述符地址位宽读取 TLV 信息块。

2. 字节序与 CRC

除 CRC 算法内部计算外,所有多字节协议控制字段均为小端序。目标内存变量的多字节值在 CodeInfo 同步后按 ENDIAN_MARK 自动选择大端或小端;未同步或未声明时默认小端。

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

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 写操作

普通内存命令生成规则:

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

特殊指令生成规则:

特殊请求 : 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 位统一地址模式

CODEINFOCODE 区必须返回写保护异常。

5. 普通内存访问帧

5.1 读请求

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

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

5.2 写请求

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

长度分别为 9 + LEN7 + LEN 字节。

5.3 正常读响应

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

长度分别为 9 + LEN7 + LEN 字节。

响应必须回显请求中的 CMDADDRLEN

5.4 正常写响应

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

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

响应必须回显请求中的 CMDADDRLEN

5.5 异常响应

CMD_ERR EXCEPTION_CODE CRC_L CRC_H

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

6. 特殊指令帧

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

请求: 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 描述符

请求:

00 CRC_L CRC_H

从机成功响应:

00 TLV_ADDR_0 TLV_ADDR_1 TLV_ADDR_2 TLV_ADDR_3 TLV_LEN_L TLV_LEN_H ADDR_WIDTH MAX_PACKET_L MAX_PACKET_H 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 信息块本体时使用的地址位宽,只允许 1632
MAX_PACKET 2 从机允许的最大完整协议帧长度,包含 CMD/ADDR/LEN/DATA/CRC;为 0 表示未声明
ENDIAN_MARK 2 目标内存值字节序标记;按字节读取为 55 AA 表示大端,AA 55 表示小端

描述符区域 CODEINFO 只读,不支持写入。CMDADDR、普通读写 LENTLV_LENMAX_PACKET、固定内存入口 byte_addr/byte_len 等多字节协议控制字段始终按小端序解析;TLV LENname_len 为单字节字段,不涉及大小端。ENDIAN_MARK 不作为协议控制数值转换,而作为原始两字节同步标记:按字节读取为 55 AA 表示目标内存多字节值大端,AA 55 表示目标内存多字节值小端。

8.2 读取完整 CodeInfo

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

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

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

最大数据长度计算:

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

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

结构体实例和单独变量也是 TLV 项,不再额外保存 entry_kind 字段:

TYPE 名称 地址宽度 小程序处理
0x01 DATA 结构体实例 16 位 创建结构体组;未导入定义时按 000102... 字节占位
0x02 DATA 单独变量 16 位 创建单变量组;byte_len 只定义长度,类型需在 UI 中配置为有符号/无符号整数、float,或导入 enum 后按枚举项显示
0x03 IDATA 结构体实例 16 位 创建结构体组;未导入定义时按 000102... 字节占位
0x04 IDATA 单独变量 16 位 创建单变量组;类型由 UI/enum 配置,长度来自 byte_len
0x05 XDATA 结构体实例 16 位 创建结构体组
0x06 XDATA 单独变量 16 位 创建单变量组;类型由 UI/enum 配置,长度来自 byte_len
0x07 ADDR32 结构体实例 32 位 创建结构体组,统一 32 位字节地址
0x08 ADDR32 单独变量 32 位 创建单变量组,统一 32 位字节地址;类型由 UI/enum 配置,长度来自 byte_len

16 位地址入口 VALUE

字段 长度 说明
byte_addr 2 结构体实例或单独变量所在区域的字节地址,小端序
byte_len 2 结构体实例或单独变量的字节长度,小端序
name_len 1 name 字节长度
name name_len 动态名称字段,UTF-8 或 ASCII;结构体定义名、变量名或 enum 类型名

因此 16 位地址固定内存入口 TLV 的 LEN = 0x05 + name_len,受 TLV 单项长度限制,name_len 最大为 250。

32 位地址入口 VALUE

字段 长度 说明
byte_addr 4 结构体实例或单独变量所在统一地址空间内的字节地址,小端序
byte_len 2 结构体实例或单独变量的字节长度,小端序
name_len 1 name 字节长度
name name_len 动态名称字段,UTF-8 或 ASCII;结构体定义名、变量名或 enum 类型名

因此 32 位地址固定内存入口 TLV 的 LEN = 0x07 + name_len,受 TLV 单项长度限制,name_len 最大为 248。

9.2 自定义 TLV

0x20~0x3F 为自定义 TLV 区域。上位机保留原始 TLV 项展示,当前不对自定义项做业务解析。

9.3 板卡信息 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 可变 UTF-8 或 ASCII 字符串 芯片型号,建议 0 结尾或 0 填充
0x47 model 可变 UTF-8 或 ASCII 字符串 电机型号,建议最多 30 字节,可容纳至少 7 个常见 UTF-8 汉字

10. 小程序同步后的处理

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

  1. TYPE LEN VALUE 遍历 TLV 项,未知类型跳过。
  2. 解析 0x40 起的可选板卡信息,更新通讯页 CodeInfo 卡片;不存在的字段不显示、不进入转换公式上下文。
  3. 遇到固定结构体入口 TYPE=0x01/0x03/0x05/0x07 创建结构体组;结构体定义未导入时按字节占位。
  4. 遇到固定变量入口 TYPE=0x02/0x04/0x06/0x08 创建单独变量组,初始按 byte_len 显示原始字节并标记为未配置。
  5. 按固定 TLV TYPE 解析内存区域和地址宽度。
  6. 根据描述符 ENDIAN_MARK 自动确定目标内存多字节值字节序:55 AA 为大端,AA 55 为小端;单字节值不受影响。
  7. 根据 byte_addrbyte_lenTYPEname 创建参数组。
  8. 如果导入了 C 结构体和 enum 定义,只有结构体入口且 name 与结构体定义名一致、长度一致时才补全结构体字段;结构体字段类型为 enum 时,读回值按枚举项显示。
  9. 单独变量入口需要在 UI 中选择与 byte_len 一致的有符号/无符号整数或 float 类型;如果 name 匹配 enum 类型名,或导入文件中存在 EnumType variable_name; 且变量名匹配 name,读回值按枚举项显示。
  10. 如果当前已有同地址、同区域、同名称、同长度的结构体组,并且已经导入过结构体定义,则保留当前导入结构,只更新来源地址、区域、轮询开关等状态。
  11. 如果存在相同 name 但区域或 byte_addr 不同的入口,应视为不同参数组。

11. 示例

11.1 读取 XDATA 中 64 字节结构体

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

11.2 读取 CodeInfo

假设描述符返回:

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

先读取 CODEINFO 描述符:

00 CRC_L CRC_H

再按 ADDR_WIDTH=32 读取 TLV 信息块:

07 56 34 12 00 20 01 CRC_L CRC_H

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

11.3 CodeInfo TLV 示例

结构体实例:

TYPE = 0x05
LEN = 0x14
VALUE = 00 20 40 00 0F 4D 6F 74 6F 72 5F 52 75 6E 74 69 6D 65 5F 74
含义 = XDATA 0x2000 / 64 bytes / Motor_Runtime_t

单独变量:

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

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. 普通读写响应必须回显请求的 CMDADDRLEN
  4. 上位机必须校验响应 CMDADDRLEN、CRC,并确认数据长度等于 LEN
  5. CODEINFOCODE 区只读,写入必须返回 WRITE_PROTECT
  6. 特殊指令正常响应不携带 STATUS,执行失败必须返回异常帧。
  7. CodeInfo 同步必须先读 area=0x00 CODEINFO 描述符,再按描述符返回的 TLV_ADDR/TLV_LEN/ADDR_WIDTH/MAX_PACKET 读取信息块;ADDR_WIDTH=16 使用 area=0x04 CODEADDR_WIDTH=32 使用 area=0x07 ADDR32,内存入口地址宽度由 TLV TYPE 决定。
  8. 固件更新 CodeInfo TLV 类型或内存入口格式时,必须同步更新本文档和小程序解析器。

13. 读写位宽说明

普通内存读写协议不额外携带变量位宽字段,只传输“字节地址 + 字节长度 + 字节数据”。变量显示和编辑位宽由以下信息决定:

  • TYPE=0x01/0x03/0x05/0x07 的结构体实例由导入的 C 结构体定义决定字段类型。
  • TYPE=0x02/0x04/0x06/0x08 的单独变量由 byte_len 给出字节长度,UI 必须选择与该长度一致的显示/编辑类型;未配置前只显示原始字节,不允许写入。
  • enum 不改变普通内存读写帧,只是在导入定义后给同长度整数值增加枚举映射;显示格式为 枚举项 (数值),写入时可输入枚举项名称或数字。
  • 参数页可在寄存器/变量配置中进一步设置公式、单位、范围和显示类型。

如果后续从机需要原子读写、硬件寄存器位宽或对齐语义,可以新增 bit6=1 的 typed 特殊指令,不改变普通内存访问帧。具体 special_code 和 DATA 格式需另行定义。

建议字段:

字段 建议值 说明
ADDR 4 字节 typed 扩展建议使用 32 位字节地址
VALUE_WIDTH 0x010x020x04 单个值 8/16/32 位
COUNT 16 位 值数量,不是字节数
DATA VALUE_WIDTH * COUNT 字节 按协议小端序