# 固件结构

> 本页讲解仓库内 2026-10-27 的源码快照。原工程后来更新了直线算法及磁铁状态处理，尚未同步到这里；查看其他版本时请以对应源码为准。

我们先跟着三路 ADC 看一下代码：`hall_line_read()` 把它们换算成直线位置，`hall_encoder_read()` 用它们计算单圈角度。两种算法共用采样和 RS485，校准参数与计算方法不同。

先看直线部分，配合实物完成[校准与测量](Calibration-and-Measurement.md)，然后再看角度算法。

## 电路、机械和算法

- **传感器**感受局部磁场，输出模拟电压；它本身并不知道“关节转了几度”。
- **机械结构**让磁场随角度或位置有规律地变化。磁铁的充磁方向、气隙、偏心和 PCB 排布都是测量系统的一部分。
- **解算算法**用三路电压反推位置。PCB 构型改变以后，必须选择对应算法；仅仅换显示单位没有作用。

## 数据怎样流动

```text
磁铁相对 PCB 运动
    ↓
U1 / U2 / U4 模拟输出
    ↓ PA0 / PA1 / PA4
ADC 规则组顺序扫描 + 64 次过采样平均
    ↓ DMA_CH0 循环写入 uint16_t[3]
直线算法 hall_line_read() / 角度算法 hall_encoder_read()
    ↓ 原码、位置、幅度、状态位
RS485 协议响应或主动上报 → Python 上位机 / 机器人主机
    └ RTT 调试输出
```

采样实现见 [hall_encoder.c](../software/firmware/src/hall_encoder.c#L57)，测量模式选择见 [main.c](../software/firmware/src/main.c#L116)。

<div data-hall-animation="signal-chain"></div>

动画仅说明采样、计算和通信的先后关系，不代表实际时序或采样率。

## 从这些文件读起

1. [main.c](../software/firmware/src/main.c#L98)：初始化与主循环。初始化 SysTick、RTT、状态灯、ADC、RS485；随后持续读取当前类型的结果并处理协议。
2. [hall_encoder.c](../software/firmware/src/hall_encoder.c#L57)：先只看 ADC 与 DMA 的共享采样层；文件名带 encoder，但直线模式也依赖它。
3. [hall_line.c](../software/firmware/src/hall_line.c#L75)：基线、相邻信号比值、直线位置、校准与零点。读完再回到 hall_encoder.c 的角度部分。
4. [rs485_proto.c](../software/firmware/src/rs485_proto.c#L584)：命令执行、配置保存、数据打包与上报。协议字段定义集中在 [rs485_proto.h](../software/firmware/inc/rs485_proto.h#L8)。
5. [systick.c](../software/firmware/src/systick.c#L47) 与 [gd32e23x_it.c](../software/firmware/src/gd32e23x_it.c#L104)：毫秒时基与 USART 接收中断。

这是无 RTOS 的循环式应用。SysTick 提供 1 ms 软件时基，USART 接收通过中断进入协议缓冲区，ADC 转换由硬件持续运行。RTT 每 50 ms 打印一次，PB4 每 500 ms 翻转一次，因此完整亮灭周期约 1 s；这些时间都不能当作 ADC 更新率。依据：[main.c](../software/firmware/src/main.c#L128)、[systick.c](../software/firmware/src/systick.c#L47)。

## ADC 和 DMA

U1、U2、U4 分别进入 PA0/ADC_IN0、PA1/ADC_IN1、PA4/ADC_IN4。三个通道按这个顺序扫描，采样时间配置为 `ADC_SAMPLETIME_55POINT5`，DMA 通道 0 循环写入三项缓冲区。固件尝试开启 IRC28M 并选用其二分频作为 ADC 时钟；稳定等待失败时退回 APB2 四分频。依据：[hall_encoder.c](../software/firmware/src/hall_encoder.c#L61)。

64 倍过采样右移 6 位，相当于硬件平均，输出仍是 12 位、0…4095。平均能减小部分随机噪声，也会影响响应速度。注释中的“1～2 个码”缺少测量记录，需复测。依据：[hall_encoder.c](../software/firmware/src/hall_encoder.c#L98)、[hall_encoder.h](../software/firmware/inc/hall_encoder.h#L45)。

系统时钟配置为 `__SYSTEM_CLOCK_72M_PLL_IRC8M_DIV2`，即 72 MHz。构建目标是 GD32E230，使用 Cortex-M23、Thumb 和软件浮点参数；实际时钟和处理速度仍需测量。依据：[system_gd32e23x.c](../software/firmware/Firmware/CMSIS/GD/GD32E23x/Source/system_gd32e23x.c#L48)、[工具链文件](../software/firmware/cmake/gcc-arm-none-eabi.cmake#L143)。

## 直线位置怎么算

默认物理顺序是 U1、U2、U4，相邻间距 10 mm；对应坐标为 −10、0、+10 mm，正方向朝 U4。固件扣掉各路零磁场基线，选择绝对值最大的通道作为峰值；若峰为负，就把三路一起反号，因此主磁极翻转后仍使用同一正峰模型。依据：[hall_line.h](../software/firmware/inc/hall_line.h#L5)、[hall_line.c](../software/firmware/src/hall_line.c#L86)。

算法假定局部磁场幅值可用一个高斯形状近似：

```text
B(xi) ≈ A exp(−(xi − x)² / (2σ²))
x = (xi + xj)/2 + σ²/(xi − xj) × ln(Bi/Bj)
```

其中 `xi`、`xj` 是峰值与较强相邻通道的位置，`σ` 描述磁场曲线的有效宽度。默认 σ=8 mm，可配置 2…30 mm。它不是传感器间距、磁铁直径或自动测得的距离，需要在固定气隙和磁铁下通过参考位置拟合。实现采用 Q16 对数查表与整数运算。依据：[hall_line.c](../software/firmware/src/hall_line.c#L121)、[hall_line.h](../software/firmware/inc/hall_line.h#L18)。

没有可用正相邻信号，或相邻值小于峰值的 1/8 时，算法退回峰值通道的坐标；若峰值在端部，同时置 `OUT`。插值结果超出 ±15 mm 时会钳位并置 `OUT`。±15 mm 是内部限幅，减机械零点后显示范围会平移；可用行程需结合误差和状态位实测。依据：[hall_line.c](../software/firmware/src/hall_line.c#L121)。

当前算法只估计单个磁铁附近的位置，没有磁栅周期识别、磁极计数、跨段拼接或长行程绝对编码。

## 换成编码器时改什么

直线位置和参考尺对比完成后，我们再换上编码器 PCB，逐项看一下哪些部分可以继续用，哪些需要改：

- **保留采样层**：相同的 PA0/PA1/PA4、ADC 扫描与 DMA，仍读取 U1/U2/U4 三个原码。
- **保留通信层**：设备号、CRC、查询、上报与校准命令仍可复用；先读取类型，再解释数值和校准字段。
- **改变物理关系**：直线板的三颗传感器观察沿空间移动的局部场强；旋转板需要三路随角度呈约 120° 相差的周期信号。
- **改变解算器**：`hall_line_read()` 用基线、相邻比值和 σ估计位置；`hall_encoder_read()` 用通道偏置、相对增益和二维向量相位估计角度。两个求解器独立，不能用同一套标定参数互换。
- **改变校准动作**：直线先拿走磁铁取基线；角度保留磁铁并转满一圈采极值。机械零点都是后续的坐标平移，角度还要处理 360° 环绕。

换板后先读取设备信息，用 `SET_PROFILE (0x07)` 选择角度或直线，再按新模式重新校准、设零、重启验证。改变类型时会清除旧校准和零点并写入 Flash；仅重新烧录通常保留配置，因此换板后不能假定程序会自动识别 PCB。依据：[rs485_proto.c](../software/firmware/src/rs485_proto.c#L902)。

## 单圈角度怎么算

源码约定从上往下看逆时针为正，U1、U2、U4 的测量相位依次为 0°、120°、240°。在理想模型中，扣掉直流偏置后的三路信号为：

```text
s0 = A cos θ
s1 = A cos(θ − 120°)
s2 = A cos(θ − 240°)
```

先慢转磁铁，看实际波形是否接近这个模型，再检查方向与敏感轴。旋转安装约定见 [hall_encoder.h](../software/firmware/inc/hall_encoder.h#L11)。

算法先按已保存的偏置与 Q12 增益修正每一路；若尚未校准，则直接使用原码。随后都减去三路平均值，再计算：

```text
alpha = 2 × s0 − s1 − s2       ≈ 3A cos θ
beta  = √3 × (s1 − s2)        ≈ 3A sin θ
θ = atan2(beta, alpha)
```

实现使用 `√3 ≈ 887/512`、整数近似反正切和整数平方根。角度归一化并减去机械零点后返回 0…35999，编码单位 0.01°；幅度约为向量长度除以 3，单位是 ADC 计数。依据：[hall_encoder.c](../software/firmware/src/hall_encoder.c#L216)。

角度只在一圈内，没有固件圈数累计、掉电圈数保存或直接转速输出。主机如何处理回绕、超时和减速比，见[机器人接入](Robot-Integration.md)。

## 同一字段，两种单位

`hall_sample_t` 定义于 [hall_encoder.h](../software/firmware/inc/hall_encoder.h#L45)：`raw[3]` 是原码，`amplitude` 是信号幅度，`angle_cdeg` 是位置数值。

同一个 `angle_cdeg` 在角度模式表示 0.01°，在直线模式表示有符号 0.01 mm。通信和应用程序必须同时读取设备类型；直接把这个字段始终解释为角度，会得到看似正常但单位错误的数据。依据：[hall_line.h](../software/firmware/inc/hall_line.h#L10)、[rs485_proto.h](../software/firmware/inc/rs485_proto.h#L25)。

## 启动和保存配置

未发现有效 Flash 配置时，设备号为 1、115200 baud、8N1、主从查询模式、上报周期参数 20 ms、角度模式、零点为 0、校准无效。角度模式初始增益是 4096，即 Q12 的 1 倍。上报周期参数存在，并不表示默认会自动上传。依据：[rs485_proto.c](../software/firmware/src/rs485_proto.c#L143)、[rs485_proto.c](../software/firmware/src/rs485_proto.c#L161)。

切到直线模式时，基线尚未校准就按 2048 解算，σ设为 8000 µm。改变测量类型会清除两种模式的零点与校准，并保存配置；它不会保留两套可随时来回切换的独立完整校准。依据：[rs485_proto.c](../software/firmware/src/rs485_proto.c#L248)。

32 字节配置含校验码，放在 `0x08007C00`。链接脚本只向程序分配前 31 KB Flash，避免占用最后 1 KB 配置页。保存过程擦页、逐字写入，期间关中断。不要把写 Flash 命令放进每周期控制循环；目前没有磨损均衡或双副本掉电提交。依据：[rs485_proto.c](../software/firmware/src/rs485_proto.c#L319)、[链接脚本](../software/firmware/ldscripts/gd32e23x_flash.ld#L28)。

## 改代码前检查

- **三路采样**：`hall_encoder_copy_raw()` 只是依次读三项 `volatile` DMA 内存，没有完整扫描帧锁存、序号或时间戳。DMA 可能在复制途中更新某一路，高速运动时要验证跨通道时差，后续可用双缓冲或扫描完成事件建立一致快照。依据：[hall_encoder.c](../software/firmware/src/hall_encoder.c#L299)。
- **通道顺序**：`HALL_LINE_LEFT/MID/RIGHT` 的注释建议改下标，但当前计算并没有用 LEFT/RIGHT 重排 `raw`；`s_pos_um` 仍固定对应 raw[0…2]。改 PCB 排列时，应检查采样映射与位置数组，不能只改这三个宏。依据：[hall_line.h](../software/firmware/inc/hall_line.h#L24)、[hall_line.c](../software/firmware/src/hall_line.c#L23)。
- **状态位**：`CAL_VALID` 只说明校准数据被接受；`WEAK` 只比较幅度；`OUT` 由前述插值和限幅条件设置。没有单独的饱和检测、磁铁脱落诊断或误差上界。无磁场仍可能得到一个数，必须检查状态。详见[校准与测量](Calibration-and-Measurement.md)。
- **处理时间**：持续采样、RTT 打印、协议发送和 Flash 保存共享有限 MCU 资源，源码没有每帧时间戳或最坏响应时间报告。软件上报周期不等于端到端延迟、传感器带宽或闭环控制周期。

## 读完可以试什么

刚开始可以先让磁铁静止，记录三路原码，然后移动或旋转磁铁，看哪一路先增大。再分别完成两种校准，对照着看“电压偏置”和“机械零点”的区别。

如果已经做过机器人项目，可以增加样本序号与采样时间戳，在主机上计算环绕后的角速度，再对照外部参考测量延迟和误差。把这些数据记录下来，再判断它适合哪一种关节、滑台或夹爪实验。
