存储访问协议用于上位机通过蓝牙透传链路按字节访问从机 MCU 的存储区域。协议为单主单从模型,不带从机地址字段,不属于标准 Modbus RTU。
小程序中该协议与标准 Modbus RTU、Bootloader 升级协议平级:
CMD + ADDR + LEN + DATA + CRC16-CCITT-FALSE。bit6=1 的特殊指令帧;当前仅定义复位特殊指令。area=0x00 的 codeinfo 描述符,再用 addr32 读取 TLV 信息块。除 CRC 算法内部计算外,所有多字节协议控制字段均为大端序。当前目标内存变量的多字节值默认按大端解释和编辑。
32 位地址 : ADDR_3 ADDR_2 ADDR_1 ADDR_0
16 位长度 : LEN_H LEN_L
CRC 输出 : CRC_H CRC_L
CRC 使用 CRC16-CCITT-FALSE:
| 参数 | 值 |
|---|---|
| 多项式 | 0x1021 |
| 初值 | 0xFFFF |
| 输入反转 | 否 |
| 输出反转 | 否 |
| 结果异或 | 0x0000 |
| 输出顺序 | 高字节在前 |
CRC 覆盖除最后两个 CRC 字节外的整帧内容。
每帧第 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。
| AREA | 名称 | 读 | 写 | 用途 |
|---|---|---|---|---|
0x00 |
CODEINFO | 支持 | 禁止 | 同步时读取一次,返回 TLV 起始地址与长度 |
0x01 |
DATA | 支持 | 支持 | 内部直接寻址 RAM 区 |
0x02 |
IDATA | 支持 | 支持 | 内部间接寻址 RAM 区 |
0x03 |
XDATA | 支持 | 支持 | 外部数据空间或扩展 RAM |
0x04 |
CODE | 支持 | 禁止 | 程序存储区,16 位地址 |
0x05..0x06 |
保留 | 禁止 | 禁止 | 保留 |
0x07 |
ADDR32 | 支持 | 支持 | 32 位统一地址模式 |
写 CODEINFO 或 CODE 区必须返回写保护异常。
MODE=0x07: CMD ADDR_3 ADDR_2 ADDR_1 ADDR_0 LEN_H LEN_L CRC_H CRC_L
MODE=0x01..0x04: CMD ADDR_H ADDR_L LEN_H LEN_L CRC_H CRC_L
长度分别为 9 字节或 7 字节。
MODE=0x07: CMD ADDR_3 ADDR_2 ADDR_1 ADDR_0 LEN_H LEN_L DATA... CRC_H CRC_L
MODE=0x01..0x04: CMD ADDR_H ADDR_L LEN_H LEN_L DATA... CRC_H CRC_L
长度分别为 9 + LEN 或 7 + LEN 字节。
MODE=0x07: CMD ADDR_3 ADDR_2 ADDR_1 ADDR_0 LEN_H LEN_L DATA... CRC_H CRC_L
MODE=0x01..0x04: CMD ADDR_H ADDR_L LEN_H LEN_L DATA... CRC_H CRC_L
长度分别为 9 + LEN 或 7 + LEN 字节。
响应必须回显请求中的 CMD、ADDR、LEN。
MODE=0x07: CMD ADDR_3 ADDR_2 ADDR_1 ADDR_0 LEN_H LEN_L CRC_H CRC_L
MODE=0x01..0x03: CMD ADDR_H ADDR_L LEN_H LEN_L CRC_H CRC_L
长度分别为 9 字节或 7 字节。
响应必须回显请求中的 CMD、ADDR、LEN。
CMD_ERR EXCEPTION_CODE CRC_H CRC_L
长度固定 4 字节。CMD_ERR 为原请求 CMD | 0x80。
特殊指令使用 CMD bit6=1,bit0~bit5 表示特殊指令码。当前仅定义 0x01 复位。
请求: CMD DATA... CRC_H CRC_L
响应: CMD DATA... CRC_H CRC_L
异常: CMD_ERR EXCEPTION_CODE CRC_H CRC_L
特殊指令执行失败时直接返回异常帧,成功响应不额外携带状态字节。
| 指令码 | CMD | 名称 | 请求 DATA | 成功响应 DATA | 说明 |
|---|---|---|---|---|---|
0x01 |
0x41 |
RESET | 无 | 无 | 复位 |
| 异常码 | 名称 | 说明 |
|---|---|---|
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 | 不支持的操作 |
CodeInfo 同步分两步:
area=0x00 CODEINFO 的描述符,响应 DATA 为 9 字节。len16、地址位宽和最大包长,读取完整 CodeInfo TLV 信息块。请求:
00 CRC_H CRC_L
从机成功响应:
00 TLV_ADDR_3 TLV_ADDR_2 TLV_ADDR_1 TLV_ADDR_0 TLV_LEN_H TLV_LEN_L ADDR_WIDTH MAX_PACKET_H MAX_PACKET_L CRC_H CRC_L
响应 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 表示未声明 |
描述符区域 CODEINFO 只读,不支持写入。CMD、ADDR、普通读写 LEN、TLV_LEN、MAX_PACKET 等多字节协议控制字段始终按大端序解析;TLV LEN 为单字节字段,不涉及大小端。
上位机继续发送普通读请求:
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
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 字节。
结构体实例和单独变量也是 TLV 项,不再额外保存 entry_kind 字段:
| TYPE | 名称 | 地址宽度 | 小程序处理 |
|---|---|---|---|
0x01 |
DATA 结构体实例 | 16 位 | 创建结构体组;未导入定义时按 00、01、02... 字节占位 |
0x02 |
DATA 单独变量 | 16 位 | 创建单变量组;byte_len 只定义长度,类型需在 UI 中配置为有符号/无符号整数、float,或导入 enum 后按枚举项显示 |
0x03 |
IDATA 结构体实例 | 16 位 | 创建结构体组;未导入定义时按 00、01、02... 字节占位 |
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 |
32 | 固定 32 字节名称字段,UTF-8 或 ASCII,建议 0 结尾或 0 填充;结构体定义名、变量名或 enum 类型名 |
因此 16 位地址固定内存入口 TLV 的 LEN 固定为 0x24。
32 位地址入口 VALUE:
| 字段 | 长度 | 说明 |
|---|---|---|
byte_addr |
4 | 结构体实例或单独变量所在统一地址空间内的字节地址 |
byte_len |
2 | 结构体实例或单独变量的字节长度 |
name |
32 | 固定 32 字节名称字段,UTF-8 或 ASCII,建议 0 结尾或 0 填充;结构体定义名、变量名或 enum 类型名 |
因此 32 位地址固定内存入口 TLV 的 LEN 固定为 0x26。
0x20~0x3F 为自定义 TLV 区域。上位机保留原始 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Ω |
0x44 |
bus_div |
4 | float32 |
母线电压分压比,大端序 |
0x45 |
along_div |
4 | float32 |
模拟输入电压分压比,大端序 |
0x46 |
chip_model |
可变 | UTF-8 或 ASCII 字符串 | 芯片型号,建议 0 结尾或 0 填充 |
0x47 |
model |
可变 | UTF-8 或 ASCII 字符串 | 电机型号,建议最多 30 字节,可容纳至少 7 个常见 UTF-8 汉字 |
小程序读取完整 CodeInfo TLV 信息块后:
TYPE LEN VALUE 遍历 TLV 项,未知类型跳过。0x40 起的可选板卡信息,更新通讯页 CodeInfo 卡片;不存在的字段不显示、不进入转换公式上下文。TYPE=0x01/0x03/0x05/0x07 创建结构体组;结构体定义未导入时按字节占位。TYPE=0x02/0x04/0x06/0x08 创建单独变量组,初始按 byte_len 显示原始字节并标记为未配置。TYPE 解析内存区域和地址宽度。byte_addr、byte_len、TYPE、name 创建参数组。name 与结构体定义名一致、长度一致时才补全结构体字段;结构体字段类型为 enum 时,读回值按枚举项显示。byte_len 一致的有符号/无符号整数或 float 类型;如果 name 匹配 enum 类型名,或导入文件中存在 EnumType variable_name; 且变量名匹配 name,读回值按枚举项显示。name 但区域或 byte_addr 不同的入口,应视为不同参数组。AREA = 0x03 XDATA
ADDR = 0x2000
LEN = 0x0040
读请求 = 03 20 00 00 40 CRC_H CRC_L
正常响应 = 03 20 00 00 40 DATA(64B) CRC_H CRC_L
假设描述符返回:
TLV_ADDR = 0x00123456
TLV_LEN = 0x0120
ADDR_WIDTH = 32
MAX_PACKET = 0x0040
先读取 CODEINFO 描述符:
00 CRC_H CRC_L
再按 ADDR_WIDTH=32 读取 TLV 信息块:
07 00 12 34 56 01 20 CRC_H CRC_L
0x07 表示 32 位统一地址模式。
结构体实例:
TYPE = 0x05
LEN = 0x24
VALUE = 20 00 00 40 4D 6F 74 6F 72 5F 52 75 6E 74 69 6D 65 5F 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
含义 = XDATA 0x2000 / 64 bytes / Motor_Runtime_t
单独变量:
TYPE = 0x08
LEN = 0x26
VALUE = 00 00 21 00 00 02 73 70 65 65 64 5F 72 65 66 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
含义 = ADDR32 0x00002100 / 2 bytes / speed_ref
LEN 单位始终为字节,字段固定 16 位,有效范围为 1..0xFFFF;CodeInfo TLV 项内的 LEN 为 1 字节,只表示单项 VALUE 长度。ADDR 单位始终为字节;MODE=0x07 时地址字段为 32 位,MODE=0x01..0x04 时地址字段为 16 位;MODE=0x00 为 CODEINFO 短描述符请求;MODE=0x05..0x06 保留。CMD、ADDR、LEN。CMD、ADDR、LEN、CRC,并确认数据长度等于 LEN。CODEINFO 和 CODE 区只读,写入必须返回 WRITE_PROTECT。STATUS,执行失败必须返回异常帧。area=0x00 CODEINFO 描述符,再按描述符返回的 TLV_ADDR/TLV_LEN/ADDR_WIDTH/MAX_PACKET 读取信息块;ADDR_WIDTH=16 使用 area=0x04 CODE,ADDR_WIDTH=32 使用 area=0x07 ADDR32,内存入口地址宽度由 TLV TYPE 决定。普通内存读写协议不额外携带变量位宽字段,只传输“字节地址 + 字节长度 + 字节数据”。变量显示和编辑位宽由以下信息决定:
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 |
0x01、0x02、0x04 |
单个值 8/16/32 位 |
COUNT |
16 位 | 值数量,不是字节数 |
DATA |
VALUE_WIDTH * COUNT 字节 |
按协议大端序 |