From 9bbeb9d1f92786085a210797da31ca1281ea3ae8 Mon Sep 17 00:00:00 2001 From: pNexus Date: Thu, 30 Apr 2026 18:09:37 +0800 Subject: [PATCH] docs(GML670): update GML670x4_V2 README --- README.md | 161 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 92 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index bf9991b..7408e69 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # GML670 Balance Board -> 当前目录是 `GML670x4_V1`。代码内的 CMake 项目名为 `GML670_V2`,BLE 设备名为 `GML670_System`。 +> 当前目录是 `GML670x4_V2`。代码内的 CMake 项目名是 `GML670_V2`,BLE 设备名是 `GML670_System`。 > -> 本文档是当前工程的总入口说明,重点覆盖已落地的固件行为。更细的协议字段和示例帧见 [BALANCE_BOARD_PROTOCOL.md](./BALANCE_BOARD_PROTOCOL.md)。 +> 这份 README 只描述当前 `V2` 工程已经落地的实现。更细的协议字段和示例帧见 [BALANCE_BOARD_PROTOCOL.md](./BALANCE_BOARD_PROTOCOL.md)。 --- @@ -23,14 +23,16 @@ ## 1. 产品概述 -本工程是平衡板的 nRF5340 App Core 固件,当前版本的核心能力是: +本工程是平衡板的 nRF5340 App Core 固件,当前版本的主链路很简单: -- 通过 ADS1256 轮询 4 路传感器输入 -- 在板端完成去皮、中值滤波和 CoP(Center of Pressure,压力中心)解算 -- 通过 BLE NUS 以二进制帧持续上报 CoP 数据 -- 接收基站下发的阻力参数、Spotter 模式和心跳帧 +- 上电后等待 2 秒,初始化 BLE NUS 传输层并启动广播 +- 初始化 ADS1256,完成一次 tare 去皮 +- 创建独立的传感器线程,循环读取 4 路差分输入 +- 在板端完成重量换算和 CoP(Center of Pressure,压力中心)解算 +- 通过 BLE NUS 持续上报 17 字节 CoP 二进制帧 +- 接收基站下发的阻力参数、Spotter 参数和心跳帧,并保存在内存里 -当前实现的上行主数据是固定 17 字节的 CoP 帧,发送频率为 50 Hz。阻力、Spotter、Heartbeat 的下行协议已经接入接收路径,但控制逻辑还没有继续向执行器或保护动作落地。 +当前实现已经把采样、解算和上行链路拆成了独立模块:`main.c` 只负责启动,BLE 在 `src/ble_transport.c`,ADS1256 在 `src/ads1256.c`,采样与 CoP 在 `src/sensor.c`。 ## 2. 硬件组成 @@ -40,49 +42,47 @@ |------|-------------|------|------|------| | 主控 | nRF5340 DK App Core | - | 运行 BLE、SPI 采样和 CoP 解算 | ✅ | | ADC | ADS1256 | SPI1 | 4 路多通道采样 | ✅ | -| 传感器输入 | 4 路差分输入 | ADS1256 MUX | 用于压力类信号采样 | ✅ | +| 传感器输入 | 4 路差分输入 | ADS1256 MUX | 用于四角重量采样 | ✅ | | 基站链路 | Nordic UART Service | BLE | 上报 CoP、接收控制参数 | ✅ | -| 编码器链路 | 预留 | BLE 协议 | 编码器上行帧结构已定义,固件未发送 | 🔲 | +| 编码器链路 | 预留 | BLE 协议 | 编码器上行帧已定义,当前固件不发送 | 🔲 | ### ADS1256 相关引脚 -`app.overlay` 当前把 ADS1256 绑定到 `spi1`,连线如下: +`app.overlay` 当前把 ADS1256 接到 `spi1`,连线如下: | 信号 | nRF5340 引脚 | 说明 | |------|--------------|------| | SCK | `P1.15` | `spi1` 时钟 | | MOSI | `P1.13` | `spi1` 主发从收 | | MISO | `P1.14` | `spi1` 主收从发 | -| CS | `P1.12` | 片选,`GPIO_ACTIVE_LOW` | +| CS | `P1.12` | 片选 | | DRDY | `P1.11` | 数据就绪输入 | | RESET | `P1.10` | ADS1256 复位控制 | +虽然 `app.overlay` 里有 `ads1256@0` 节点,但当前实现没有走单独的 Zephyr ADS1256 驱动,实际访问路径还是 `src/ads1256.c` 里直接操作 `spi1` 和 GPIO alias。 + ## 3. 系统架构 ```text -4 路传感器输入 +4 路差分传感器 | v - ADS1256 + ADS1256 + SPI1 | v - SPI1 + src/ads1256.c | v - nRF5340 App Core + src/sensor.c | - +--> tare / 7 点中值滤波 / CoP 解算 + +--> 去皮 / 17 次均值 / 死区抑制 / 重量换算 / CoP 解算 | - +--> BLE NUS Notify --> 基站 + +--> CoP 二进制帧 --> src/ble_transport.c --> BLE NUS Notify | - +<-- BLE NUS Write <-- 基站 - | | | - | | +-- Heartbeat - | +------------ Spotter 参数 - +--------------------- Resistance 参数 + +<-- Resistance / Spotter / Heartbeat <-- BLE NUS Write ``` -当前主循环完成 4 路轮询采样、滤波、CoP 解算和上行发送。下行方向只负责解析三类控制帧并保存参数,参数接收后会记录到内存并打印日志,不会继续驱动执行机构。 +`src/main.c` 本身不承载采样逻辑,它只按顺序调用 `ble_transport_init()`、`ble_transport_adv_start()` 和 `sensor_init()`。 ## 4. 功能需求与当前状态 @@ -90,21 +90,23 @@ | 项目 | 说明 | 状态 | |------|------|------| -| 4 路轮询采样 | 通过 ADS1256 MUX 依次读取 `0x01 / 0x23 / 0x45 / 0x67` 四组通道 | ✅ | -| tare 去皮 | 启动阶段每路取 7 个样本,中值作为零点偏移 | ✅ | -| 中值滤波 | 每路保留最近 7 个样本,排序后取中位数 | ✅ | -| 死区抑制 | 中值落在 `-80 ~ 80` 时压到 `0` | ✅ | -| CoP 解算 | 根据四角力值计算 `cop_x / cop_y / force` | ✅ | -| 力值标定 | `ADC_TO_FORCE_SCALE` 仍为占位值 `1.0f` | 🔲 | +| 4 路轮询采样 | 通过 ADS1256 MUX 依次读取 `0x01 / 0x23 / 0x45 / 0x67` 四组差分通道 | ✅ | +| tare 去皮 | 每路读取 17 个样本,排序后取中位数作为零点偏移 | ✅ | +| 运行期滤波 | 每帧每通道采 17 次并取均值 | ✅ | +| 死区抑制 | 去皮后的均值落在 `-250 ~ 250` counts 时压到 `0` | ✅ | +| 重量换算 | `ADC_TO_FORCE_SCALE` 已按 GML670 50 kg 传感器参数推导 | ✅ | +| CoP 解算 | 根据四角重量计算 `cop_x / cop_y / force` | ✅ | +| 有效力阈值 | 总重量低于 `0.5 kg` 时,CoP 坐标置零并清除 `force_valid` | ✅ | ### 4.2 BLE 通信 | 项目 | 说明 | 状态 | |------|------|------| | BLE 角色 | `Peripheral`,使用 NUS 私有服务 | ✅ | -| 广播 | 广播包带 NUS UUID,扫描响应带设备名 | ✅ | +| 广播内容 | 广播包带 NUS UUID,扫描响应带设备名 | ✅ | | 连接参数请求 | 连接后请求 `7.5-15 ms` 区间 | ✅ | -| CoP 上行 | 固定 17 字节二进制帧,50 Hz | ✅ | +| CoP 上行 | 固定 17 字节二进制帧,目标频率约 `50 Hz` | ✅ | +| NUS 发送保护 | 仅在连接存在且通知已使能时发送,失败时短间隔重试 3 次 | ✅ | | 断线重广播 | 断开 1 秒后重新广播 | ✅ | | NUS 下行解析 | Resistance / Spotter / Heartbeat 三类帧已接收解析 | ✅ | | 编码器上行 | 协议已预留,主循环未发送 | 🔲 | @@ -113,9 +115,9 @@ | 项目 | 说明 | 状态 | |------|------|------| -| Resistance 参数接收 | 接收 `K / B / Tau` 并保存到内存 | ✅ | -| Spotter 参数接收 | 接收 `threshold / enable` 并保存到内存 | ✅ | -| Heartbeat 接收 | 接收后刷新时间戳 | ✅ | +| Resistance 参数接收 | 接收 `K / B / Tau` 并保存到传输层静态变量 | ✅ | +| Spotter 参数接收 | 接收 `threshold / enable` 并保存到传输层静态变量 | ✅ | +| Heartbeat 接收 | 接收后刷新最近心跳时间戳 | ✅ | | 阻力闭环控制 | 参数已接入协议层,控制逻辑未落地 | 🔲 | | Spotter 保护动作 | 参数已接入协议层,保护动作未落地 | 🔲 | @@ -125,7 +127,7 @@ - 上行方向:平衡板 -> 基站,使用 NUS `Notify` - 下行方向:基站 -> 平衡板,使用 NUS `Write` -- 广播行为:广播包包含 NUS 128-bit UUID,扫描响应包含设备名 `GML670_System` +- 广播内容:广播包带 NUS UUID,扫描响应带设备名 `GML670_System` ### 5.1 NUS UUID @@ -139,7 +141,7 @@ | 参数 | 当前值 | 说明 | |------|--------|------| -| 广播模式 | `BT_LE_ADV_CONN_FAST_2` | 上电和断线后通过同一入口启动 | +| 广播模式 | `BT_LE_ADV_CONN_FAST_2` | 上电和断线后走同一入口启动 | | 连接间隔请求 | `7.5-15 ms` | `interval_min = 6`,`interval_max = 12` | | 从站延迟 | `0` | 不跳过连接事件 | | 监督超时 | `4000 ms` | `timeout = 400` | @@ -148,7 +150,7 @@ | 类型名 | 值 | 方向 | 长度 | 频率 | 说明 | |--------|----|------|------|------|------| -| `PROTO_TYPE_COP` | `0x01` | 平衡板 -> 基站 | `17` | `50 Hz` | CoP 上行帧 | +| `PROTO_TYPE_COP` | `0x01` | 平衡板 -> 基站 | `17` | 约 `50 Hz` | CoP 上行帧 | | `PROTO_TYPE_ENCODER` | `0x02` | 平衡板 -> 基站 | `13` | TBD | 编码器上行帧,预留 | | `PROTO_TYPE_RESISTANCE` | `0x10` | 基站 -> 平衡板 | `15` | 按需 | 阻力参数帧 | | `PROTO_TYPE_SPOTTER` | `0x11` | 基站 -> 平衡板 | `8` | 按需 | Spotter 参数帧 | @@ -167,9 +169,9 @@ CoP 帧固定 17 字节,结构如下: | `4` | `flags` | `uint8` | `bit0` 表示 `force_valid` | | `5-8` | `cop_x` | `float32 LE` | 压力中心 X 坐标,单位 mm | | `9-12` | `cop_y` | `float32 LE` | 压力中心 Y 坐标,单位 mm | -| `13-16` | `force` | `float32 LE` | 总压力,单位 N | +| `13-16` | `force` | `float32 LE` | 总重量,单位 kg | -更完整的协议示例帧和字段说明见 [BALANCE_BOARD_PROTOCOL.md](./BALANCE_BOARD_PROTOCOL.md)。 +更完整的字段说明和示例帧见 [BALANCE_BOARD_PROTOCOL.md](./BALANCE_BOARD_PROTOCOL.md)。 ## 6. ADS1256 / CoP 规格 @@ -182,18 +184,20 @@ CoP 帧固定 17 字节,结构如下: | +-- 配置 CS / DRDY / RESET GPIO +-- 绑定 SPI1,频率 500 kHz - +-- 关闭 ADS1256 连续输出 - +-- 配置 STATUS / MUX / ADCON / DRATE - +-- 执行自校准 + +-- 硬件复位 ADS1256 + +-- 发送 CMD_SDATAC,关闭连续输出模式 + +-- 写寄存器: STATUS=0x06, MUX=0x01, ADCON=0x07, DRATE=0xC0 + +-- 发送 CMD_SELFCAL + +-- 回读关键寄存器做校验 +-- tare 去皮 | - +-- 主循环 + +-- 传感器线程循环 | - +-- 轮询 4 路 MUX - +-- 读取 ADC 原始值 + +-- 依次切换 4 路 MUX + +-- 每路读 17 次并求均值 +-- 扣除零点偏移 - +-- 7 点中值滤波 +-- 死区抑制 + +-- 重量换算 +-- CoP 解算 +-- 打包 CoP 帧并发送 ``` @@ -203,21 +207,32 @@ CoP 帧固定 17 字节,结构如下: | 项目 | 当前值 | 说明 | |------|--------|------| | SPI 频率 | `500 kHz` | `spi_cfg.frequency = 500000` | -| 中值滤波窗口 | `7` | `MEDIAN_WINDOW = 7` | -| 死区 | `±80` | `-80 < median_val < 80` 时压到 `0` | -| 主循环周期 | `20 ms` | `k_msleep(20)` | -| CoP 上行频率 | `50 Hz` | 与主循环周期一致 | -| 板面半宽 | `200 mm` | `BOARD_HALF_WIDTH_MM = 200.0f` | -| 板面半长 | `200 mm` | `BOARD_HALF_LENGTH_MM = 200.0f` | -| CoP 最小有效总力 | `50 N` | `COP_MIN_FORCE_THRESHOLD = 50.0f` | -| 力值换算系数 | `1.0f` | `ADC_TO_FORCE_SCALE` 仍是占位值 | +| STATUS | `0x06` | `ACAL=1`,`BUFEN=1` | +| PGA | `64` | `ADCON = 0x07` | +| 采样率 | `3750 SPS` | `DRATE = 0xC0` | +| 每帧均值次数 | `17` | `AVG_COUNT = 17` | +| 去皮窗口 | `17` | 每路取 17 点,中位数为零点 | +| 死区 | `±250 counts` | `DEADZONE_THRESHOLD = 250` | +| 板面半宽 | `100 mm` | `BOARD_HALF_WIDTH_MM = 100.0f` | +| 板面半长 | `100 mm` | `BOARD_HALF_LENGTH_MM = 100.0f` | +| CoP 最小有效总重 | `0.5 kg` | `COP_MIN_FORCE_THRESHOLD = 0.5f` | +| 调试日志频率 | 每 10 帧一次 | 目标运行状态下约 `5 Hz` | ### 6.3 CoP 判定规则 -- 四路滤波后 ADC 值先通过 `ADC_TO_FORCE_SCALE` 转成力值 -- 总力小于 `50 N` 时,`cop_x` 和 `cop_y` 置 `0.0f` +- 四路去皮后的 ADC 值先乘 `ADC_TO_FORCE_SCALE`,换算成重量 +- 总重量低于 `0.5 kg` 时,`cop_x` 和 `cop_y` 置 `0.0f` - 此时 `flags.bit0 = 0`,表示 CoP 坐标没有物理意义 -- 总力达到阈值后,再根据四个角点坐标做加权平均 +- 总重量达到阈值后,再根据四角坐标做加权平均 + +当前四路传感器与板面坐标的映射如下: + +| 通道 | 位置 | X | Y | +|------|------|---|---| +| `S0` | 右前 `FR` | `+100` | `+100` | +| `S1` | 右后 `BR` | `+100` | `-100` | +| `S2` | 左后 `BL` | `-100` | `-100` | +| `S3` | 左前 `FL` | `-100` | `+100` | ## 7. 开发环境与板级配置 @@ -229,11 +244,13 @@ CoP 帧固定 17 字节,结构如下: - `CONFIG_BT_PERIPHERAL=y`,工作在外设角色 - `CONFIG_BT_NUS=y`,启用 Nordic UART Service - `CONFIG_BT_DEVICE_NAME="GML670_System"`,设置广播设备名 +- `CONFIG_BT_NUS_SECURITY_ENABLED=n`,关闭 NUS 安全限制 - 日志走 RTT:`CONFIG_USE_SEGGER_RTT=y` - 关闭 UART Console:`CONFIG_CONSOLE=n`、`CONFIG_UART_CONSOLE=n` +- 关闭 UART 日志后端:`CONFIG_LOG_BACKEND_UART=n` - 关闭串口驱动:`CONFIG_SERIAL=n` -`app.overlay` 当前只保留了 SPI1 和 ADS1256 控制 GPIO 的定义,没有额外启用 UART 控制台复用。 +`app.overlay` 当前只保留了 SPI1、ADS1256 设备节点和三根控制 GPIO 的定义,没有把 UART Console 复用回来。 ## 8. 项目结构 @@ -241,8 +258,11 @@ CoP 帧固定 17 字节,结构如下: | 路径 | 说明 | |------|------| -| `src/main.c` | 主流程,包含 BLE、ADS1256 采样、滤波、CoP 解算和发送 | -| `src/protocol.h` | 二进制协议结构、常量、几何参数和阈值 | +| `src/main.c` | 启动入口,负责初始化 BLE 和传感器子系统 | +| `src/ads1256.c` | ADS1256 SPI 读写、复位、寄存器配置和校验 | +| `src/sensor.c` | 采样线程、tare、均值滤波、重量换算和 CoP 打包 | +| `src/ble_transport.c` | BLE 广播、NUS 收发、断线重广播和下行参数缓存 | +| `inc/protocol.h` | 二进制协议结构、重量换算系数、几何参数和阈值 | | `app.overlay` | SPI1、CS、DRDY、RESET 的板级连线 | | `prj.conf` | BLE、日志和控制台相关配置 | | `BALANCE_BOARD_PROTOCOL.md` | 协议专项说明、字段表和示例帧 | @@ -265,15 +285,18 @@ west flash 当前日志走 RTT,不占用 UART Console。启动后可以看到如下类型的信息: - 启动 banner +- BLE transport 初始化结果 +- ADS1256 关键寄存器回读结果 - tare 开始和结束 -- 下行 Resistance 参数日志 -- 下行 Spotter 参数日志 -- Heartbeat 接收路径已接入,默认 `INF` 级别下不会单独打印 +- Resistance / Spotter 下行参数日志 +- 周期性的四角重量、总重、CoP 和帧耗时日志 + +Heartbeat 接收路径已经接入,但默认 `DBG` 级别下才单独打印。 ### 10.2 当前约束 -- 阻力参数、Spotter 参数和 Heartbeat 当前只做到协议接收、内存保存和日志打印 -- 编码器上行帧只在协议头里预留,主循环没有产生这类数据 -- `ADC_TO_FORCE_SCALE = 1.0f` 仍是占位值,真实力值需要结合实物标定修正 -- CoP 当前依赖四角传感器的固定几何布局,默认使用 `200 mm x 200 mm` 半尺寸 -- 主循环固定为 `20 ms` 周期,当前 README 中所有频率描述都以这一实现为准 +- 阻力参数、Spotter 参数和 Heartbeat 目前只做到协议接收、内存保存和日志留痕 +- 编码器上行帧只在协议头里预留,当前没有实际数据源 +- 传感器线程没有额外 `k_msleep()` 节流,实际帧率由 ADS1256 采样和 BLE 发送耗时共同决定 +- README 中的频率描述按当前 `3750 SPS + 4 通道 * 17 次均值` 这套实现估算,目标运行频率约 `50 Hz` +- 当前实现默认四角传感器是固定几何布局,半宽和半长都是 `100 mm`