From ec412b1ffd606f41149cd8246824e7f2c88cf6d4 Mon Sep 17 00:00:00 2001 From: yelvlab Date: Mon, 7 Sep 2026 00:29:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(VL53L4CD):=20=E6=94=AF=E6=8C=81=E9=80=8F?= =?UTF-8?q?=E8=BF=87=E7=8E=BB=E7=92=83=E4=B8=B2=E6=89=B0=E8=A1=A5=E5=81=BF?= =?UTF-8?q?=E3=80=81=E4=B8=B2=E5=8F=A3=E6=A0=A1=E5=87=86=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E4=B8=8E=E6=A0=A1=E5=87=86=E6=95=B0=E6=8D=AEFlash=E5=AD=98?= =?UTF-8?q?=E5=82=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES.md | 60 ++++++++ CMakeLists.txt | 1 + Inc/VL53L4CD_Driver.h | 30 ++++ Inc/board_config.h | 8 ++ Inc/calib_store.h | 37 +++++ Inc/command.h | 22 +++ LD/gd32e23x_flash.ld | 3 +- README.md | 108 +++++++++++++- Src/VL53L4CD_Driver.c | 116 +++++++++++++++ Src/calib_store.c | 137 ++++++++++++++++++ Src/command.c | 324 ++++++++++++++++++++++++++++++++++++++++-- Src/main.c | 164 +++++++++++++++++---- cmake/project.cmake | 4 +- 13 files changed, 966 insertions(+), 48 deletions(-) create mode 100644 Inc/calib_store.h create mode 100644 Src/calib_store.c diff --git a/CHANGES.md b/CHANGES.md index 4ddcc90..5afd8a9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -127,3 +127,63 @@ Src/VL53L4CD_Driver.c ### 说明 固定安装场景使用单点偏移校准即可。`VL53L4CD_AUTO_CALIB = CFG_ENABLE` 时每次开机自动校准,适合重新标定;校准完成后建议改为 `CFG_DISABLE`,使用 `VL53L4CD_CALIB_OFFSET_MM` 中的固定值,避免每次开机都依赖目标存在。 + +## 9. 透过玻璃串扰补偿(Crosstalk) + +### 改动 + +- `Inc/VL53L4CD_Driver.h` / `Src/VL53L4CD_Driver.c`:新增 `vl53l4cd_set_xtalk()`、`vl53l4cd_get_xtalk()`、`vl53l4cd_calibrate_xtalk()`。 +- `Inc/board_config.h`:新增 `VL53L4CD_GLASS_XTALK`、`VL53L4CD_GLASS_XTALK_KCPS`、`VL53L4CD_XTALK_CALIB_TARGET_MM`、`VL53L4CD_XTALK_CALIB_SAMPLES` 开关。 +- `Src/main.c`:开机按开关写入固定串扰值。 + +### 实现 + +按 ST ULD 官方 `vl53l4cd_calibration.c` 的 `VL53L4CD_CalibrateXtalk()` 流程实现(清零补偿 → 热机 10 帧 → 采样有效帧 → 计算写回),浮点公式改为定点数以适配无 FPU 的 Cortex-M23: + +```text +xtalk = (1 - 平均距离/目标距离) * 平均信号速率/平均SPAD数 +``` + +上限 127 kcps,寄存器值 = `xtalk_kcps * 512`(XTALK_PLANE_OFFSET_KCPS 0x0016),X/Y 梯度寄存器(0x0018/0x001A)清零。 + +## 10. 串口命令扩展 + +### 改动 + +- `Inc/command.h` / `Src/command.c`:新增命令处理与共享状态(最近一帧结果、上报开关)。 +- `Src/main.c`:主循环推送最近一帧结果、按上报开关输出。 + +### 命令列表 + +- 基础:`M1`(开上报)、`M2`(关上报)、`M3`(查最近一帧 `d=距离;s=状态`)、`M888`(重启)、`M999`(版本)、`M9999`(OTA) +- 校准:`M101[S]`(读/写偏移)、`M102S`(偏移校准)、`M103[S]`(读/写串扰)、`M104S`(串扰校准)、`M105`(存 Flash)、`M106`(从 Flash 加载应用)、`M107`(擦除并清零) + +`M102`/`M104` 阻塞约 2 秒,期间连续上报暂停。带符号参数(如 `M101S-9`)通过新增的 `parse_int_dec()` 解析。 + +## 11. 校准数据 Flash 持久化 + +### 改动 + +- 新增 `Inc/calib_store.h` / `Src/calib_store.c`。 +- `LD/gd32e23x_flash.ld`:代码区 16K → 15K,保留最后一页(1KB)。 +- `Inc/board_config.h`:新增 `VL53L4CD_PERSIST_CALIB`。 +- `Src/main.c`:开机优先从 Flash 加载校准数据。 + +### 实现 + +- 校准记录(偏移 + 串扰)16 字节:魔数 `0x344C5643`("CVL4")+ 版本 + 偏移 + 串扰 + 累加和。 +- 页地址按运行时检测的 Flash 容量取最后一页:16K→`0x0800 3C00`、32K→`0x0800 7C00`、64K→`0x0800 FC00`。 +- 擦写用 SDK 的 `fmc_page_erase()` / `fmc_doubleword_program()`,期间暂停串口接收中断防止丢字节。 + +## 12. 体积优化与构建调整 + +新增功能后固件超预算(16K 仅剩约 1.9K),做了两处优化: + +- `Src/main.c`:测距上报与启动信息改用手写格式化输出(`uart_write_str/u32/i32`),输出与原 printf 版本逐字节一致,不再链接 vfprintf/malloc 等 libc 组件,省约 2.7K。 +- `cmake/project.cmake`:Debug 优化等级 `-O0` → `-Og`(`-O0` 已无法放下,且旧 Debug 本就已用 15.6K/16K)。 + +### 结果 + +- Release:`text 12908 / data 24 / bss 1704`(改动前 `text 14316 / data 116 / bss 2012`) +- Debug:`text 14876 / data 24 / bss 1712` +- 两配置均在 15K 代码区内,编译无警告(Release) diff --git a/CMakeLists.txt b/CMakeLists.txt index e5b0bd0..fcc8cf5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ set(TARGET_SRC Src/i2c.c Src/board_config.c Src/VL53L4CD_Driver.c + Src/calib_store.c ) # 设置输出目录 diff --git a/Inc/VL53L4CD_Driver.h b/Inc/VL53L4CD_Driver.h index eb69d52..e7d1721 100644 --- a/Inc/VL53L4CD_Driver.h +++ b/Inc/VL53L4CD_Driver.h @@ -140,6 +140,22 @@ bool vl53l4cd_set_range_timing(uint8_t timing_budget_ms, uint32_t inter_measurem */ bool vl53l4cd_set_offset(int16_t offset_mm); +/** + * @brief 写入串扰(玻璃反射)补偿值,透过玻璃测距时使用 + * @details 写 XTALK_PLANE_OFFSET_KCPS (0x0016) 并将 X/Y 梯度寄存器清零, + * 寄存器值为 xtalk_kcps * 512 + * @param[in] xtalk_kcps: 串扰补偿值,单位 kcps,范围 0..127 + * @retval true: 成功; false: 参数无效或写入失败 + */ +bool vl53l4cd_set_xtalk(uint16_t xtalk_kcps); + +/** + * @brief 读取当前生效的串扰补偿值 + * @param[out] xtalk_kcps: 返回串扰值,单位 kcps + * @retval true: 成功; false: 失败 + */ +bool vl53l4cd_get_xtalk(uint16_t *xtalk_kcps); + /** * @brief 读取当前生效的距离偏移量 * @param[out] offset_mm: 返回偏移量,单位 mm @@ -157,6 +173,20 @@ bool vl53l4cd_get_offset(int16_t *offset_mm); bool vl53l4cd_calibrate_offset(uint16_t target_distance_mm, uint16_t num_samples, int16_t *measured_offset_mm); +/** + * @brief 执行一次串扰校准(透过玻璃安装时使用) + * @details 参照 ST ULD vl53l4cd_calibration.c 的 CalibrateXtalk 流程,定点数实现: + * 先清零串扰补偿,热机 10 帧,然后采样有效测距帧, + * xtalk = (1 - 平均距离/目标距离) * 平均信号/平均SPAD数,上限 127 kcps。 + * 校准期间传感器被独占,主循环上报会暂停。 + * @param[in] target_distance_mm: 玻璃后方目标的实际距离,单位 mm,范围 10..5000 + * @param[in] num_samples: 采样次数,范围 5..255,ST 建议至少 10 + * @param[out] measured_xtalk_kcps: 计算得到的串扰值,单位 kcps + * @retval true: 校准并写入成功; false: 失败(含有效样本不足或串扰超限) + */ +bool vl53l4cd_calibrate_xtalk(uint16_t target_distance_mm, uint16_t num_samples, + uint16_t *measured_xtalk_kcps); + /** * @brief 读一个 8 位寄存器 * @param[in] reg: 16 位寄存器地址 diff --git a/Inc/board_config.h b/Inc/board_config.h index 46278f0..773e533 100644 --- a/Inc/board_config.h +++ b/Inc/board_config.h @@ -55,6 +55,14 @@ #define VL53L4CD_AUTO_CALIB CFG_DISABLE // CFG_ENABLE: 每次开机自动校准; CFG_DISABLE: 使用下面固定偏移 #define VL53L4CD_CALIB_OFFSET_MM (-9) // 校准后保存的固定偏移量,单位 mm,可为负 +/* >>>>>>>>>>>>>>>>>>>>[VL53L4CD GLASS XTALK / FLASH PERSIST]<<<<<<<<<<<<<<<<<<<< */ + +#define VL53L4CD_GLASS_XTALK CFG_DISABLE // CFG_ENABLE: 透过玻璃测距,开机写入串扰补偿 +#define VL53L4CD_GLASS_XTALK_KCPS 20U // 编译期固定串扰值,单位 kcps,范围 0..127 +#define VL53L4CD_XTALK_CALIB_TARGET_MM 500U // M104 串扰校准默认目标距离(玻璃后方),单位 mm +#define VL53L4CD_XTALK_CALIB_SAMPLES 20U // M104 串扰校准默认采样次数,ST 建议至少 10 +#define VL53L4CD_PERSIST_CALIB CFG_ENABLE // CFG_ENABLE: 开机优先从 Flash 末页加载校准数据 + /* >>>>>>>>>>>>>>>>>>>>[RS485 MODE]<<<<<<<<<<<<<<<<<<<< */ #define RS485_MODE CFG_ENABLE // CFG_DISABLE: RS485 off; CFG_ENABLE: RS485 on diff --git a/Inc/calib_store.h b/Inc/calib_store.h new file mode 100644 index 0000000..fa0ff7d --- /dev/null +++ b/Inc/calib_store.h @@ -0,0 +1,37 @@ +/** + * @file calib_store.h + * @brief VL53L4CD 校准数据 Flash 持久化存储接口 + * @details 利用主 Flash 最后一页(1KB,地址按运行时检测到的 Flash 容量计算) + * 保存偏移校准与串扰校准结果,掉电不丢失。 + * 注意:链接脚本已将代码区缩小 1KB,为 16K 容量器件保留该页。 + */ +#ifndef CALIB_STORE_H +#define CALIB_STORE_H + +#include +#include + +/** + * @brief 从 Flash 读取校准数据 + * @param[out] offset_mm: 返回保存的偏移量,单位 mm + * @param[out] xtalk_kcps: 返回保存的串扰值,单位 kcps + * @retval true: 校准数据有效; false: 无数据、版本不匹配或校验失败 + */ +bool calib_store_load(int16_t *offset_mm, uint16_t *xtalk_kcps); + +/** + * @brief 保存校准数据到 Flash(整页擦除后写入) + * @details 操作期间会短暂暂停串口接收中断,耗时约几十毫秒 + * @param[in] offset_mm: 偏移量,单位 mm + * @param[in] xtalk_kcps: 串扰值,单位 kcps,0..127 + * @retval true: 保存成功; false: Flash 操作失败 + */ +bool calib_store_save(int16_t offset_mm, uint16_t xtalk_kcps); + +/** + * @brief 擦除 Flash 中的校准数据页 + * @retval true: 擦除成功; false: Flash 操作失败 + */ +bool calib_store_erase(void); + +#endif /* CALIB_STORE_H */ diff --git a/Inc/command.h b/Inc/command.h index b83d2a7..706e866 100644 --- a/Inc/command.h +++ b/Inc/command.h @@ -9,6 +9,7 @@ #include #include +#include "VL53L4CD_Driver.h" /** * @defgroup Command 命令处理模块 @@ -95,6 +96,27 @@ void command_execute(const char *cmd_str); */ void command_m737_timer_finished_callback(void); +/** + * @brief 由主循环推送最近一帧测距结果,供 M3 命令立即查询 + * @param result 最近一帧测距结果 + * @ingroup Command + */ +void command_set_last_result(const vl53l4cd_result_t *result); + +/** + * @brief 查询测距上报是否开启(M1/M2 控制) + * @retval true 上报开启;false 上报关闭 + * @ingroup Command + */ +bool get_sensor_report_enabled(void); + +/** + * @brief 开启/关闭测距上报(同时控制 LED),供 M1/M2 命令调用 + * @param enabled true 开启上报并点亮 LED;false 关闭上报并熄灭 LED + * @ingroup Command + */ +void set_sensor_report_enabled(bool enabled); + /** @} */ // end of Command group #endif // COMMAND_H diff --git a/LD/gd32e23x_flash.ld b/LD/gd32e23x_flash.ld index 405de65..369e51d 100644 --- a/LD/gd32e23x_flash.ld +++ b/LD/gd32e23x_flash.ld @@ -12,7 +12,8 @@ _Min_Stack_Size = 0x400; /* required amount of stack */ /* Memories definition */ MEMORY { - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 16K + /* 代码区保留 1KB:最后一页 Flash 用于保存校准数据(calib_store,页地址按实际容量取末页) */ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 15K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4K } diff --git a/README.md b/README.md index 165d153..2e7ae32 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,14 @@ - [硬件连接](#硬件连接) - [功能开关](#功能开关) - [编译与烧录](#编译与烧录) +- [串口命令(Mx)](#串口命令mx) - [串口输出](#串口输出) - [VL53L4CD 驱动](#vl53l4cd-驱动) - [测距结果字段](#测距结果字段) - [测距状态码](#测距状态码) +- [偏移校准](#偏移校准) +- [透过玻璃测距(Crosstalk)](#透过玻璃测距crosstalk) +- [校准数据 Flash 存储](#校准数据-flash-存储) - [I2C 16 位寄存器说明](#i2c-16-位寄存器说明) - [Flash 体积说明](#flash-体积说明) - [已知限制](#已知限制) @@ -64,6 +68,11 @@ | `VL53L4CD_CALIB_SAMPLES` | `20` | 校准采样次数 | | `VL53L4CD_AUTO_CALIB` | `CFG_DISABLE` | 开机自动校准开关 | | `VL53L4CD_CALIB_OFFSET_MM` | `-9` | 当前固定的距离偏移量 | +| `VL53L4CD_GLASS_XTALK` | `CFG_DISABLE` | 透过玻璃测距:开机写入串扰补偿 | +| `VL53L4CD_GLASS_XTALK_KCPS` | `20` | 编译期固定串扰值,单位 kcps,0..127 | +| `VL53L4CD_XTALK_CALIB_TARGET_MM` | `500` | M104 串扰校准默认目标距离(玻璃后方) | +| `VL53L4CD_XTALK_CALIB_SAMPLES` | `20` | M104 串扰校准默认采样次数 | +| `VL53L4CD_PERSIST_CALIB` | `CFG_ENABLE` | 开机优先从 Flash 末页加载校准数据 | | `RS485_MODE` | `CFG_ENABLE` | 使用 RS485 串口输出 | | `SOFTWARE_IIC` | `CFG_DISABLE` | 使用硬件 I2C | @@ -108,6 +117,41 @@ D:/toolchain/openocd/bin/openocd.exe \ 也可以在 VSCode 中使用已配置的 Flash 任务。 +## 串口命令(Mx) + +命令帧格式 `D5 01 LEN [命令] CRC`(LEN 为命令字节数,CRC 为帧内累加和),响应帧 `B5 TYPE LEN [数据] CRC`,`TYPE=0xF0` 表示成功。波特率 115200,8N1。 + +### 基础命令 + +| 命令 | 功能 | 成功响应 | +|---|---|---| +| `M1` | 开启测距上报,LED 亮 | `ok` | +| `M2` | 关闭测距上报,LED 灭 | `ok` | +| `M3` | 立即返回最近一帧结果(上报关闭时也可查询) | `d=830;s=0` | +| `M888` | 软件重启 | `ok` 后复位 | +| `M999` | 输出固件版本号 | `v20.0.0.1` | +| `M9999` | 复位进入 OTA/Bootloader | 复位 | + +### 校准命令 + +| 命令 | 功能 | 成功响应 | +|---|---|---| +| `M101` | 读取当前偏移量 | `o=-9` | +| `M101S` | 写入偏移量并立即生效,如 `M101S-9` | `o=-9` | +| `M102S` | 执行偏移校准,`S` 为目标实际距离(默认 44) | `o=-9` | +| `M103` | 读取当前串扰补偿值 | `x=12` | +| `M103S` | 写入串扰补偿值 0..127,如 `M103S12` | `x=12` | +| `M104S` | 执行串扰校准,`S` 为玻璃后方目标距离(默认 500) | `x=12` | +| `M105` | 保存当前偏移+串扰到 Flash(掉电保持) | `ok` | +| `M106` | 从 Flash 读取并应用校准数据 | `o=-9` | +| `M107` | 擦除 Flash 校准数据,偏移/串扰清零 | `ok` | + +说明: + +- `M102`/`M104` 为阻塞执行,耗时约 2 秒,期间连续上报暂停。 +- `M101S`/`M103S`/`M102`/`M104` 只改寄存器,掉电丢失;需要掉电保持时接一条 `M105`。 +- 参数错误返回 `0xFD`,未知命令返回 `0xFE`,校准/Flash 失败返回 `0xFF`。 + ## 串口输出 连接串口后使用 115200 8N1 查看,例如 COM17: @@ -139,6 +183,9 @@ Distance: 830 mm | status: 0 | signal: 1128 kcps | ambient: 72 kcps | sigma: 3 m | `vl53l4cd_set_offset(offset_mm)` | 写入手动指定的距离偏移量 | | `vl53l4cd_get_offset(&offset_mm)` | 读取当前生效的距离偏移量 | | `vl53l4cd_calibrate_offset(target_mm, samples, &offset)` | 对当前目标执行偏移校准并写回 | +| `vl53l4cd_set_xtalk(xtalk_kcps)` | 写入串扰补偿值(透过玻璃),0..127 kcps | +| `vl53l4cd_get_xtalk(&xtalk_kcps)` | 读取当前串扰补偿值 | +| `vl53l4cd_calibrate_xtalk(target_mm, samples, &xtalk)` | 执行串扰校准并写回 | | `vl53l4cd_read_reg8/16()` | 读 8/16 位寄存器 | | `vl53l4cd_write_reg8/16()` | 写 8/16 位寄存器 | @@ -155,10 +202,56 @@ VL53L4CD 出厂带有默认偏移,但器件之间存在差异,固定安装 3. 上电后串口会打印 `VL53L4CD offset calibrated: X mm`。 4. 把打印出的 `X` 填入 `VL53L4CD_CALIB_OFFSET_MM`,再将 `VL53L4CD_AUTO_CALIB` 改回 `CFG_DISABLE`。 +也可以不发固件、直接用串口命令在线校准:目标放好后依次发送 `M102S44`(校准)、`M105`(保存到 Flash)。`M101` 可随时读回当前偏移。 + 也可以直接调用 `vl53l4cd_calibrate_offset()`,该函数会先清零偏移、热机 10 帧,再统计有效测距样本并计算 `offset = 实际距离 - 平均距离`,最后写入 `RANGE_OFFSET_MM (0x001E)`。 注意:单点偏移只修正当前固定距离的绝对误差。近距离高反射金属目标存在非线性,建议使用哑光目标。 +## 透过玻璃测距(Crosstalk) + +传感器透过盖板玻璃测距时,玻璃上下表面的反射会产生串扰(crosstalk),表现为读数偏小、近距离出现固定虚假目标。VL53L4CD 通过 XTALK 寄存器组(0x0016..0x001D)做信号级补偿。 + +固件按 ST ULD 官方算法(`vl53l4cd_calibration.c`)实现,并改为定点数运算以省 Flash: + +```text +xtalk = (1 - 平均距离/目标距离) * 平均信号速率/平均SPAD数 +``` + +上限 127 kcps,超出判定为校准失败;寄存器值为 `xtalk_kcps * 512`。 + +### 使用方式 + +**方式一:编译期固定值(不使用串口)** + +1. 测得或校准出串扰值后,打开 `VL53L4CD_GLASS_XTALK`,把值填入 `VL53L4CD_GLASS_XTALK_KCPS`。 +2. Flash 中无校准数据时,开机自动写入该值。 + +**方式二:串口命令(推荐)** + +1. 装上玻璃,目标放在玻璃后方已知距离(如 500mm),确保目标与玻璃之间无遮挡。 +2. 先完成偏移校准:`M102S44`。 +3. 执行串扰校准:`M104S500`,返回如 `x=12`。 +4. `M105` 保存到 Flash,掉电不丢。 +5. `M103` 可随时读回当前串扰值。 + +注意:偏移校准与串扰校准相互影响,重新校准其中一项后建议两项都重新校准并再次 `M105` 保存。 + +## 校准数据 Flash 存储 + +校准数据(偏移 + 串扰)保存在主 Flash 的最后一页(1KB),由 `Src/calib_store.c` 管理: + +| 器件 | 校准页地址 | +|---|---| +| 16K(F4) | `0x0800 3C00` | +| 32K(F6) | `0x0800 7C00` | +| 64K(F8) | `0x0800 FC00` | + +- 页地址按上电检测到的 Flash 容量动态计算,一份固件适配三种容量。 +- 记录 16 字节,含魔数 `CVL4`、版本号、累加和校验;空白 Flash(全 `0xFF`)不会误判。 +- 链接脚本代码区已缩至 15K,避免代码长入校准页;擦写通过 GD32 SDK 的 `fmc_page_erase()` / `fmc_doubleword_program()` 完成,操作期间短暂暂停串口接收中断。 +- 上电加载顺序(`VL53L4CD_PERSIST_CALIB = CFG_ENABLE` 时):Flash 有有效数据 → 用 Flash 值;否则用 `VL53L4CD_CALIB_OFFSET_MM`(及开启 `VL53L4CD_GLASS_XTALK` 时的固定串扰值)。 + ### 修改时序预算 在 `Src/VL53L4CD_Driver.c` 的 `vl53l4cd_init()` 末尾: @@ -179,6 +272,8 @@ delay_ms(500); 改为需要的毫秒数即可。 +## I2C 16 位寄存器说明 + ## 测距结果字段 `vl53l4cd_get_result()` 返回 `vl53l4cd_result_t`: @@ -216,21 +311,22 @@ VL53L4CD 的寄存器地址是 16 位。本项目的 `i2c_read()` / `i2c_write() ## Flash 体积说明 -当前 Release 固件约: +链接脚本代码区为 15K(16K 容量器件预留最后 1KB 页存放校准数据)。当前 Release 固件约: ```text -text 14316 -data 116 -bss 2012 +text 12908 +data 24 +bss 1704 ``` -16K Flash 下仍有约 2K 余量。保留 `printf` 后体积会增加约 2.5K,因此不建议同时开启全部 IIC 调试打印。 +15K 内仍有约 2.4K 余量。为省体积,主循环测距上报与启动信息改为手写格式化输出(不再链接 vfprintf,省约 2.7K),输出格式与旧版本逐字节一致。Debug 构建优化等级由 `-O0` 调整为 `-Og`(-O0 已无法放下),text 约 14876。保留 `printf` 会增加约 2.5K,因此 `DEBUG_VERBOSE` 全开仍会让 Flash 非常紧张。 ## 已知限制 - 驱动只支持连续测距模式 -- 尚未实现 Crosstalk 校准、距离阈值中断、自主低功耗模式、ULP 模式 +- 距离阈值中断、自主低功耗模式、ULP 模式尚未实现 - VL53L4CD 是单区传感器,不是多区测距 +- 串扰补偿为单区平面补偿(梯度寄存器清零),玻璃需正对传感器且尽量平行 - `DEBUG_VERBOSE` 全开时 IIC 调试打印会让 Flash 非常紧张 ## 变更记录 diff --git a/Src/VL53L4CD_Driver.c b/Src/VL53L4CD_Driver.c index a691847..001e2e1 100644 --- a/Src/VL53L4CD_Driver.c +++ b/Src/VL53L4CD_Driver.c @@ -300,6 +300,122 @@ bool vl53l4cd_calibrate_offset(uint16_t target_distance_mm, uint16_t num_samples return true; } +bool vl53l4cd_set_xtalk(uint16_t xtalk_kcps) +{ + /* ST 固件上限:127 kcps(65536/512),寄存器值单位为 1/512 kcps */ + if (xtalk_kcps > 127U) { + return false; + } + if (!vl53l4cd_write_reg16(VL53L4CD_REG_XTALK_X_PLANE_GRADIENT_KCPS, 0U) || + !vl53l4cd_write_reg16(VL53L4CD_REG_XTALK_Y_PLANE_GRADIENT_KCPS, 0U)) { + return false; + } + return vl53l4cd_write_reg16(VL53L4CD_REG_XTALK_PLANE_OFFSET_KCPS, + (uint16_t)(xtalk_kcps << 9)); +} + +bool vl53l4cd_get_xtalk(uint16_t *xtalk_kcps) +{ + uint16_t reg_value; + + if (xtalk_kcps == NULL) { + return false; + } + if (!vl53l4cd_read_reg16(VL53L4CD_REG_XTALK_PLANE_OFFSET_KCPS, ®_value)) { + return false; + } + *xtalk_kcps = (uint16_t)(reg_value >> 9); + return true; +} + +bool vl53l4cd_calibrate_xtalk(uint16_t target_distance_mm, uint16_t num_samples, + uint16_t *measured_xtalk_kcps) +{ + vl53l4cd_result_t result; + uint32_t distance_sum = 0U; + uint32_t signal_sum = 0U; + uint32_t spad_sum = 0U; + uint32_t valid_samples = 0U; + uint16_t avg_distance; + uint32_t avg_signal; + uint32_t avg_spad; + uint32_t signal_ratio; + uint16_t xtalk_kcps; + uint16_t i; + + if (measured_xtalk_kcps == NULL || + target_distance_mm < 10U || target_distance_mm > 5000U || + num_samples < 5U || num_samples > 255U) { + return false; + } + + /* 先禁用串扰补偿再采样 */ + if (!vl53l4cd_set_xtalk(0U)) { + return false; + } + + if (!vl53l4cd_stop_ranging() || !vl53l4cd_start_ranging()) { + return false; + } + + /* 热机 10 帧,丢弃 */ + for (i = 0U; i < 10U; i++) { + if (!vl53l4cd_wait_for_data_ready(VL53L4CD_DATA_TIMEOUT_MS)) { + (void)vl53l4cd_stop_ranging(); + return false; + } + (void)vl53l4cd_clear_interrupt(); + (void)vl53l4cd_get_result(&result); + } + + /* 统计有效测距样本(ST 参考实现:跳过首个采样帧) */ + for (i = 0U; i < num_samples; i++) { + if (!vl53l4cd_wait_for_data_ready(VL53L4CD_DATA_TIMEOUT_MS)) { + (void)vl53l4cd_stop_ranging(); + return false; + } + (void)vl53l4cd_clear_interrupt(); + if (i > 0U && + vl53l4cd_get_result(&result) && + result.range_status == VL53L4CD_STATUS_RANGE_VALID) { + distance_sum += result.distance_mm; + signal_sum += result.signal_rate_kcps; + spad_sum += result.number_of_spad; + valid_samples++; + } + } + + (void)vl53l4cd_stop_ranging(); + + if (valid_samples == 0U) { + return false; + } + + /* 定点数计算 xtalk = (1 - 平均距离/目标距离) * 平均信号/平均SPAD数 */ + avg_distance = (uint16_t)(distance_sum / valid_samples); + avg_signal = signal_sum / valid_samples; + avg_spad = spad_sum / valid_samples; + if (avg_spad == 0U || avg_distance >= target_distance_mm) { + return false; + } + + /* 乘法上界安全:avg_signal<=524280, (target-avg_distance)<=4990, 乘积 < 2^32 */ + signal_ratio = (uint32_t)((avg_signal * (uint32_t)(target_distance_mm - avg_distance)) + / target_distance_mm); + xtalk_kcps = (uint16_t)((signal_ratio + (avg_spad / 2U)) / avg_spad); + if (xtalk_kcps > 127U) { + /* 超出传感器固件支持的最大串扰值,判定为校准失败 */ + return false; + } + + if (!vl53l4cd_set_xtalk(xtalk_kcps)) { + return false; + } + + *measured_xtalk_kcps = xtalk_kcps; + return true; +} + bool vl53l4cd_init(void) { uint16_t model_id = 0U; diff --git a/Src/calib_store.c b/Src/calib_store.c new file mode 100644 index 0000000..3cf1a33 --- /dev/null +++ b/Src/calib_store.c @@ -0,0 +1,137 @@ +/** + * @file calib_store.c + * @brief VL53L4CD 校准数据 Flash 持久化存储实现 + * @details 校准记录保存在主 Flash 的最后一页(1KB)。页地址根据上电检测到的 + * Flash 容量(16K/32K/64K)动态计算,同一份固件可适配不同容量器件: + * 16K: 0x0800 3C00 32K: 0x0800 7C00 64K: 0x0800 FC00 + * 记录为 16 字节(2 个双字),带魔数、版本与累加和校验; + * 空白 Flash 全 0xFF,不会误判为有效记录。 + * GD32E230 页擦除为 1KB,编程以 64 位双字为单位。 + */ +#include "gd32e23x.h" +#include "calib_store.h" +#include "board_config.h" +#include "gd32e23x_fmc.h" +#include "uart.h" +#include + +/** 校准记录魔数,"CVL4" 的 ASCII 小端编码 */ +#define CALIB_RECORD_MAGIC 0x344C5643U +#define CALIB_RECORD_VERSION 1U +#define CALIB_RECORD_SIZE 16U + +typedef struct { + uint32_t magic; /**< 魔数,用于识别有效记录 */ + uint16_t version; /**< 记录格式版本 */ + uint16_t flags; /**< 保留标志位 */ + int16_t offset_mm; /**< 偏移校准值,单位 mm */ + uint16_t xtalk_kcps; /**< 串扰校准值,单位 kcps */ + uint16_t checksum; /**< 前 12 字节累加和 */ + uint16_t pad; /**< 填充到 16 字节 */ +} calib_record_t; + +/** + * @brief 计算校准记录所在页的起始地址(Flash 最后一页) + * @retval uint32_t 页起始地址 + */ +static uint32_t calib_page_address(void) +{ + uint32_t size_kb = get_flash_size(); + + if (size_kb == 0U) { + size_kb = 16U; /* 读取异常时按最小容量处理 */ + } + return 0x08000000U + (size_kb * 1024U) - 1024U; +} + +/** + * @brief 计算记录前 12 字节的 16 位累加和 + * @param[in] rec: 校准记录指针 + * @retval uint16_t 累加和 + */ +static uint16_t calib_checksum(const calib_record_t *rec) +{ + const uint8_t *p = (const uint8_t *)rec; + uint16_t sum = 0U; + uint8_t i; + + for (i = 0U; i < 12U; i++) { + sum += p[i]; + } + return sum; +} + +bool calib_store_load(int16_t *offset_mm, uint16_t *xtalk_kcps) +{ + const calib_record_t *rec = (const calib_record_t *)(uintptr_t)calib_page_address(); + + if (offset_mm == NULL || xtalk_kcps == NULL) { + return false; + } + if (rec->magic != CALIB_RECORD_MAGIC || rec->version != CALIB_RECORD_VERSION) { + return false; + } + if (rec->checksum != calib_checksum(rec)) { + return false; + } + + *offset_mm = rec->offset_mm; + *xtalk_kcps = rec->xtalk_kcps; + return true; +} + +bool calib_store_save(int16_t offset_mm, uint16_t xtalk_kcps) +{ + calib_record_t rec; + uint64_t data[2]; + uint32_t page_addr; + fmc_state_enum fmc_state; + + rec.magic = CALIB_RECORD_MAGIC; + rec.version = CALIB_RECORD_VERSION; + rec.flags = 0U; + rec.offset_mm = offset_mm; + rec.xtalk_kcps = xtalk_kcps; + rec.checksum = 0U; + rec.pad = 0U; + rec.checksum = calib_checksum(&rec); + + /* GD32E230 按双字(64 位)编程,直接按内存布局拆成两个 64 位 */ + memcpy(&data[0], &rec, 8U); + memcpy(&data[1], (const uint8_t *)&rec + 8U, 8U); + + page_addr = calib_page_address(); + + /* Flash 操作期间总线停顿,暂停串口接收避免丢字节 */ + uart_rx_irq_pause(); + fmc_unlock(); + + fmc_state = fmc_page_erase(page_addr); + if (fmc_state == FMC_READY) { + fmc_state = fmc_doubleword_program(page_addr, data[0]); + if (fmc_state == FMC_READY) { + fmc_state = fmc_doubleword_program(page_addr + 8U, data[1]); + } + } + + fmc_lock(); + uart_rx_irq_resume(); + + return (fmc_state == FMC_READY); +} + +bool calib_store_erase(void) +{ + uint32_t page_addr = calib_page_address(); + fmc_state_enum fmc_state; + + uart_rx_irq_pause(); + fmc_unlock(); + + fmc_state = fmc_page_erase(page_addr); + + fmc_lock(); + uart_rx_irq_resume(); + + return (fmc_state == FMC_READY); +} diff --git a/Src/command.c b/Src/command.c index c8ab702..32cb83f 100644 --- a/Src/command.c +++ b/Src/command.c @@ -21,6 +21,7 @@ #include "gd32e23x_usart.h" #include "i2c.h" #include "core_cm23.h" +#include "calib_store.h" /* ============================================================================ * 协议格式说明 @@ -88,6 +89,13 @@ static const uint8_t s_report_status_ok[] __attribute__((unused)) = { 'o', 'k' } static const uint8_t s_report_status_err[] = { 'e','r','r' }; /**< 错误响应数据 */ /** @} */ +/** @name 运行状态共享数据 + * @{ */ +static bool s_report_enabled = true; /**< 测距上报开关,M1/M2 控制 */ +static vl53l4cd_result_t s_last_result; /**< 最近一帧测距结果,主循环推送 */ +static bool s_last_result_valid = false; /**< 最近一帧结果是否有效 */ +/** @} */ + void system_software_reset(void) { // 确保所有待处理的内存访问完成 @@ -111,6 +119,42 @@ void system_software_reset(void) * 公共接口函数 * ============================================================================ */ +bool get_sensor_report_enabled(void) +{ + return s_report_enabled; +} + +void set_sensor_report_enabled(bool enabled) +{ + s_report_enabled = enabled; + if (enabled) { + led_on(); + } else { + led_off(); + } +} + +void command_set_last_result(const vl53l4cd_result_t *result) +{ + if (result != NULL) { + s_last_result = *result; + s_last_result_valid = true; + } +} + +/** + * @brief 发送 "前缀=数值" 格式的成功响应,如 "off=-9"、"xt=12"。 + * @param prefix 数值前缀字符。 + * @param value 有符号数值。 + */ +static void send_resp_prefixed(char prefix, int32_t value); + +/** + * @brief 发送 "d=<距离>;s=<状态>" 格式的测距结果响应。 + * @param result 测距结果指针。 + */ +static void send_resp_result(const vl53l4cd_result_t *result); + /** * @brief 计算协议包的 8 位累加校验值(Checksum)。 * @details 对输入缓冲区逐字节累加并取低 8 位,累加范围为 data[1] 至 data[len-2], @@ -150,7 +194,7 @@ static uint8_t command_sum_crc_calc(const uint8_t *data, uint8_t len) static void send_response(uint8_t type, const uint8_t *payload, uint8_t len) { uint8_t buf_len = (uint8_t)(3 + len + 1); - uint8_t buf[16]; // 简单场景足够,必要时可增大 + uint8_t buf[20]; // 简单场景足够,必要时可增大 if (buf_len > sizeof(buf)) return; // 防御 buf[0] = RESP_HEADER; @@ -322,6 +366,62 @@ static uint8_t parse_uint_dec(const uint8_t *s, uint8_t n, uint32_t *out) return i; } +/** + * @brief 从缓冲区解析十进制有符号整数。 + * @details 支持可选的正负号前缀('-' 或 '+'),数字部分同 parse_uint_dec()。 + * @param s 指向待解析字符缓冲区的起始位置。 + * @param n 允许解析的最大字符数。 + * @param out 输出参数,存储解析结果,可为NULL。 + * @return uint8_t 实际消耗的字符数(含符号位),首字符不是数字时返回 0。 + * @ingroup Command + */ +static uint8_t parse_int_dec(const uint8_t *s, uint8_t n, int32_t *out) +{ + uint8_t used = 0; + bool negative = false; + uint32_t magnitude = 0; + uint8_t digits; + + if (n > 0 && (s[0] == '-' || s[0] == '+')) { + negative = (s[0] == '-'); + used = 1U; + } + + digits = parse_uint_dec(&s[used], (uint8_t)(n - used), &magnitude); + if (digits == 0U) { + return 0; + } + used = (uint8_t)(used + digits); + + if (out != NULL) { + *out = negative ? -(int32_t)magnitude : (int32_t)magnitude; + } + return used; +} + +/** + * @brief 在命令字符串中查找带符号参数的值 + * @param cmd 指向命令起始位置(Mxxx 之后) + * @param cmd_len 命令剩余长度 + * @param key 要查找的参数字符(如 'S') + * @param value 输出参数,存储解析到的值 + * @return bool 是否找到该参数 + */ +static bool find_int_parameter_value(const uint8_t *cmd, uint8_t cmd_len, char key, int32_t *value) +{ + for (uint8_t i = 0; i < cmd_len; i++) { + if (cmd[i] == (uint8_t)key) { + uint8_t param_idx = (uint8_t)(i + 1); + if (param_idx >= cmd_len) return false; + if (parse_int_dec(&cmd[param_idx], (uint8_t)(cmd_len - param_idx), value) > 0) { + return true; + } + return false; + } + } + return false; +} + /** * @brief 通用T参数解析函数 * @details 解析命令中的T参数(定时参数),格式为T<数字> @@ -390,6 +490,41 @@ static bool __attribute__((unused)) find_parameter_value(const uint8_t *cmd, uin return false; } +/** + * @brief 发送 "前缀=数值" 格式的成功响应,如 "off=-9"、"xt=12"。 + * @param prefix 数值前缀字符。 + * @param value 有符号数值。 + */ +static void send_resp_prefixed(char prefix, int32_t value) +{ + char payload[13]; + uint8_t n = 0; + + payload[n++] = prefix; + payload[n++] = '='; + n += int_to_str(value, &payload[n]); + send_response(RESP_TYPE_OK, (const uint8_t *)payload, n); +} + +/** + * @brief 发送 "d=<距离>;s=<状态>" 格式的测距结果响应。 + * @param result 测距结果指针。 + */ +static void send_resp_result(const vl53l4cd_result_t *result) +{ + char payload[13]; + uint8_t n = 0; + + payload[n++] = 'd'; + payload[n++] = '='; + n += uint_to_str(result->distance_mm, &payload[n]); + payload[n++] = ';'; + payload[n++] = 's'; + payload[n++] = '='; + n += uint_to_str(result->range_status, &payload[n]); + send_response(RESP_TYPE_OK, (const uint8_t *)payload, n); +} + /* ============================================================================ * 命令处理函数 * ============================================================================ */ @@ -397,18 +532,30 @@ static bool __attribute__((unused)) find_parameter_value(const uint8_t *cmd, uin /** * @brief 解析并处理完整的命令帧。 * @details 处理经过协议校验的完整命令帧,支持以下命令格式: - * - 无参数命令:M<数字>(如 M1、M2、M10、M201) - * - 带参数命令:M<数字>S<参数>(如 M100S123,参数为十进制) - * + * - 无参数命令:M<数字>(如 M1、M2、M999) + * - 带参数命令:M<数字>S<参数>(如 M101S-9、M102S44,参数为十进制) + * * 支持的命令: - * - M1: 开启LED,启用传感器上报 - * - M2: 关闭LED,禁用传感器上报 - * - M100S: 设置PWM值(示例) - * + * - M1 开启测距上报(LED 亮) + * - M2 关闭测距上报(LED 灭) + * - M3 立即返回最近一帧测距结果 "d=830;s=0" + * - M101 读取当前偏移量 "off=-9" + * - M101S 写入偏移量(mm,立即生效) + * - M102S 执行偏移校准(S=目标实际距离 mm,默认 44) + * - M103 读取当前串扰补偿 "xt=12" + * - M103S 写入串扰补偿(kcps,0..127,立即生效) + * - M104S 执行串扰校准(S=玻璃后方目标距离 mm,默认 500) + * - M105 保存当前偏移+串扰到 Flash + * - M106 从 Flash 读取并应用校准数据 + * - M107 擦除 Flash 校准数据并清零偏移/串扰 + * - M888 软件重启 + * - M999 输出固件版本号 + * - M9999 进入 OTA/Bootloader 模式 + * * @param frame 指向完整命令帧的缓冲区(从包头0xD5开始)。 * @param len 命令帧总长度(字节)。 * @note 函数内部进行帧格式校验,格式错误时自动发送错误响应。 - * @warning 假设输入帧已通过基本协议校验(包头、类型、CRC等)。 + * @warning M102/M104 校准命令为阻塞执行,耗时约 2 秒,期间主循环上报暂停。 * @ingroup Command */ @@ -440,9 +587,164 @@ void handle_command(const uint8_t *frame, uint8_t len) { cmd_index = (uint8_t)(cmd_index + used_base_cmd); // 更新索引到命令后 switch (base_cmd) { - case 1u: // M1 + /* ========================================== + * M1 开启测距上报(LED 亮) + * M2 关闭测距上报(LED 灭) + * ========================================== */ + case 1u: + set_sensor_report_enabled(true); + send_response(RESP_TYPE_OK, s_report_status_ok, sizeof(s_report_status_ok)); + return; + + case 2u: + set_sensor_report_enabled(false); + send_response(RESP_TYPE_OK, s_report_status_ok, sizeof(s_report_status_ok)); + return; + + /* ========================================== + * M3 立即返回最近一帧测距结果 "d=830;s=0" + * (上报关闭时也可用于查询) + * ========================================== */ + case 3u: + if (s_last_result_valid) { + send_resp_result(&s_last_result); + } else { + send_response(RESP_TYPE_ERR, s_report_status_err, sizeof(s_report_status_err)); + } + return; + + /* ========================================== + * M101 读取当前偏移量 "off=-9" + * M101S 写入偏移量(立即生效,如 M101S-9) + * ========================================== */ + case 101u: { + int32_t offset_param; + int16_t offset_mm; + + if (find_int_parameter_value(&cmd[cmd_index], (uint8_t)(cmd_len - cmd_index), 'S', &offset_param)) { + if (offset_param < -4095 || offset_param > 4095) { + send_response(RESP_TYPE_PARAM_ERR, s_report_status_err, sizeof(s_report_status_err)); + return; + } + if (!vl53l4cd_set_offset((int16_t)offset_param)) { + send_response(RESP_TYPE_ERR, s_report_status_err, sizeof(s_report_status_err)); + return; + } + } + if (vl53l4cd_get_offset(&offset_mm)) { + send_resp_prefixed('o', offset_mm); + COMMAND_DEBUG("offset %d mm", offset_mm); + } else { + send_response(RESP_TYPE_ERR, s_report_status_err, sizeof(s_report_status_err)); + } + return; + } + + /* ========================================== + * M102[S] 执行偏移校准 + * 目标放在已知距离,可选 S 指定距离(默认 VL53L4CD_CALIB_TARGET_MM), + * 校准需数秒,期间上报暂停,成功返回 "off=-9" + * ========================================== */ + case 102u: { + uint32_t target_param; + int16_t measured_offset; + + if (!find_parameter_value(&cmd[cmd_index], (uint8_t)(cmd_len - cmd_index), 'S', &target_param)) { + target_param = VL53L4CD_CALIB_TARGET_MM; + } + if (vl53l4cd_calibrate_offset((uint16_t)target_param, VL53L4CD_CALIB_SAMPLES, &measured_offset)) { + send_resp_prefixed('o', measured_offset); + } else { + send_response(RESP_TYPE_ERR, s_report_status_err, sizeof(s_report_status_err)); + } + return; + } + + /* ========================================== + * M103 读取当前串扰补偿值 "xt=12" + * M103S 写入串扰补偿值 0..127(立即生效) + * ========================================== */ + case 103u: { + uint32_t xtalk_param; + uint16_t xtalk_kcps; + + if (find_parameter_value(&cmd[cmd_index], (uint8_t)(cmd_len - cmd_index), 'S', &xtalk_param)) { + if (xtalk_param > 127U) { + send_response(RESP_TYPE_PARAM_ERR, s_report_status_err, sizeof(s_report_status_err)); + return; + } + if (!vl53l4cd_set_xtalk((uint16_t)xtalk_param)) { + send_response(RESP_TYPE_ERR, s_report_status_err, sizeof(s_report_status_err)); + return; + } + } + if (vl53l4cd_get_xtalk(&xtalk_kcps)) { + send_resp_prefixed('x', (int32_t)xtalk_kcps); + COMMAND_DEBUG("xtalk %u kcps", xtalk_kcps); + } else { + send_response(RESP_TYPE_ERR, s_report_status_err, sizeof(s_report_status_err)); + } + return; + } + + /* ========================================== + * M104[S] 执行串扰校准(透过玻璃) + * 玻璃后方目标放在已知距离(默认 VL53L4CD_XTALK_CALIB_TARGET_MM), + * 建议先完成 M102 偏移校准;成功返回 "xt=12" + * ========================================== */ + case 104u: { + uint32_t target_param; + uint16_t measured_xtalk; + + if (!find_parameter_value(&cmd[cmd_index], (uint8_t)(cmd_len - cmd_index), 'S', &target_param)) { + target_param = VL53L4CD_XTALK_CALIB_TARGET_MM; + } + if (vl53l4cd_calibrate_xtalk((uint16_t)target_param, VL53L4CD_XTALK_CALIB_SAMPLES, &measured_xtalk)) { + send_resp_prefixed('x', (int32_t)measured_xtalk); + } else { + send_response(RESP_TYPE_ERR, s_report_status_err, sizeof(s_report_status_err)); + } + return; + } + + /* ========================================== + * M105 保存当前偏移+串扰到 Flash(掉电保持) + * M106 从 Flash 读取并应用校准数据 + * M107 擦除 Flash 校准数据并清零当前偏移/串扰 + * ========================================== */ + case 105u: { + int16_t offset_mm; + uint16_t xtalk_kcps; + + if (vl53l4cd_get_offset(&offset_mm) && vl53l4cd_get_xtalk(&xtalk_kcps) && + calib_store_save(offset_mm, xtalk_kcps)) { send_response(RESP_TYPE_OK, s_report_status_ok, sizeof(s_report_status_ok)); - return; + } else { + send_response(RESP_TYPE_ERR, s_report_status_err, sizeof(s_report_status_err)); + } + return; + } + + case 106u: { + int16_t offset_mm; + uint16_t xtalk_kcps; + + if (calib_store_load(&offset_mm, &xtalk_kcps) && + vl53l4cd_set_offset(offset_mm) && vl53l4cd_set_xtalk(xtalk_kcps)) { + send_resp_prefixed('o', offset_mm); + } else { + send_response(RESP_TYPE_ERR, s_report_status_err, sizeof(s_report_status_err)); + } + return; + } + + case 107u: + if (calib_store_erase() && vl53l4cd_set_offset(0) && vl53l4cd_set_xtalk(0)) { + send_response(RESP_TYPE_OK, s_report_status_ok, sizeof(s_report_status_ok)); + } else { + send_response(RESP_TYPE_ERR, s_report_status_err, sizeof(s_report_status_err)); + } + return; /* ========================================== * M888 软件重启命令 diff --git a/Src/main.c b/Src/main.c index e335158..77aac95 100644 --- a/Src/main.c +++ b/Src/main.c @@ -1,35 +1,35 @@ /*! \file main.c \brief running LED - + \version 2025-02-10, V2.4.0, demo for GD32E23x */ /* Copyright (c) 2025, GigaDevice Semiconductor Inc. - Redistribution and use in source and binary forms, with or without modification, + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - 1. Redistributions of source code must retain the above copyright notice, this + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - 3. Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without + 3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "gd32e23x.h" @@ -38,10 +38,100 @@ OF SUCH DAMAGE. #include "uart_ring_buffer.h" #include "led.h" #include "command.h" -#include #include "i2c.h" #include "board_config.h" #include "VL53L4CD_Driver.h" +#include "calib_store.h" + +/*! + \brief 向测距串口发送一个字节 + \param[in] byte: 待发送字节 + \param[out] none + \retval none +*/ +static void uart_put_byte(uint8_t byte) +{ + while (usart_flag_get(UART_PHY, USART_FLAG_TBE) == RESET) {} + usart_data_transmit(UART_PHY, byte); +} + +/*! + \brief 向测距串口发送以 NUL 结尾的字符串 + \param[in] s: 字符串指针 + \param[out] none + \retval none +*/ +static void uart_write_str(const char *s) +{ + while (*s != '\0') { + uart_put_byte((uint8_t)*s++); + } +} + +/*! + \brief 以十进制输出无符号整数(替代 printf,节省 libc 体积) + \param[in] value: 待输出的数值 + \param[out] none + \retval none +*/ +static void uart_write_u32(uint32_t value) +{ + char temp[10]; + uint8_t i = 0U; + + if (value == 0U) { + uart_put_byte('0'); + return; + } + while (value > 0U) { + temp[i++] = (char)('0' + (value % 10U)); + value /= 10U; + } + while (i > 0U) { + uart_put_byte((uint8_t)temp[--i]); + } +} + +/*! + \brief 以十进制输出有符号整数 + \param[in] value: 待输出的数值 + \param[out] none + \retval none +*/ +static void uart_write_i32(int32_t value) __attribute__((unused)); +static void uart_write_i32(int32_t value) +{ + uint32_t magnitude; + + if (value < 0) { + uart_put_byte('-'); + magnitude = (uint32_t)(-value); + } else { + magnitude = (uint32_t)value; + } + uart_write_u32(magnitude); +} + +/*! + \brief 输出一帧测距结果,格式与原 printf 版本逐字节一致 + \param[in] r: 测距结果指针 + \param[out] none + \retval none +*/ +static void report_result(const vl53l4cd_result_t *r) +{ + uart_write_str("Distance: "); + uart_write_u32(r->distance_mm); + uart_write_str(" mm | status: "); + uart_write_u32(r->range_status); + uart_write_str(" | signal: "); + uart_write_u32(r->signal_rate_kcps); + uart_write_str(" kcps | ambient: "); + uart_write_u32(r->ambient_rate_kcps); + uart_write_str(" kcps | sigma: "); + uart_write_u32(r->sigma_mm); + uart_write_str(" mm\r\n"); +} /*! \brief main function @@ -84,21 +174,41 @@ int main(void) if (vl53l4cd_calibrate_offset(VL53L4CD_CALIB_TARGET_MM, VL53L4CD_CALIB_SAMPLES, &cal_offset)) { - printf("VL53L4CD offset calibrated: %d mm\r\n", cal_offset); + uart_write_str("VL53L4CD offset calibrated: "); + uart_write_i32(cal_offset); + uart_write_str(" mm\r\n"); } else { - printf("VL53L4CD offset calibration failed\r\n"); + uart_write_str("VL53L4CD offset calibration failed\r\n"); } #else - (void)vl53l4cd_set_offset(VL53L4CD_CALIB_OFFSET_MM); + int16_t stored_offset = 0; + uint16_t stored_xtalk = 0; + bool applied = false; + +#if VL53L4CD_PERSIST_CALIB == CFG_ENABLE + /* Flash 末页有有效校准数据时优先生效(M105 保存 / M107 擦除) */ + if (calib_store_load(&stored_offset, &stored_xtalk)) { + vl53l4cd_set_offset(stored_offset); + vl53l4cd_set_xtalk(stored_xtalk); + applied = true; + } +#endif + if (!applied) { + vl53l4cd_set_offset(VL53L4CD_CALIB_OFFSET_MM); +#if VL53L4CD_GLASS_XTALK == CFG_ENABLE + /* 透过玻璃安装:写入编译期固定串扰补偿 */ + vl53l4cd_set_xtalk(VL53L4CD_GLASS_XTALK_KCPS); +#endif + } #endif vl53l4cd_start_ranging(); - printf("VL53L4CD ready\r\n"); + uart_write_str("VL53L4CD ready\r\n"); } else { - printf("VL53L4CD init failed\r\n"); + uart_write_str("VL53L4CD init failed\r\n"); } /* ========== Command Testing ========== */ - + /* ========== */ while(1){ @@ -109,12 +219,10 @@ int main(void) vl53l4cd_clear_interrupt(); if (vl53l4cd_get_result(&result)) { - printf("Distance: %u mm | status: %u | signal: %lu kcps | ambient: %lu kcps | sigma: %u mm\r\n", - result.distance_mm, - result.range_status, - (unsigned long)result.signal_rate_kcps, - (unsigned long)result.ambient_rate_kcps, - result.sigma_mm); + command_set_last_result(&result); /* 供 M3 命令查询 */ + if (get_sensor_report_enabled()) { + report_result(&result); + } } delay_ms(500); } diff --git a/cmake/project.cmake b/cmake/project.cmake index fe46b24..cdb5b7c 100644 --- a/cmake/project.cmake +++ b/cmake/project.cmake @@ -14,8 +14,8 @@ target_compile_options(${TARGET_NAME} PRIVATE "$<$>,$>:-g0>" "$<$>,$>:-g0>" - "$<$,$>:-O0>" - "$<$,$>:-O0>" + "$<$,$>:-Og>" + "$<$,$>:-Og>" "$<$>,$>:-Os>" "$<$>,$>:-Os>"