generated from hulk/gd32e23x_template_cmake_vscode
feat(VL53L4CD): 支持透过玻璃串扰补偿、串口校准命令与校准数据Flash存储
This commit is contained in:
+60
@@ -127,3 +127,63 @@ Src/VL53L4CD_Driver.c
|
|||||||
### 说明
|
### 说明
|
||||||
|
|
||||||
固定安装场景使用单点偏移校准即可。`VL53L4CD_AUTO_CALIB = CFG_ENABLE` 时每次开机自动校准,适合重新标定;校准完成后建议改为 `CFG_DISABLE`,使用 `VL53L4CD_CALIB_OFFSET_MM` 中的固定值,避免每次开机都依赖目标存在。
|
固定安装场景使用单点偏移校准即可。`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)
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ set(TARGET_SRC
|
|||||||
Src/i2c.c
|
Src/i2c.c
|
||||||
Src/board_config.c
|
Src/board_config.c
|
||||||
Src/VL53L4CD_Driver.c
|
Src/VL53L4CD_Driver.c
|
||||||
|
Src/calib_store.c
|
||||||
)
|
)
|
||||||
|
|
||||||
# 设置输出目录
|
# 设置输出目录
|
||||||
|
|||||||
@@ -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);
|
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 读取当前生效的距离偏移量
|
* @brief 读取当前生效的距离偏移量
|
||||||
* @param[out] offset_mm: 返回偏移量,单位 mm
|
* @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,
|
bool vl53l4cd_calibrate_offset(uint16_t target_distance_mm, uint16_t num_samples,
|
||||||
int16_t *measured_offset_mm);
|
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 位寄存器
|
* @brief 读一个 8 位寄存器
|
||||||
* @param[in] reg: 16 位寄存器地址
|
* @param[in] reg: 16 位寄存器地址
|
||||||
|
|||||||
@@ -55,6 +55,14 @@
|
|||||||
#define VL53L4CD_AUTO_CALIB CFG_DISABLE // CFG_ENABLE: 每次开机自动校准; CFG_DISABLE: 使用下面固定偏移
|
#define VL53L4CD_AUTO_CALIB CFG_DISABLE // CFG_ENABLE: 每次开机自动校准; CFG_DISABLE: 使用下面固定偏移
|
||||||
#define VL53L4CD_CALIB_OFFSET_MM (-9) // 校准后保存的固定偏移量,单位 mm,可为负
|
#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]<<<<<<<<<<<<<<<<<<<< */
|
/* >>>>>>>>>>>>>>>>>>>>[RS485 MODE]<<<<<<<<<<<<<<<<<<<< */
|
||||||
|
|
||||||
#define RS485_MODE CFG_ENABLE // CFG_DISABLE: RS485 off; CFG_ENABLE: RS485 on
|
#define RS485_MODE CFG_ENABLE // CFG_DISABLE: RS485 off; CFG_ENABLE: RS485 on
|
||||||
|
|||||||
@@ -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 <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 */
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include "VL53L4CD_Driver.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @defgroup Command 命令处理模块
|
* @defgroup Command 命令处理模块
|
||||||
@@ -95,6 +96,27 @@ void command_execute(const char *cmd_str);
|
|||||||
*/
|
*/
|
||||||
void command_m737_timer_finished_callback(void);
|
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
|
/** @} */ // end of Command group
|
||||||
|
|
||||||
#endif // COMMAND_H
|
#endif // COMMAND_H
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ _Min_Stack_Size = 0x400; /* required amount of stack */
|
|||||||
/* Memories definition */
|
/* Memories definition */
|
||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 16K
|
/* 代码区保留 1KB:最后一页 Flash 用于保存校准数据(calib_store,页地址按实际容量取末页) */
|
||||||
|
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 15K
|
||||||
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4K
|
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4K
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,14 @@
|
|||||||
- [硬件连接](#硬件连接)
|
- [硬件连接](#硬件连接)
|
||||||
- [功能开关](#功能开关)
|
- [功能开关](#功能开关)
|
||||||
- [编译与烧录](#编译与烧录)
|
- [编译与烧录](#编译与烧录)
|
||||||
|
- [串口命令(Mx)](#串口命令mx)
|
||||||
- [串口输出](#串口输出)
|
- [串口输出](#串口输出)
|
||||||
- [VL53L4CD 驱动](#vl53l4cd-驱动)
|
- [VL53L4CD 驱动](#vl53l4cd-驱动)
|
||||||
- [测距结果字段](#测距结果字段)
|
- [测距结果字段](#测距结果字段)
|
||||||
- [测距状态码](#测距状态码)
|
- [测距状态码](#测距状态码)
|
||||||
|
- [偏移校准](#偏移校准)
|
||||||
|
- [透过玻璃测距(Crosstalk)](#透过玻璃测距crosstalk)
|
||||||
|
- [校准数据 Flash 存储](#校准数据-flash-存储)
|
||||||
- [I2C 16 位寄存器说明](#i2c-16-位寄存器说明)
|
- [I2C 16 位寄存器说明](#i2c-16-位寄存器说明)
|
||||||
- [Flash 体积说明](#flash-体积说明)
|
- [Flash 体积说明](#flash-体积说明)
|
||||||
- [已知限制](#已知限制)
|
- [已知限制](#已知限制)
|
||||||
@@ -64,6 +68,11 @@
|
|||||||
| `VL53L4CD_CALIB_SAMPLES` | `20` | 校准采样次数 |
|
| `VL53L4CD_CALIB_SAMPLES` | `20` | 校准采样次数 |
|
||||||
| `VL53L4CD_AUTO_CALIB` | `CFG_DISABLE` | 开机自动校准开关 |
|
| `VL53L4CD_AUTO_CALIB` | `CFG_DISABLE` | 开机自动校准开关 |
|
||||||
| `VL53L4CD_CALIB_OFFSET_MM` | `-9` | 当前固定的距离偏移量 |
|
| `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 串口输出 |
|
| `RS485_MODE` | `CFG_ENABLE` | 使用 RS485 串口输出 |
|
||||||
| `SOFTWARE_IIC` | `CFG_DISABLE` | 使用硬件 I2C |
|
| `SOFTWARE_IIC` | `CFG_DISABLE` | 使用硬件 I2C |
|
||||||
|
|
||||||
@@ -108,6 +117,41 @@ D:/toolchain/openocd/bin/openocd.exe \
|
|||||||
|
|
||||||
也可以在 VSCode 中使用已配置的 Flash 任务。
|
也可以在 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<mm>` | 写入偏移量并立即生效,如 `M101S-9` | `o=-9` |
|
||||||
|
| `M102S<mm>` | 执行偏移校准,`S` 为目标实际距离(默认 44) | `o=-9` |
|
||||||
|
| `M103` | 读取当前串扰补偿值 | `x=12` |
|
||||||
|
| `M103S<kcps>` | 写入串扰补偿值 0..127,如 `M103S12` | `x=12` |
|
||||||
|
| `M104S<mm>` | 执行串扰校准,`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:
|
连接串口后使用 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_set_offset(offset_mm)` | 写入手动指定的距离偏移量 |
|
||||||
| `vl53l4cd_get_offset(&offset_mm)` | 读取当前生效的距离偏移量 |
|
| `vl53l4cd_get_offset(&offset_mm)` | 读取当前生效的距离偏移量 |
|
||||||
| `vl53l4cd_calibrate_offset(target_mm, samples, &offset)` | 对当前目标执行偏移校准并写回 |
|
| `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_read_reg8/16()` | 读 8/16 位寄存器 |
|
||||||
| `vl53l4cd_write_reg8/16()` | 写 8/16 位寄存器 |
|
| `vl53l4cd_write_reg8/16()` | 写 8/16 位寄存器 |
|
||||||
|
|
||||||
@@ -155,10 +202,56 @@ VL53L4CD 出厂带有默认偏移,但器件之间存在差异,固定安装
|
|||||||
3. 上电后串口会打印 `VL53L4CD offset calibrated: X mm`。
|
3. 上电后串口会打印 `VL53L4CD offset calibrated: X mm`。
|
||||||
4. 把打印出的 `X` 填入 `VL53L4CD_CALIB_OFFSET_MM`,再将 `VL53L4CD_AUTO_CALIB` 改回 `CFG_DISABLE`。
|
4. 把打印出的 `X` 填入 `VL53L4CD_CALIB_OFFSET_MM`,再将 `VL53L4CD_AUTO_CALIB` 改回 `CFG_DISABLE`。
|
||||||
|
|
||||||
|
也可以不发固件、直接用串口命令在线校准:目标放好后依次发送 `M102S44`(校准)、`M105`(保存到 Flash)。`M101` 可随时读回当前偏移。
|
||||||
|
|
||||||
也可以直接调用 `vl53l4cd_calibrate_offset()`,该函数会先清零偏移、热机 10 帧,再统计有效测距样本并计算 `offset = 实际距离 - 平均距离`,最后写入 `RANGE_OFFSET_MM (0x001E)`。
|
也可以直接调用 `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()` 末尾:
|
在 `Src/VL53L4CD_Driver.c` 的 `vl53l4cd_init()` 末尾:
|
||||||
@@ -179,6 +272,8 @@ delay_ms(500);
|
|||||||
|
|
||||||
改为需要的毫秒数即可。
|
改为需要的毫秒数即可。
|
||||||
|
|
||||||
|
## I2C 16 位寄存器说明
|
||||||
|
|
||||||
## 测距结果字段
|
## 测距结果字段
|
||||||
|
|
||||||
`vl53l4cd_get_result()` 返回 `vl53l4cd_result_t`:
|
`vl53l4cd_get_result()` 返回 `vl53l4cd_result_t`:
|
||||||
@@ -216,21 +311,22 @@ VL53L4CD 的寄存器地址是 16 位。本项目的 `i2c_read()` / `i2c_write()
|
|||||||
|
|
||||||
## Flash 体积说明
|
## Flash 体积说明
|
||||||
|
|
||||||
当前 Release 固件约:
|
链接脚本代码区为 15K(16K 容量器件预留最后 1KB 页存放校准数据)。当前 Release 固件约:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
text 14316
|
text 12908
|
||||||
data 116
|
data 24
|
||||||
bss 2012
|
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 是单区传感器,不是多区测距
|
- VL53L4CD 是单区传感器,不是多区测距
|
||||||
|
- 串扰补偿为单区平面补偿(梯度寄存器清零),玻璃需正对传感器且尽量平行
|
||||||
- `DEBUG_VERBOSE` 全开时 IIC 调试打印会让 Flash 非常紧张
|
- `DEBUG_VERBOSE` 全开时 IIC 调试打印会让 Flash 非常紧张
|
||||||
|
|
||||||
## 变更记录
|
## 变更记录
|
||||||
|
|||||||
@@ -300,6 +300,122 @@ bool vl53l4cd_calibrate_offset(uint16_t target_distance_mm, uint16_t num_samples
|
|||||||
return true;
|
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)
|
bool vl53l4cd_init(void)
|
||||||
{
|
{
|
||||||
uint16_t model_id = 0U;
|
uint16_t model_id = 0U;
|
||||||
|
|||||||
@@ -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 <string.h>
|
||||||
|
|
||||||
|
/** 校准记录魔数,"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);
|
||||||
|
}
|
||||||
+313
-11
@@ -21,6 +21,7 @@
|
|||||||
#include "gd32e23x_usart.h"
|
#include "gd32e23x_usart.h"
|
||||||
#include "i2c.h"
|
#include "i2c.h"
|
||||||
#include "core_cm23.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' }; /**< 错误响应数据 */
|
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)
|
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)。
|
* @brief 计算协议包的 8 位累加校验值(Checksum)。
|
||||||
* @details 对输入缓冲区逐字节累加并取低 8 位,累加范围为 data[1] 至 data[len-2],
|
* @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)
|
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_len = (uint8_t)(3 + len + 1);
|
||||||
uint8_t buf[16]; // 简单场景足够,必要时可增大
|
uint8_t buf[20]; // 简单场景足够,必要时可增大
|
||||||
if (buf_len > sizeof(buf)) return; // 防御
|
if (buf_len > sizeof(buf)) return; // 防御
|
||||||
|
|
||||||
buf[0] = RESP_HEADER;
|
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;
|
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参数解析函数
|
* @brief 通用T参数解析函数
|
||||||
* @details 解析命令中的T参数(定时参数),格式为T<数字>
|
* @details 解析命令中的T参数(定时参数),格式为T<数字>
|
||||||
@@ -390,6 +490,41 @@ static bool __attribute__((unused)) find_parameter_value(const uint8_t *cmd, uin
|
|||||||
return false;
|
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 解析并处理完整的命令帧。
|
* @brief 解析并处理完整的命令帧。
|
||||||
* @details 处理经过协议校验的完整命令帧,支持以下命令格式:
|
* @details 处理经过协议校验的完整命令帧,支持以下命令格式:
|
||||||
* - 无参数命令:M<数字>(如 M1、M2、M10、M201)
|
* - 无参数命令:M<数字>(如 M1、M2、M999)
|
||||||
* - 带参数命令:M<数字>S<参数>(如 M100S123,参数为十进制)
|
* - 带参数命令:M<数字>S<参数>(如 M101S-9、M102S44,参数为十进制)
|
||||||
*
|
*
|
||||||
* 支持的命令:
|
* 支持的命令:
|
||||||
* - M1: 开启LED,启用传感器上报
|
* - M1 开启测距上报(LED 亮)
|
||||||
* - M2: 关闭LED,禁用传感器上报
|
* - M2 关闭测距上报(LED 灭)
|
||||||
* - M100S<value>: 设置PWM值(示例)
|
* - 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 frame 指向完整命令帧的缓冲区(从包头0xD5开始)。
|
||||||
* @param len 命令帧总长度(字节)。
|
* @param len 命令帧总长度(字节)。
|
||||||
* @note 函数内部进行帧格式校验,格式错误时自动发送错误响应。
|
* @note 函数内部进行帧格式校验,格式错误时自动发送错误响应。
|
||||||
* @warning 假设输入帧已通过基本协议校验(包头、类型、CRC等)。
|
* @warning M102/M104 校准命令为阻塞执行,耗时约 2 秒,期间主循环上报暂停。
|
||||||
* @ingroup Command
|
* @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); // 更新索引到命令后
|
cmd_index = (uint8_t)(cmd_index + used_base_cmd); // 更新索引到命令后
|
||||||
|
|
||||||
switch (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<mm> 写入偏移量(立即生效,如 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<mm>] 执行偏移校准
|
||||||
|
* 目标放在已知距离,可选 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<kcps> 写入串扰补偿值 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<mm>] 执行串扰校准(透过玻璃)
|
||||||
|
* 玻璃后方目标放在已知距离(默认 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));
|
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 软件重启命令
|
* M888 软件重启命令
|
||||||
|
|||||||
+136
-28
@@ -1,35 +1,35 @@
|
|||||||
/*!
|
/*!
|
||||||
\file main.c
|
\file main.c
|
||||||
\brief running LED
|
\brief running LED
|
||||||
|
|
||||||
\version 2025-02-10, V2.4.0, demo for GD32E23x
|
\version 2025-02-10, V2.4.0, demo for GD32E23x
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright (c) 2025, GigaDevice Semiconductor Inc.
|
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:
|
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.
|
list of conditions and the following disclaimer.
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
this list of conditions and the following disclaimer in the documentation
|
this list of conditions and the following disclaimer in the documentation
|
||||||
and/or other materials provided with the distribution.
|
and/or other materials provided with the distribution.
|
||||||
3. Neither the name of the copyright holder nor the names of its contributors
|
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
|
may be used to endorse or promote products derived from this software without
|
||||||
specific prior written permission.
|
specific prior written permission.
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
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
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
OF SUCH DAMAGE.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "gd32e23x.h"
|
#include "gd32e23x.h"
|
||||||
@@ -38,10 +38,100 @@ OF SUCH DAMAGE.
|
|||||||
#include "uart_ring_buffer.h"
|
#include "uart_ring_buffer.h"
|
||||||
#include "led.h"
|
#include "led.h"
|
||||||
#include "command.h"
|
#include "command.h"
|
||||||
#include <stdio.h>
|
|
||||||
#include "i2c.h"
|
#include "i2c.h"
|
||||||
#include "board_config.h"
|
#include "board_config.h"
|
||||||
#include "VL53L4CD_Driver.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
|
\brief main function
|
||||||
@@ -84,21 +174,41 @@ int main(void)
|
|||||||
if (vl53l4cd_calibrate_offset(VL53L4CD_CALIB_TARGET_MM,
|
if (vl53l4cd_calibrate_offset(VL53L4CD_CALIB_TARGET_MM,
|
||||||
VL53L4CD_CALIB_SAMPLES,
|
VL53L4CD_CALIB_SAMPLES,
|
||||||
&cal_offset)) {
|
&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 {
|
} else {
|
||||||
printf("VL53L4CD offset calibration failed\r\n");
|
uart_write_str("VL53L4CD offset calibration failed\r\n");
|
||||||
}
|
}
|
||||||
#else
|
#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
|
#endif
|
||||||
vl53l4cd_start_ranging();
|
vl53l4cd_start_ranging();
|
||||||
printf("VL53L4CD ready\r\n");
|
uart_write_str("VL53L4CD ready\r\n");
|
||||||
} else {
|
} else {
|
||||||
printf("VL53L4CD init failed\r\n");
|
uart_write_str("VL53L4CD init failed\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========== Command Testing ========== */
|
/* ========== Command Testing ========== */
|
||||||
|
|
||||||
/* ========== */
|
/* ========== */
|
||||||
|
|
||||||
while(1){
|
while(1){
|
||||||
@@ -109,12 +219,10 @@ int main(void)
|
|||||||
|
|
||||||
vl53l4cd_clear_interrupt();
|
vl53l4cd_clear_interrupt();
|
||||||
if (vl53l4cd_get_result(&result)) {
|
if (vl53l4cd_get_result(&result)) {
|
||||||
printf("Distance: %u mm | status: %u | signal: %lu kcps | ambient: %lu kcps | sigma: %u mm\r\n",
|
command_set_last_result(&result); /* 供 M3 命令查询 */
|
||||||
result.distance_mm,
|
if (get_sensor_report_enabled()) {
|
||||||
result.range_status,
|
report_result(&result);
|
||||||
(unsigned long)result.signal_rate_kcps,
|
}
|
||||||
(unsigned long)result.ambient_rate_kcps,
|
|
||||||
result.sigma_mm);
|
|
||||||
}
|
}
|
||||||
delay_ms(500);
|
delay_ms(500);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -14,8 +14,8 @@ target_compile_options(${TARGET_NAME} PRIVATE
|
|||||||
"$<$<AND:$<NOT:$<CONFIG:Debug>>,$<COMPILE_LANGUAGE:C>>:-g0>"
|
"$<$<AND:$<NOT:$<CONFIG:Debug>>,$<COMPILE_LANGUAGE:C>>:-g0>"
|
||||||
"$<$<AND:$<NOT:$<CONFIG:Debug>>,$<COMPILE_LANGUAGE:CXX>>:-g0>"
|
"$<$<AND:$<NOT:$<CONFIG:Debug>>,$<COMPILE_LANGUAGE:CXX>>:-g0>"
|
||||||
|
|
||||||
"$<$<AND:$<CONFIG:Debug>,$<COMPILE_LANGUAGE:C>>:-O0>"
|
"$<$<AND:$<CONFIG:Debug>,$<COMPILE_LANGUAGE:C>>:-Og>"
|
||||||
"$<$<AND:$<CONFIG:Debug>,$<COMPILE_LANGUAGE:CXX>>:-O0>"
|
"$<$<AND:$<CONFIG:Debug>,$<COMPILE_LANGUAGE:CXX>>:-Og>"
|
||||||
"$<$<AND:$<NOT:$<CONFIG:Debug>>,$<COMPILE_LANGUAGE:C>>:-Os>"
|
"$<$<AND:$<NOT:$<CONFIG:Debug>>,$<COMPILE_LANGUAGE:C>>:-Os>"
|
||||||
"$<$<AND:$<NOT:$<CONFIG:Debug>>,$<COMPILE_LANGUAGE:CXX>>:-Os>"
|
"$<$<AND:$<NOT:$<CONFIG:Debug>>,$<COMPILE_LANGUAGE:CXX>>:-Os>"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user