# 校准与测量

> 本页对应仓库内 2026-10-27 的固件快照。原工程后续修改了直线算法、参数和磁铁状态处理；使用新版本前请核对对应源码和上位机。

我们先把三路原码调到正常，然后校准和设零，最后与独立参考尺比较。直线校准要拿开磁铁，角度校准要带着磁铁转满一圈；两种动作不能互换。

## 准备

确认 PCB 类型、磁铁固定方式、供电和通信正常；先只连一块传感器，读取三路 ADC 原码。它们应在 12 位范围内，并随预期运动变化。不要一开始就把异常读数通过“设零点”隐藏起来。

**电气校准**处理偏置或相对幅值，**机械零点**选择位置坐标原点。顺序是机械装配固定 → 电气校准 → 检查信号 → 机械零点 → 参考位置验证。校准、零点和直线宽度成功修改后，协议命令会写入 Flash，不需要每次再发 `SAVE`。相关实现：[rs485_proto.c](../software/firmware/src/rs485_proto.c#L768)、[rs485_proto.c](../software/firmware/src/rs485_proto.c#L838)、[rs485_proto.c](../software/firmware/src/rs485_proto.c#L931)。

上位机提供“开始校准”“结束校准”“清除校准”“设当前位置为零”等操作；对应命令定义见 [rs485_proto.h](../software/firmware/inc/rs485_proto.h#L49)。切换测量类型会清掉原有校准和零点，务必先选对类型。

原 GUI 的 `GET_CAL` 参数显示有错位问题，查看基线和增益时请按[协议](Protocol-Reference.md)核对原始应答，详见[已知限制](Known-Limitations.md)。它不改变固件保存的参数；新增只读示例也不读取这些参数。

## 直线基线

1. 选择“直线位置”。确认 U1、U2、U4 沿直线依次排列，相邻间距为本版本默认的 10 mm。
2. 将磁铁和明显的其它磁源移离感应区，使三路原码回到稳定基线附近。**直线校准期间不要来回扫过磁铁。**
3. 点击“开始校准”，稍等稳定后点击“结束校准”。
4. 读取校准，确认三路基线和 `CAL_VALID`；放回磁铁，保持固定气隙，缓慢滑动，观察位置与 `weak/out`。
5. 使用参考尺或直线台确定机械原点，设当前位置为零；然后调整宽度并做参考位置验证。

直线校准累计三路原码的算术平均值作为各自基线。至少需要 16 次 `hall_line_read()`，最多累计前 4096 次读取；采集完成只说明读取次数足够，没有自动检查“磁铁真的已经拿开”。依据：[hall_line.c](../software/firmware/src/hall_line.c#L201)、[hall_line.c](../software/firmware/src/hall_line.c#L269)。

这是软件读取次数，主循环可能重复读取同一组 DMA 数据；超过前 4096 次后也不会继续累加。未校准时内部用 2048，`GET_CAL` 则返回基线 0 和无效标志，不能把这个 0 当成模拟输出。依据：[hall_line.c](../software/firmware/src/hall_line.c#L155)。

基线校准只消除三路零磁场偏置，没有自动标定每一路灵敏度或磁铁形状。磁铁仍放在阵列上时采基线，会把有效磁场错误地扣掉。

<div data-hall-animation="calibration"></div>

这里用动画看一下基线扣除和机械零点的区别，曲线与数值为原理示意，并非板卡实测。

## 调整宽度 σ

默认 σ=8 mm，配置范围 2…30 mm；协议 `SET_SIGMA (0x27)` 传输 `uint16` 微米，例如 8000 表示 8 mm。它只在直线模式有效，成功后写 Flash。依据：[hall_line.h](../software/firmware/inc/hall_line.h#L18)、[rs485_proto.c](../software/firmware/src/rs485_proto.c#L931)。

σ描述当前安装下磁场曲线的宽度。先固定磁铁姿态和气隙，然后再调这个参数：

1. 校准基线，保持磁铁姿态不变，选若干已知位置，记录三路原码与参考位置。
2. 用相同原码离线运行位置公式，比较不同 σ；再将少量候选值写入设备验证。这样可以避免在扫参数时反复擦写 Flash。
3. 用一组位置选参数，再用另一组位置验证，至少分别包括中心附近、传感器交接处和两端附近。
4. 如中心很好、两端误差明显，先检查模型与安装；不要只靠机械零点去补偿所有误差。

换磁铁或改变气隙后要重新检查 σ。靠近阵列两端时缺少另一侧信号，误差往往更难约束；±15 mm 的软件限幅不能当作等精度行程。

## 换板后重新校准

HallPos 的“零磁场平均值”估计每颗传感器的静态基线，σ对应特定安装下的磁场空间宽度；Encoder 的“一圈极值”同时估计每路偏置与相对增益。它们共用 `CAL_BEGIN/CAL_END` 命令，但设备根据类型执行不同动作。

完成直线课程后再换旋转板：读取当前类型 → 切到角度模式 → 保持磁铁安装并转满一圈校准 → 设机械零点 → 验证一圈误差。切换类型会清除原有标定与零点；先把直线实验参数记下来，不能把旧 σ当作角度增益复用。共用参数槽位的解释见 [rs485_proto.c](../software/firmware/src/rs485_proto.c#L178)。

## 角度校准

1. 固定磁铁气隙和中心，选择“角度编码器”。
2. 点击“开始校准”，或发送 `CAL_BEGIN (0x23)`，空载荷。
3. 缓慢、连续地旋转磁铁至少一整圈，使三路都覆盖其峰值和谷值。允许多转几圈；避免磁铁在旋转时上下移动或偏心改变。
4. 点击“结束校准”，或发送 `CAL_END (0x24)`。
5. 成功后读取 `GET_CAL (0x26)`，检查三路偏置、增益与 `CAL_VALID`。随后把机械结构放到选定原点，用空载荷 `SET_ZERO (0x21)` 捕获当前角度为零。
6. 重启并读取信息，验证保存，再做多角度参考测量。

校准记录每一路的最小值与最大值，然后计算：

```text
span[i]   = max[i] − min[i]
offset[i] = (max[i] + min[i]) / 2
gain[i]   = span[0] / span[i]
```

增益在固件中为 Q12 整数，4096 代表 1 倍，以 U1 的峰峰值作为幅值基准。每一路峰峰值都必须至少 400 个 ADC 计数，并且相对增益可装进 `uint16_t`；否则结束校准返回“数据不足”，仍保持采集中，可继续转动后再结束。依据：[hall_encoder.c](../software/firmware/src/hall_encoder.c#L355)。

代码只检查极值和阈值，不检查角度覆盖或圈数；是否完整转过一圈，要由操作者和参考装置确认。

这一步补偿各通道的静态偏置和幅值差，相位误差、偏心、谐波、温漂和动态滞后仍在。未校准时也会减三路平均值并输出角度。依据：[hall_encoder.c](../software/firmware/src/hall_encoder.c#L229)。

## 状态位

- `WEAK = 0x01`：幅度小于 80 个 ADC 计数。角度幅度是合成向量长度除以 3；直线幅度是扣基线后最大一路的绝对幅值。两种模式的幅度定义不同，不能直接当作同一磁场单位比较。依据：[hall_encoder.h](../software/firmware/inc/hall_encoder.h#L24)、[hall_encoder.c](../software/firmware/src/hall_encoder.c#L263)、[hall_line.c](../software/firmware/src/hall_line.c#L90)。
- `CAL_VALID = 0x02`：存在被算法接受的校准数据。它不是精度达标、磁铁存在或当前位置可信的证明。
- `CAL_RUN = 0x04`：正在记录校准。校准结束失败时可以继续保持这个状态。
- `OUT = 0x08`：直线算法在某些端部退化情况或位置钳位时设置。它是模型的边界提示，没有独立测距器确认磁铁真的越界；它也不覆盖所有模型失配。

实际置位函数见 [hall_encoder.c](../software/firmware/src/hall_encoder.c#L315)、[hall_line.c](../software/firmware/src/hall_line.c#L284)。上层程序应保留这些标志和数据时间，而不是只保存一个浮点位置。

## 怎样理解精度

**输出单位**：角度以 0.01° 的整数传输，位置以 0.01 mm 的有符号整数传输。这只决定数值如何编码。它不等于单次读数能分辨这么小的实际运动。

**ADC 位宽**：原码是 12 位范围。64 次平均后仍右移 6 位，因此输出没有增加额外位数。有效位数需要静态噪声、失真等测量条件才能估算，源码没有足够证据给出整机 ENOB。

**算法误差**：角度函数是整数近似 `atan2`。把源码第一象限公式对所有 65537 个 Q16 比值与标准 `atan` 比较，函数本身最大偏差约 **0.23518°**。这个数只描述反正切近似，既不包含 ADC 量化，也不包含磁铁、相位、装配和温度误差，不能作为整机精度规格。算法依据：[hall_encoder.c](../software/firmware/src/hall_encoder.c#L163)。

下列独立计算可复现这个有限枚举结果，适合课堂比较“编码单位”和“算法误差”：

```python
import math
max_error = 0.0
for z in range(65537):
    approx_cdeg = ((z * 4500) >> 16) + ((z * (65536 - z) * 1564) >> 32)
    true_deg = math.degrees(math.atan(z / 65536))
    max_error = max(max_error, abs(approx_cdeg / 100 - true_deg))
print(max_error)  # 约 0.23517668°，仅该近似函数的比值网格实验
```

**重复性**：同一位置反复测量的离散程度。**准确度**：测量值接近参考真值的程度。把固定位置的输出平均得很平滑，只能说明某些随机误差变小，不能消除固定偏差、非线性或模型错误。

**响应速度**：20 ms 的上报参数与 50 ms 的 RTT 打印周期都不是传感器带宽。三路扫描非同时采样，64 倍平均也引入时间窗口；目前没有样本时间戳或完整 DMA 帧锁存，应通过动态实验确定可接受速度。

## 先观察静止和移动

先做一组简单的测试：固定磁铁，连续采集静止原码；接着做一个明显小位移或小角度转动，观察原码是否变化；最后观察算法输出和状态位。比较有磁铁、移开磁铁与恢复原位三种情况。

记录环境、磁铁方向、气隙、板卡版本、模式、基线或增益、σ、时间、原码、位置、幅度和标志。回答三个问题：三路为什么不同？设零点前后原码变了吗？为什么无磁铁时也可能出现一个角度数字？

## 用参考装置测误差

角度可用有独立刻度或外部编码器的旋转台；直线可用已验证的直线台、量块或其它参考尺。参考装置的不确定度应显著小于要评估的误差，并记录它的来源。

1. 在整个待测范围选取至少 9 个位置，包含中心、通道交接处和边缘；角度覆盖一整圈。
2. 每个位置等待稳定，采集一段数据，分别做正向和反向逼近；重复至少 3 轮。校准点与最终验证点分开选择。
3. 计算每点平均误差、静止标准差、重复测量离散和最大绝对误差，保存状态位。
4. 改变气隙、供电条件或环境温度后重复一组点，观察参数是否还能复用。

直线误差为 `测量位置 − 参考位置`。角度误差要处理 0°/360° 环绕：

```python
error_deg = (measured_deg - reference_deg + 180) % 360 - 180
```

结果应写为“在某板卡、某磁铁、某气隙、某温度和采样条件下，测得某个误差范围”，而不是只写“精度 0.01”。没有实测数据前，项目不给出毫米级或角度级准确度保证。

## 测延迟和异常

让磁铁按已知速度运动，主机记录接收时间和外部参考；逐步增加速度，观察误差、跨通道时差、丢包与延迟。主动上报的总线先只开一台设备，避免总线竞争；多传感器从主从查询开始。

模拟磁铁移开、机械越界、串口断开和设备重启，验证主机在 `WEAK/OUT`、超时或类型变化时如何停止使用位置。当前固件没有独立传感器脱落或饱和标志，接近 ADC 上下限也需要主机或后续固件单独检查。通信校验通过只能说明字节传输被接受，不能说明物理位置正确。

之后再接入低速、低能量的机器人演示。若继续改进，可加入 DMA 快照、采样时间戳、超时处理、相位校正或查表补偿，并用同一套参考实验比较改动前后。
