# 上位机和读取示例

> 本页对应仓库内 2026-10-27 的上位机及固件快照。原工程后来已有修改，实物操作与录屏请使用互相匹配的固件和上位机。

我们先看一下怎么把数据读到电脑上。观察波形、操作校准，可以用原图形上位机；想先看数据格式和单位，可以用独立命令行示例。这里先读直线位置，然后再换成角度数据。

## 不接硬件先运行

从仓库根目录执行，建议 Python 3.10 或更新版本：

```powershell
python software/examples/read_sensor.py --demo --profile line --count 5
python software/examples/read_sensor.py --demo --profile encoder --count 5
```

直接执行 `python software/examples/read_sensor.py` 也会运行直线演示，不连接硬件，不需要第三方包。示例输出每行一个 JSON，含：

- `profile`：line 或 encoder。
- `raw_value/raw_unit`：协议整数和 `0.01_mm` 或 `0.01_deg`。
- `display_value/display_unit`：便于人看的 mm 或 deg。
- `si_value/si_unit`：便于机器人计算的 m 或 rad。
- `adc_u1_u2_u4`、`amplitude_adc_counts`、`flags/flag_names`：原码、幅度与状态。
- `calibration_valid_flag`、`measurement_warnings`：校准位，以及弱磁、未校准、校准中和越界提示。异常样本保留供分析，接控制器前需筛选。
- `host_rx_unix_s`、`host_rx_monotonic_s`：主机接收解码时间及本机单调时间。
- `source=simulated_not_hardware`：模拟数据标识。

时间字段是主机解码时刻。角度演示使用三相余弦，直线演示使用简化高斯曲线；实际信号还受磁铁、安装、噪声和温度影响，详见[测量说明](Calibration-and-Measurement.md)。

源码：[独立协议模块](../software/examples/hall_protocol.py) · [只读示例](../software/examples/read_sensor.py)。

## 用串口读取

准备接实物时，先确认一块板的供电、RS485 接线、USB-RS485 适配器和设备配置，然后执行：

```powershell
python -m pip install pyserial
python software/examples/read_sensor.py --port COM7 --id 1 --baud 115200 --count 20
```

将 COM7 换成实际串口。Linux 可替换为 `/dev/ttyUSB0`。示例先 `GET_INFO`，再按实际返回的测量类型轮询 `GET_DATA`；`--profile line` 只控制演示，不会给真实设备切换模式。固件出厂默认角度，使用直线 PCB 时先按实验步骤切换并校准。

示例默认 20 帧后结束，`--count 0` 为持续读取；Ctrl+C 停止。`--interval-ms 100` 表示每次成功读取后等待 100 ms，实际周期还包括请求、应答和调度延迟；所以实际采集率会低于 10 Hz。串口超时、错误应答或拔出导致的异常会结束程序并返回 2，不继续重复最后一个位置。

返回模式、周期、波特率与类型需符合当前协议，信息中的校准有效字节也需与标志一致；12 位 ADC、单圈角度和未知状态位会检查。直线值保留机械零点偏移后的有符号结果，不在主机再次限幅。解析器支持 CRC 错误和截断帧后的恢复，但持续丢包仍会触发事务超时并停止。

示例只发读取命令，不改配置，独立于原 GUI，也不需要 Qt。机器人接口扩展见[机器人接入](Robot-Integration.md)。

## 启动图形上位机

再看一下图形上位机。保留的原上位机位于 `software/host/`，依赖文件列出 pyserial、PySide6、pyqtgraph、NumPy，入口为 `host/__main__.py`。从仓库根目录可按以下方式建立独立环境：

```powershell
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r software/host/requirements.txt
Set-Location software
..\.venv\Scripts\python.exe -m host
```

若已在 `software` 目录且当前环境装好依赖，可直接运行 `python -m host`。这里提供源码启动方式，尚未连接板卡测试 GUI。

依据：[依赖文件](../software/host/requirements.txt)，[模块入口](../software/host/__main__.py)，[GUI 入口，第 931 行](../software/host/app.py#L931)。

## GUI 演示

1. 勾选“无设备演示”，设置演示 RPM。
2. 点击“开始采集”。看三路原码的相位关系，以及角度按 0–360°和连续展开的区别。
3. 用“开始录制”导出 CSV，再停止录制。

GUI 的无设备演示固定为角度，与之前选的类型无关。直线演示请用前面的命令行示例。

依据：[演示模式入口，第 573 行](../software/host/app.py#L573)，[模拟生成，第 803 行](../software/host/app.py#L803)。

## 接板卡采集

### 直线板

1. 刷新串口；初次单板连接通常选 ID 1、115200、类型“不限”，再点连接。
2. 连接动作会读设备信息；看到“角度编码器”并不代表 PCB 已自动识别。固件默认类型是角度；点击“切换为直线位置”会写入类型并清除原零点和校准。
3. **先拿开磁铁**，点开始校准，保持采集一段时间后点结束校准。这里采的是零磁场基线，不是移动全行程拟合。
4. 放回磁铁，保持高度与姿态，观察 U1/U2/U4 和位置。敏感宽度默认 8 mm，是模型参数；需要用已知机械位置检查误差并调整。
5. 把滑台移到选定的机械参考位置，再点“当前位置设为 0 mm”。基线、宽度和机械零点是不同概念。
6. 先选“主从查询”、间隔 20 ms，开始采集；核对移动方向、弱磁和超出阵列状态。

### 换成角度板

1. 换好磁铁与角度 PCB，确认机械结构，再切换为角度编码器。切换会清除两种模式校准和零点。
2. 保持磁铁安装关系，开始校准并转满至少一圈，再结束校准。
3. 在明确的机械参考位置设零；观察转动方向和 359°→0°回卷。
4. 按实际类型改用 deg/rad；同一 `angle_cdeg` 字段从直线的 0.01 mm 回到角度的 0.01°。

依据：[GUI 校准与切换动作，第 601 行](../software/host/app.py#L601)，[宽度控件，第 437 行](../software/host/app.py#L437)，[固件切换逻辑，第 902 行](../software/firmware/src/rs485_proto.c#L902)。

## 显示和 CSV

GUI 显示三路 ADC 原码、幅度、位置/角度和差分估算速度；角度速度为 RPM，直线速度为 mm/s。速度由主机收到的相邻位置和时间差估算，并做平滑，不是传感器直接返回的速度。连续角度展开也由主机完成，不能当成掉电保存的多圈编码器。

角度 CSV 表头为 `t_s, angle_deg, amplitude, u1, u2, u4, flags, rpm`；直线为 `t_s, position_mm, amplitude, u1, u2, u4, flags, mm_s`。CSV 的 `t_s` 来源是 Python `time.perf_counter()`，不是 Unix 时间，也不是从零开始的实验时间；分析时可减第一帧时间求相对秒数。导出的角度保持 0–360°，即使图上显示连续展开角度也一样。

依据：[时间与采样解析，第 159 行](../software/host/bus.py#L159)，[速度与展开，第 193 行](../software/host/app.py#L193)，[CSV 表头，第 627 行](../software/host/app.py#L627)，[CSV 行，第 785 行](../software/host/app.py#L785)。

## 原 GUI 的几个问题

对照原 GUI 源码，可以看到下面几个问题，接实物时需要留意：

- **改 ID 后可能报超时，但设备其实已经改成功。** 固件应答 SRC 已是新 ID，GUI 事务匹配仍要求旧 ID。只接一台板操作，超时后断开，使用新 ID 重新连接并读信息；若未连上，再检查旧 ID。由非 1 号恢复出厂也会触发同类问题，此时先检查 ID 1/115200。依据：[固件设置 ID，第 650 行](../software/firmware/src/rs485_proto.c#L650)，[GUI 事务匹配，第 372 行](../software/host/bus.py#L372)。
- **读取校准时，部分基线/宽度显示会错位。** `GET_CAL` 布局含 flags，`CAL_END` 布局不含 flags；GUI `_after_cal()` 用同一偏移处理两者。不要把错位显示当作硬件基线异常，也不要据此回写宽度。依据：[读取校准调用，第 454 行](../software/host/bus.py#L454)，[校准解析，第 526 行](../software/host/bus.py#L526)，[实际协议布局，第 883 行](../software/firmware/src/rs485_proto.c#L883)。
- **主动上报时点“停止采集”不等于让设备停止发送。** `stream off` 只更改本机状态；后台仍收包，设备的上报模式也会保存在 Flash。需要恢复总线轮询时，选择“主从查询”并开始一次采集，让主机发 `SET_MODE=0`，再停止。多设备接入前必须检查该配置。依据：[采集模式，第 459 行](../software/host/bus.py#L459)，[收包分发，第 410 行](../software/host/bus.py#L410)。
- **“已连接”首先表示串口已打开。** 打开后读信息失败只记录日志，并不自动证明选对了设备。轮询超时也只限频记日志；显示可能保留旧值。机器人必须另外实现超时与新鲜度判断。依据：[串口连接，第 339 行](../software/host/bus.py#L339)，[轮询与信息失败，第 421 行](../software/host/bus.py#L421)。

独立示例只读且遇错停止；校准操作仍需使用原工具或按协议发送命令。

继续阅读：[协议字节参考](Protocol-Reference.md) · [机器人接入和断链处理](Robot-Integration.md)。
