# 存储访问协议 ## 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` 布局,应同步更新本文档和小程序解析器。