# 完整协议说明 ## 1. 协议模式总览 本项目在同一条 BLE 透传链路上支持四种工作模式。模式由设置页 `protocolMode` 控制,通讯页和参数页会按当前模式切换 UI、参数分组和自动读取协议。 | 模式 | key | 通讯页 | 参数页 | 说明 | |---|---|---|---|---| | 无协议 | `none` | 串口发送卡片、日志卡片 | 不显示协议参数组 | 只做原始字节透传和日志观察 | | 标准 Modbus | `modbus-rtu` | 标准 Modbus 指令卡片、日志卡片 | Modbus 寄存器组 | 使用从机地址、功能码和 Modbus CRC | | 存储访问 | `storage-access` | 同步、CodeInfo、特殊指令、读写卡片、日志卡片 | 存储访问结构体组/单变量组 | 按字节访问 DATA、IDATA、XDATA、CODE 或 32 位统一地址空间 | | Bootloader | 设置页升级工具 | Bootloader 工具卡片 | 不参与参数页 | 升级前先用存储访问复位特殊指令进入 Bootloader,随后使用独立升级帧 | 切换协议时,参数页会同步切换对应协议的分组集合。自动读取运行中如果检测到协议变化,会停止当前读取循环,避免用旧协议继续访问新模式下的参数组。 ## 2. 公共链路规则 所有协议均通过 BLE 透传发送原始字节。发送队列、响应等待、日志和超时由 `transport/ble-core.js` 统一处理。 | 项目 | 规则 | |---|---| | 发送单位 | 原始字节数组 | | 日志显示 | HEX 或文本两种显示方式 | | 响应识别 | 由 `protocols/transport-helpers.js` 根据待响应协议调用对应解析器 | | 包长限制 | 设置页最大包长用于 Modbus 和存储访问分片,`0` 表示不限制 | | 参数自动读取 | 由设置页“自动轮询”开关控制,参数页“读取”只执行一次全量读取 | ## 3. 无协议模式 无协议模式不解析帧格式,也不绑定参数页分组。 | 功能 | 行为 | |---|---| | 发送 | 通讯页原始串口发送卡片支持 HEX 或文本 | | 日志 | 显示发送和接收日志 | | 响应 | 不要求特定响应帧 | | 参数页 | 不显示标准 Modbus 或存储访问参数组 | 适用场景:临时调试私有串口命令、观察设备透传输出、验证蓝牙链路连通性。 ## 4. 标准 Modbus RTU 标准 Modbus 使用常规 RTU 帧格式: ```text SLAVE FUNC DATA... CRC_L CRC_H ``` CRC 使用 `CRC16-Modbus`,低字节在前。 | 参数 | 值 | |---|---| | 多项式 | `0x8005` 反射形式 | | 初值 | `0xFFFF` | | 输入反转 | 是 | | 输出反转 | 是 | | 结果异或 | `0x0000` | | 输出顺序 | 低字节在前 | 支持功能码: | 功能码 | 名称 | 方向 | 参数页用途 | |---:|---|---|---| | `0x01` | 读线圈 | 读 | bit/coil 类型参数组 | | `0x02` | 读离散输入 | 读 | 只读 bit 类型参数组 | | `0x03` | 读保持寄存器 | 读 | 可读写寄存器组 | | `0x04` | 读输入寄存器 | 读 | 只读寄存器组 | | `0x05` | 写单线圈 | 写 | 单个 bit 写入 | | `0x06` | 写单寄存器 | 写 | 单个 16 位寄存器写入 | | `0x10` | 写多个寄存器 | 写 | 连续寄存器批量写入 | 数量限制按当前最大包长计算。默认 64 字节包长下,`0x03/0x04` 单帧最多读取 29 个寄存器,`0x10` 单帧最多写入 27 个寄存器。超过限制时参数页会按连续片段分片读取或写入。 异常响应格式: ```text SLAVE (FUNC | 0x80) EXCEPTION_CODE CRC_L CRC_H ``` 标准 Modbus 与存储访问完全独立,不解析 CodeInfo TLV 信息块,也不使用存储访问的 `CMD/AREA`。 ## 5. 存储访问协议 存储访问协议不带从机地址,不属于标准 Modbus。普通内存访问帧格式为: ```text CMD ADDR LEN DATA... CRC_L CRC_H ``` CRC 使用 `CRC16-CCITT-FALSE`,低字节在前。除 CRC 外,地址、长度和多字节数据字段都按 CodeInfo 描述符 `ENDIAN_MARK` 自动选择大端或小端;未同步时按设置页“默认大端模式”开关处理。 `CMD` 位定义: ```text bit7 ERR 故障标志,仅回帧出现 bit6 SPECIAL 特殊指令标志位 bit5~bit4 RSV 保留,普通读写保持 0 bit3 RW 普通读写标志,0=读,1=写 bit2~bit0 AREA 普通读写区域码 ``` 地址区域与存储区域: | AREA | 名称 | 地址宽度 | 读 | 写 | 说明 | |---:|---|---|---|---|---| | `0x00` | CODEINFO | 16 位 | 支持 | 禁止 | 同步时读取一次,返回 TLV 起始地址与长度 | | `0x01` | DATA | 16 位 | 支持 | 支持 | 内部直接寻址 RAM | | `0x02` | IDATA | 16 位 | 支持 | 支持 | 内部间接寻址 RAM | | `0x03` | XDATA | 16 位 | 支持 | 支持 | 外部数据空间或扩展 RAM | | `0x04` | CODE | 16 位 | 支持 | 禁止 | 程序存储区 | | `0x05..0x06` | 保留 | - | 禁止 | 禁止 | 保留 | | `0x07` | ADDR32 | 32 位 | 支持 | 支持 | 统一 32 位字节地址 | 普通读请求: ```text CMD ADDR_0 ADDR_1 ADDR_2 ADDR_3 LEN_L LEN_H CRC_L CRC_H 或 CMD ADDR_L ADDR_H LEN_L LEN_H CRC_L CRC_H ``` 上方普通帧示例按 `ENDIAN_MARK=AA 55` 的小端设备展示;`ENDIAN_MARK=55 AA` 时 `ADDR/LEN` 字节顺序反转,CRC 仍固定低字节在前。 普通读响应回显 `CMD`、`ADDR`、`LEN`,然后携带 `LEN` 字节数据。 普通写请求: ```text CMD ADDR_0 ADDR_1 ADDR_2 ADDR_3 LEN_L LEN_H DATA... CRC_L CRC_H 或 CMD ADDR_L ADDR_H LEN_L LEN_H DATA... CRC_L CRC_H ``` 普通写响应只回显 `CMD`、`ADDR`、`LEN`。写 CODEINFO 或 CODE 区必须返回写保护异常。 特殊指令使用 `CMD bit6=1`: ```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 | 无 | 无 | 复位 | 完整帧细节、异常码、示例和从机参考见 `存储访问协议.md`。 ## 6. CodeInfo 与参数组同步 存储访问同步固定分两步: 1. 发送 `00 CRC_L CRC_H` 读取 `area=0x00 CODEINFO` 描述符,数据区为 `TLV_ADDR32 + TLV_LEN16 + ADDR_WIDTH8 + MAX_PACKET16 + ENDIAN_MARK16`。 2. 根据描述符返回的地址、长度、地址位宽和最大包长读取完整 CodeInfo TLV 信息块;`ADDR_WIDTH=16` 使用 `CODE` 区 16 位地址,`ADDR_WIDTH=32` 使用 `ADDR32`。 除 CRC 固定低字节在前外,其余地址、长度和多字节数据字段都跟随描述符末尾的原始两字节 `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` 为单字节字段,不涉及大小端。 CodeInfo 信息块使用纯 TLV 格式,不再包含固定头、`format_version`、`board_info_format`、`struct_entry_len` 或 `struct_table`: ```text TYPE LEN VALUE... ``` 常用 TLV: | TYPE | 名称 | VALUE | |---:|---|---| | `0x01..0x0F` | 基础变量 | `ENTRY_PREFIX + var_name_len8 + var_name` | | `0x10..0x1F` | 基础数组 | `ENTRY_PREFIX + elem_byte_len16 + elem_count16 + dim_count8 + dim_len16[] + var_name_len8 + var_name` | | `0x20` | enum 变量 | `ENTRY_PREFIX + def_name_len8 + def_name + var_name_len8 + var_name` | | `0x21` | 结构体实例 | `ENTRY_PREFIX + def_name_len8 + def_name + var_name_len8 + var_name` | | `0x22` | enum 数组 | `ENTRY_PREFIX + elem_byte_len16 + elem_count16 + dim_count8 + dim_len16[] + def_name_len8 + def_name + var_name_len8 + var_name` | | `0x23` | 结构体数组 | `ENTRY_PREFIX + elem_byte_len16 + elem_count16 + dim_count8 + dim_len16[] + def_name_len8 + def_name + var_name_len8 + var_name` | | `0x40..` | 板卡参数 | `0x40` 起按 `cave_freq/ref_volt/...` 递增 | `ENTRY_PREFIX` 由 CODEINFO 描述符 `ADDR_WIDTH` 决定:`ADDR_WIDTH=16` 时为 `area8 + byte_addr16 + byte_len16`,其中 `area` 为 `DATA/IDATA/XDATA/CODE`;`ADDR_WIDTH=32` 时为 `byte_addr32 + byte_len16`,区域固定为 `ADDR32`。TLV `LEN` 为 1 字节,表示单项 `VALUE` 字节数;整段 CodeInfo 总长度仍由 CODEINFO 描述符 `TLV_LEN16` 决定。`byte_addr/byte_len/elem_byte_len/elem_count/dim_len` 按 `ENDIAN_MARK` 解析。 基础变量和基础数组的类型由 TYPE 直接决定,覆盖 raw、8/16/32/64/128/256 位有符号和无符号整数、float32、double,以及文本数组。文本数组按 UTF-8、GBK、ASCII 顺序自动解析;除 ASCII 单字符外,文本都按数组形式描述。未定义 TYPE 可由项目自定义,通用解析器只保留原始项并跳过业务解析。 同步到参数页后的规则: | 条件 | 处理 | |---|---| | `TYPE=0x01..0x0F` | 创建基础单变量组,按 TYPE 解码;raw 类型只显示原始字节 | | `TYPE=0x10..0x1F` | 基础数组按 `dim_len[]` 展开为 `var[i]` 或 `var[i][j]`;文本数组显示为整段文本字段 | | `TYPE=0x20` 且 `def_name` 匹配 enum 定义 | 创建 enum 变量组,列表左侧显示存放位置、起始地址、长度、原始十六进制和枚举定义/枚举项,右侧显示十进制值 | | `TYPE=0x21` 且未导入结构体定义 | 创建结构体组,每个字节按 `00`、`01`、`02` 命名 | | `TYPE=0x21` 且 `def_name` 与导入定义名、长度匹配 | 按 C 结构体字段展示,保留多字节字段整体值;字段类型为 enum 时按枚举项显示 | | `TYPE=0x22` enum 数组 | 按元素展开并按 `def_name` 匹配 enum 定义 | | `TYPE=0x23` 结构体数组 | 导入 C struct 后展开为 `var[i].field`、`var[i][j].field` 等,未导入前按字节占位 | | 相同名称但地址或区域不同 | 视为不同参数组 | | 当前已有同区域、同地址、同名称、同长度且已导入定义 | 保留当前导入结构,只更新同步来源信息 | | 未知 TLV `TYPE` | 跳过 | 结构体和 enum 可在同一个 `.h/.c/.txt` 定义文件中导入。结构体入口按 `def_name` 匹配 `typedef struct` 名称,enum 入口按 `def_name` 匹配 `typedef enum` 名称;`var_name` 只作为参数页显示名和去重信息。enum 只影响显示与输入映射,不改变底层字节读写协议。 CodeInfo 卡片始终显示在通讯页存储访问协议卡片内。板卡信息 TLV 不存在时对应字段不显示;电机型号和芯片型号按 UTF-8、GBK、ASCII 顺序自动解析。 ## 7. 参数值与转换公式 参数页读回的数据先按字段类型解码为原始值,再根据可选转换公式显示实际值。公式由参数配置自定义,适合标幺值转实际值、比例缩放和偏置换算。 存储访问参数组的多字节变量值按 CodeInfo 描述符 `ENDIAN_MARK` 自动选择大端或小端解码和写入;未同步 CodeInfo 时按设置页“默认大端模式”开关处理。标准 Modbus 寄存器仍保持 Modbus 字/字节规则,不受该字段影响。 公式支持变量: | 变量 | 含义 | |---|---| | `x`、`value`、`rawValue` | 当前字段原始值 | | `caveFreq` | CodeInfo 载波频率 | | `refVolt` | CodeInfo 参考电压实际值 | | `ampGain` | CodeInfo 运放倍数 | | `rsShunt` | CodeInfo 采样电阻 | | `busDiv` | CodeInfo 母线电压分压比 | | `alongDiv` | CodeInfo 模拟输入电压分压比 | | `maxPacketLength` | 当前同步上下文的最大完整协议帧长度,未声明时为 0 | | `addressWidth` | CodeInfo 本体读取地址长度,来自描述符 `ADDR_WIDTH` | | `memoryEndian` | 当前目标内存变量字节序,来自描述符 `ENDIAN_MARK`;未同步时来自设置页默认字节序 | 公式只支持数字、括号和 `+ - * /`,不执行任意脚本。 ## 8. Bootloader 协议 Bootloader 协议用于固件升级。升级流程启动时先发送存储访问特殊指令复位帧 `0x41 + CRC`,随后在 500ms 握手窗口内每 50ms 发送一次 Bootloader 握手帧。Bootloader 升级帧头固定为: ```text 46 54 PAYLOAD... CRC_H CRC_L ``` CRC 使用 `CRC16-CCITT-FALSE`,高字节在前。 主要命令: | 命令 | 载荷 | 响应长度 | 用途 | |---|---|---:|---| | 握手 | `39 42 4C` | 15 | 读取 Bootloader 版本和芯片 ID | | 解锁 | `08 4E 00` | 8 | 进入可编程状态 | | 编程 | `44 ADDR_L ADDR_H DATA(128B)` | 8 | 写入 128 字节程序块 | | 全 Flash 校验 | `19 43 43` | 9 | 读取 Flash 校验值,返回数据部分低字节在前 | | 页擦除开关 | `08 50 45/44` | 8 | 开启或关闭页擦除 | | 退出 | `08 42 42` | 8 | 退出 Bootloader | ACK 为 `0x06`,NAK 为 `0x15`。固件文件加载、芯片型号识别、升级地址、Flash 容量、升级前复位和握手轮询由 `features/bootloader/` 处理。 升级时保留固件最后一个不可擦写扇区:16K 固件从 `0x3F80` 开始不写入,32K 固件从 `0x7F00` 开始不写入。固件文件仍按完整 16K/32K 文件校验大小,编程循环只覆盖可擦写地址范围。 ## 9. 维护约束 1. 标准 Modbus、存储访问、Bootloader 分别只保留一个协议入口:`protocols/modbus-rtu/index.js`、`protocols/storage-access/index.js`、`protocols/bootloader/index.js`。 2. 页面只负责 UI 展示和事件转发,不直接拼帧、不直接解析二进制响应。 3. 通讯页业务聚合在 `features/communication/`,参数页业务聚合在 `features/parameter-groups/`,存储访问按 `features/storage-access/manual-command.js`、`features/storage-access/memory-areas.js`、`features/storage-access/protocol-io.js`、`features/storage-access/code-info-sync.js` 分离 UI 状态、区域元数据、协议传输和数据同步。 4. 可复用且有明确领域边界的逻辑保留在 `domain/parameter-groups/` 和 `domain/storage-access/`。 5. 不再为单个按钮、单个字段、单个导入步骤新增小模块;新增能力优先并入现有协议模块、功能服务或领域模型。