# RS485 协议

> 本页对应仓库内 2026-10-27 的固件快照。原工程后续版本对磁铁状态和数据有效性有更新，尚未同步到本仓库；对接实物前请核对实际固件和协议版本。

这块板通过半双工 RS485 通信，应用协议是我们自己定义的二进制格式。CRC 使用 CRC-16/MODBUS 算法，但帧头、地址和命令格式是项目自己的设计，不能直接用 Modbus RTU 主站读取。

第一次接这块板，可以先按末尾的通信步骤读一下设备信息，再对照十六进制例子看每个字段。然后换用编码器时，帧格式相同，但测量类型、单位和校准内容会变。

想对照代码看，定义在 [rs485_proto.h](../software/firmware/inc/rs485_proto.h#L5)，实际处理在 [rs485_proto.c](../software/firmware/src/rs485_proto.c#L584)。广播等细节按实现说明。

## 串口与默认配置

- MCU 串口为 USART0：PA9/TX、PA10/RX，8 数据位、无校验、1 停止位，即 8N1。
- PB5 控制收发方向；高电平发送，低电平接收。源码说明 SIT3088 的 DE 与 RE# 接在一起。主机通常使用自动收发 USB-RS485 适配器。
- 未保存配置或 Flash 配置校验失败时：ID=1，115200 bit/s，轮询模式，周期参数 20 ms，角度测量类型。
- 波特率参数是索引：`0→9600`、`1→19200`、`2→38400`、`3→57600`、`4→115200`、`5→230400`、`6→460800`、`7→921600`。

依据：[默认配置，第 143 行](../software/firmware/src/rs485_proto.c#L143)，[UART 配置，第 393 行](../software/firmware/src/rs485_proto.c#L393)，[波特率表，第 52 行](../software/firmware/src/rs485_proto.c#L52)。接口供电、A/B 接线和端接电阻应再对照实际硬件，软件引脚定义不能代替接线图。

## 帧格式

所有多字节字段均为小端；有符号整数用二进制补码。

```text
偏移       长度    字段
0          1       A5
1          1       5A
2          1       DST：目标设备地址
3          1       SRC：源设备地址
4          1       TYPE：测量类型
5          1       CMD：命令
6          1       SEQ：8 位序号
7          1       LEN：DATA 的字节数
8          LEN     DATA
8+LEN      1       CRC 低字节
9+LEN      1       CRC 高字节
```

`LEN≤32`，整帧长 `10+LEN`，最大 42 字节。CRC 从 DST 开始，到 DATA 最后一个字节结束，不包含 `A5 5A` 和 CRC 自身。初始值 `0xFFFF`，逐位右移，多项式 `0xA001`，无最终异或；校验向量 `CRC("123456789")=0x4B37`，线上顺序为 `37 4B`。

依据：[CRC，第 75 行](../software/firmware/src/rs485_proto.c#L75)，[发送组帧，第 489 行](../software/firmware/src/rs485_proto.c#L489)。可用 [独立教学实现](../software/examples/hall_protocol.py) 和 [协议检查](../tests/test_protocol.py) 重算本文实例。

<div data-hall-animation="rs485"></div>

先用动画看一下请求、方向切换和应答的关系。速度已放慢，不表示真实收发时序。

## 地址、类型和应答

- 主机地址使用 `0x00`；设备 ID 为 `1..254`；`DST=0xFF` 为广播。
- `TYPE=0x01` 是角度编码器，`0x02` 是直线位置，`0xFF` 是任意类型。设备只处理当前类型或 `0xFF`。
- 同一总线所有传感器都应有唯一 ID。指定类型能减少不同类型误应答，不能替代唯一地址。
- 普通应答：`DST=请求SRC`，`SRC=设备当前ID`，`TYPE=当前测量类型`，`CMD=请求CMD|0x80`，`SEQ=请求SEQ`。
- 应答 DATA 第一字节是状态；状态成功才按该命令的布局解析后续内容。
- SEQ 用于关联事务，8 位回卷；固件没有请求去重功能。超时重发的设置命令可能再次执行和写 Flash。

特别注意：`SET_ID` 的成功应答源地址已经是**新 ID**；`SET_PROFILE` 的成功应答类型已经是**新类型**。写主机时需要为这两种事务匹配预期的新标识。`SET_BAUD` 先按旧波特率发应答，再切到新波特率。

依据：[地址与类型过滤，第 606 行](../software/firmware/src/rs485_proto.c#L606)，[设置 ID，第 650 行](../software/firmware/src/rs485_proto.c#L650)，[应答与波特率切换，第 967 行](../software/firmware/src/rs485_proto.c#L967)。

### 广播

固件直接忽略以下广播命令：`GET_INFO`、`GET_DATA`、`GET_CAL`、`SET_ID`、`SET_BAUD`、`FACTORY`。广播不会用于发现设备或批量分配 ID。

其余已实现设置命令可执行广播，但不回成功或错误应答；仍受 TYPE 过滤和参数检查约束。广播切换模式时应先理解其清除校准的影响。多设备教学优先采用逐台、逐地址轮询。

依据：[广播排除列表，第 616 行](../software/firmware/src/rs485_proto.c#L616)，[广播不应答，第 968 行](../software/firmware/src/rs485_proto.c#L968)。

## 命令

下文 `u8/u16/i16/i32` 分别是无符号 8 位、无符号 16 位、有符号 16 位和有符号 32 位。所列成功返回内容均在状态字节之后。

### 基本通信与配置

- `0x01 GET_INFO`：请求为空；返回类型 `u8`、ID `u8`、上传模式 `u8`、波特率索引 `u8`、周期 ms `u16`、固件版本 `u16`、校准有效 `u8`、标志 `u8`、机械零点 2 字节。应答 DATA 共 13 字节。版本常量为 `0x0101`，当前上位机显示为 1.1。零点在角度模式按 `u16` 解读，在直线模式按 `i16` 解读。
- `0x02 SET_ID`：请求新 ID `u8`，范围 1..254；成功返回新 ID `u8`。自动保存。
- `0x03 SET_BAUD`：请求索引 `u8`，范围 0..7；成功返回索引 `u8`。自动保存，旧波特率应答后立即切换。
- `0x04 SET_MODE`：请求上传模式 `u8` 加周期 ms `u16`；模式 0=轮询，1=主动上报，周期范围 5..60000 ms，即使模式 0 也要求周期合法。返回同样三字节。自动保存。
- `0x05 SAVE`：请求为空；保存当前配置，无额外成功数据。
- `0x06 FACTORY`：请求为空；恢复 ID=1、115200、轮询、20 ms、角度模式，清两种模式零点和校准并保存；返回新 ID `u8`、波特率索引 `u8`。若原波特率不同，同样先按旧波特率应答。
- `0x07 SET_PROFILE`：请求类型 `u8`，只允许 1 或 2；成功返回当前类型 `u8`。真正切换到另一类型时会清除两种模式的零点与校准并保存；请求已经处于的同一类型时直接返回，不清除、不重写。

依据：[基本命令，第 626 行](../software/firmware/src/rs485_proto.c#L626)，[模式切换，第 902 行](../software/firmware/src/rs485_proto.c#L902)，[清理两种校准，第 251 行](../software/firmware/src/rs485_proto.c#L251)。

### 采样与主动上报

- `0x10 GET_DATA`：请求为空；成功应答 DATA 是 `status + sample`，共 14 字节。
- `0x30 REPORT`：设备主动发送，目标 `0x00`；DATA 仅为 sample，共 13 字节，**没有状态字节**。它的 SEQ 是设备独立递增的上报序号。

```text
sample 内偏移    类型    内容
0               i32     角度或位置整数
4               i16     幅度，ADC 计数
6               u16     U1 ADC 原码
8               u16     U2 ADC 原码
10              u16     U4 ADC 原码
12              u8      flags
```

`TYPE=1` 时 `value/100` 为度，`value×π/18000` 为 rad；`TYPE=2` 时 `value/100` 为 mm，`value×0.00001` 为 m。变量名 `angle_cdeg` 在直线模式中仍被复用，不能按名字判断单位。

幅度不是磁感应强度的 T 或 mT；它是用于判断信号强弱的 ADC 计数，线上按 i16 打包且执行饱和裁剪。原码是 12 位 ADC 结果，协议用 u16 承载。

依据：[采样打包，第 522 行](../software/firmware/src/rs485_proto.c#L522)，[数据应答，第 758 行](../software/firmware/src/rs485_proto.c#L758)，[主动上报，第 1065 行](../software/firmware/src/rs485_proto.c#L1065)，[采样结构，第 45 行](../software/firmware/inc/hall_encoder.h#L45)。

### 零点、校准和直线模型宽度

- `0x21 SET_ZERO`：请求为空时捕获当前原始测量作为机械零点；也可传入 2 字节零点偏移。角度为 `u16`、0..35999、单位 0.01°；直线为 `i16`、单位 0.01 mm。它设置的是从原始读数中减掉的偏移，不是命令“当前位置显示指定数值”。返回已存零点 2 字节并自动保存。
- `0x22 CLEAR_ZERO`：请求为空；将当前模式零点设为 0，自动保存；无额外成功数据。
- `0x23 CAL_BEGIN`：请求为空；开始角度峰峰值采集或直线零磁场基线采集；此时不写 Flash。角度需转满至少一圈；直线需先拿开磁铁。
- `0x24 CAL_END`：请求为空；校准成功后自动保存，返回 `offset[3]`（三个 i16）和 `gain[3]`（三个 u16），DATA 共 13 字节。数据不足返回 0x04 并保持校准进行状态；未开始就结束返回 0x03。
- `0x25 CAL_CLEAR`：请求为空；清除当前模式校准并自动保存。直线回到基线 2048；零点和敏感宽度保留。
- `0x26 GET_CAL`：请求为空；返回 `flags u8 + offset[3] i16 + gain[3] u16`，DATA 共 14 字节。比 `CAL_END` 多了一个 flags，解析偏移不能共用。
- `0x27 SET_SIGMA`：仅直线模式；请求敏感宽度 `u16`，单位 µm、范围 2000..30000，默认 8000；返回宽度 `u16` 并保存。GUI 用 mm 显示，所以 8 mm 对应请求整数 8000。

角度模式的 offset 是每路偏置，gain 是 Q12 相对增益，4096=1.0。直线模式复用这些槽位：offset 为三路零磁场基线；`gain[0]` 为宽度 µm，`gain[1]` 和 `gain[2]` 为 0；不能把 8000 解读为 Q12 增益。

依据：[零点与校准，第 768 行](../software/firmware/src/rs485_proto.c#L768)，[GET_CAL，第 883 行](../software/firmware/src/rs485_proto.c#L883)，[宽度设置，第 931 行](../software/firmware/src/rs485_proto.c#L931)，[直线校准导出，第 556 行](../software/firmware/src/rs485_proto.c#L556)。

## 状态码和标志

状态码描述命令是否执行成功：

- `0x00` 成功。
- `0x01` 载荷长度错误。
- `0x02` 参数超范围。
- `0x03` 当前状态不允许，例如在角度模式设置直线宽度。
- `0x04` 校准数据不足。
- `0x05` Flash 写入失败。
- `0x06` 未知命令。

flags 描述测量状态，多个位可同时置位：bit0/`0x01` 磁场弱；bit1/`0x02` 校准有效；bit2/`0x04` 校准进行中；bit3/`0x08` 直线超出阵列或有效插值条件。

状态成功后仍要检查弱磁、越界和校准标志。CRC 通过不代表位置正确；误差和使用条件见[校准与测量](Calibration-and-Measurement.md)。固件没有硬件采样时间戳、温度补偿质量字段或磁铁在位确认字段。

依据：[状态定义，第 122 行](../software/firmware/inc/rs485_proto.h#L122)，[flags 定义，第 37 行](../software/firmware/inc/hall_encoder.h#L37)。

## 收发、超时与保存

设备发帧前拉高 PB5 并用软件忙等约 50 µs，发完等待发送完成，再忙等约 50 µs 后拉低回接收。这是代码里的近似延时，应通过逻辑分析仪核对实际波形。

固件有 128 字节接收环（保留一个空槽，有效容量 127）；满时新字节被丢弃，无可查询溢出计数。拼帧长超过 32、CRC 错误、地址/类型不匹配均静默丢弃，不返回错误帧；已通过 CRC 且属于本机的错误命令才可能返回状态码。主循环处理残留拼帧时，距上次处理超过 20 ms 会清掉不完整帧。

主动上报要求距最近接收和发送各至少 3 ms、本轮没有应答且不在拼帧中。这只是本机避让，没有完整总线仲裁；一条总线上同时只启用一台主动上报设备，多节点采用主机逐一轮询。

周期参数是调度目标。115200/8N1 下，23 字节上报的纯线速时间约 2.00 ms；24 字节采样应答约 2.08 ms。轮询还需 10 字节请求约 0.87 ms，以及方向切换、运算和主机延迟。9600 下同一上报约需 24 ms，所以参数允许 5 ms 不代表所有波特率都能做到 200 Hz，更不代表 ADC 同步采样频率。

写 Flash 的命令应留出不少于 100 ms 的超时；原 GUI 通常留 300–500 ms。配置实际保存为最后一页地址 `0x08007C00` 上的 32 字节结构，带 magic/layout/CRC。读取失败时回默认值；当前实现是擦页后写入，无双份备份或掉电事务承诺。收到 Flash 错误后应回读配置与测量状态，不假定每条命令都完整回滚。不要在采样循环重复发送保存或模式设置。

依据：[发送方向，第 459 行](../software/firmware/src/rs485_proto.c#L459)，[接收缓冲，第 425 行](../software/firmware/src/rs485_proto.c#L425)，[拼帧恢复，第 985 行](../software/firmware/src/rs485_proto.c#L985)，[配置保存，第 319 行](../software/firmware/src/rs485_proto.c#L319)。

## 第一次通信

1. 单设备接入，按已知 ID 和波特率发送 `GET_INFO`，类型先用 ANY。
2. 确认返回 TYPE。固件出厂默认角度；直线 PCB 应明确切换到类型 2，再重新读取信息。
3. 按对应方法校准；直线先拿走磁铁采基线，再放回磁铁调宽度；角度转满至少一圈。
4. 确认旗标与机械安装，设置机械零点；再用 `GET_DATA` 轮询采集。
5. 主机按返回类型转换为 m 或 rad，并附主机接收时间。断链时停止产出新位置，进入上层故障处理。
6. 更换 PCB 为角度构型后，通信帧仍可复用；切换类型、重做校准和零点，并改变单位解释。

## 十六进制样例

以下帧由代码构造，CRC 已与原上位机 `crc16` 独立核对，未连接板卡抓包。

读取 ID=1 的信息，TYPE=ANY，SEQ=1：

```text
A5 5A 01 00 FF 01 01 00 61 8E
```

当前为角度类型 1 时，切换至直线类型 2，SEQ=3；成功应答已使用新类型 2：

```text
请求：A5 5A 01 00 01 07 03 01 02 47 25
应答：A5 5A 00 01 02 87 03 02 00 02 C5 72
```

读取 ID=1 的直线采样，SEQ=2。构造应答值为 -1250，即 -12.50 mm=-0.0125 m；幅度 1000，U1/U2/U4 为 2048/2200/2500，flags=2：

```text
请求：A5 5A 01 00 02 10 02 00 00 D7
应答：A5 5A 00 01 02 90 02 0E 00 1E FB FF FF E8 03 00 08 98 08 C4 09 02 59 BE
```

同一采样的主动帧没有状态字节，LEN=0x0D，SEQ=7：

```text
A5 5A 00 01 02 30 07 0D 1E FB FF FF E8 03 00 08 98 08 C4 09 02 09 CA
```

设置直线敏感宽度 8000 µm，SEQ=6：

```text
A5 5A 01 00 02 27 06 02 40 1F 65 62
```

若设备实际仍为角度类型，使用角度 TYPE 请求 `SET_SIGMA` 会收到状态 3；TYPE 写错成直线则会被静默忽略：

```text
角度设备的状态错误应答：A5 5A 00 01 01 A7 06 01 03 A5 F5
```

设置 20 ms 主动上报和恢复 20 ms 轮询分别为：

```text
主动：A5 5A 01 00 02 04 04 03 01 14 00 8A 60
轮询：A5 5A 01 00 02 04 05 03 00 14 00 E6 60
```

角度模式读数请求的对照帧（TYPE=1、SEQ=1）为 `A5 5A 01 00 01 10 01 00 00 63`。它与直线请求复用相同命令，数据的物理含义由类型决定。

继续阅读：[上位机与独立读取示例](Host-Tools.md) · [机器人接入](Robot-Integration.md)。
