generated from hulk/gd32e23x_template_cmake_vscode
Compare commits
5
Commits
6eb9f304d3
...
v0.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
baa3e6310b | ||
|
|
5723f521d7 | ||
|
|
ec412b1ffd | ||
|
|
ef37553b18 | ||
|
|
782fe2b2b0 |
@@ -1,6 +1,5 @@
|
||||
# 忽略构建输出目录
|
||||
Build/
|
||||
.vscode/
|
||||
|
||||
# 忽略 Toolchain 目录下所有内容,但保留目录本身
|
||||
Toolchain/*
|
||||
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
erase
|
||||
exit
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"marus25.cortex-debug",
|
||||
"ms-vscode.cmake-tools",
|
||||
"twxs.cmake",
|
||||
"ms-vscode.cpptools"
|
||||
]
|
||||
}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
loadfile Application.hex
|
||||
r
|
||||
go
|
||||
exit
|
||||
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"cwd": "${workspaceFolder}",
|
||||
"executable": "${workspaceFolder}/Build/Debug/Application/Application.elf",
|
||||
"name": "Debug with OpenOCD",
|
||||
"request": "launch",
|
||||
"type": "cortex-debug",
|
||||
"runToEntryPoint": "main",
|
||||
"showDevDebugOutput": "none",
|
||||
"gdbPath": "${env:GD32_GCC_BIN}/arm-none-eabi-gdb.exe",
|
||||
"servertype": "openocd",
|
||||
"serverpath": "${env:GD32_OPENOCD_BIN}/openocd.exe",
|
||||
"svdFile": "${workspaceFolder}/GD32E230.svd",
|
||||
"configFiles": [
|
||||
"${env:GD32_OPENOCD_SCRIPTS}/${config:gd32.openocd.targetConfig}"
|
||||
],
|
||||
"serverArgs": [
|
||||
"-s", "${env:GD32_OPENOCD_SCRIPTS}"
|
||||
],
|
||||
"env": {
|
||||
"OPENOCD_SCRIPTS": "${env:GD32_OPENOCD_SCRIPTS}"
|
||||
},
|
||||
"searchDir": [
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"preLaunchTask": "Build",
|
||||
"preRestartCommands": [
|
||||
"load",
|
||||
"continue"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cwd": "${workspaceFolder}",
|
||||
"executable": "${workspaceFolder}/Build/Debug/Application/Application.elf",
|
||||
"name": "Debug with J-Link",
|
||||
"request": "launch",
|
||||
"type": "cortex-debug",
|
||||
"runToEntryPoint": "main",
|
||||
"showDevDebugOutput": "none",
|
||||
"gdbPath": "${env:GD32_GCC_BIN}/arm-none-eabi-gdb.exe",
|
||||
"servertype": "jlink",
|
||||
"device": "${config:gd32.jlink.device}",
|
||||
"interface": "${config:gd32.jlink.interface}",
|
||||
"svdFile": "${workspaceFolder}/GD32E230.svd",
|
||||
"liveWatch": {
|
||||
"enabled": true,
|
||||
"samplesPerSecond": 1
|
||||
},
|
||||
"preLaunchTask": "Build",
|
||||
"preRestartCommands": [
|
||||
"load",
|
||||
"continue"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
r
|
||||
exit
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"cmake.configureOnOpen": true,
|
||||
"cmake.useCMakePresets": "always",
|
||||
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
|
||||
"cortex-debug.variableUseNaturalFormat": true,
|
||||
"gd32.openocd.targetConfig": "target/openocd_gdlink_gd32e23x.cfg",
|
||||
"gd32.openocd.massEraseCommand": "gd32e23x mass_erase 0",
|
||||
"gd32.jlink.device": "GD32E230F8",
|
||||
"gd32.jlink.interface": "SWD",
|
||||
"gd32.jlink.speed": "4000",
|
||||
"files.associations": {
|
||||
"*.h": "c",
|
||||
"*.c": "c"
|
||||
}
|
||||
}
|
||||
Vendored
+229
@@ -0,0 +1,229 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Build",
|
||||
"type": "cmake",
|
||||
"command": "build",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": [
|
||||
{
|
||||
"base": "$gcc",
|
||||
"fileLocation": [
|
||||
"relative",
|
||||
"${command:cmake.buildDirectory}"
|
||||
]
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"environment": {
|
||||
"CLICOLOR_FORCE": "1",
|
||||
"OPENOCD_SCRIPTS": "${env:GD32_OPENOCD_SCRIPTS}"
|
||||
}
|
||||
},
|
||||
"presentation": {
|
||||
"clear": true
|
||||
},
|
||||
"icon": {
|
||||
"id": "code",
|
||||
"tooltip": "Build"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Build and Flash",
|
||||
"dependsOn": [
|
||||
"Build",
|
||||
"Flash MCU"
|
||||
],
|
||||
"dependsOrder": "sequence",
|
||||
"group": "build",
|
||||
"icon": {
|
||||
"id": "insert",
|
||||
"tooltip": "Build and Flash"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Flash MCU",
|
||||
"type": "process",
|
||||
"command": "${env:GD32_OPENOCD_BIN}/openocd.exe",
|
||||
"args": [
|
||||
"-s", "${env:GD32_OPENOCD_SCRIPTS}",
|
||||
"-f", "${env:GD32_OPENOCD_SCRIPTS}/${config:gd32.openocd.targetConfig}",
|
||||
"-c", "init; reset halt; flash write_image erase ${command:cmake.launchTargetFilename}; reset; exit"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"options": {
|
||||
"cwd": "${command:cmake.buildDirectory}/Application",
|
||||
"environment": {
|
||||
"OPENOCD_SCRIPTS": "${env:GD32_OPENOCD_SCRIPTS}"
|
||||
}
|
||||
},
|
||||
"presentation": {
|
||||
"clear": true
|
||||
},
|
||||
"icon": {
|
||||
"id": "gather",
|
||||
"tooltip": "Flash MCU"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Reset MCU",
|
||||
"type": "process",
|
||||
"command": "${env:GD32_OPENOCD_BIN}/openocd.exe",
|
||||
"args": [
|
||||
"-s", "${env:GD32_OPENOCD_SCRIPTS}",
|
||||
"-f", "${env:GD32_OPENOCD_SCRIPTS}/${config:gd32.openocd.targetConfig}",
|
||||
"-c", "init; reset; exit"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"options": {
|
||||
"cwd": "${command:cmake.buildDirectory}/Application",
|
||||
"environment": {
|
||||
"OPENOCD_SCRIPTS": "${env:GD32_OPENOCD_SCRIPTS}"
|
||||
}
|
||||
},
|
||||
"presentation": {
|
||||
"clear": true
|
||||
},
|
||||
"icon": {
|
||||
"id": "discard",
|
||||
"tooltip": "Reset MCU"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Mass Erase MCU",
|
||||
"type": "process",
|
||||
"command": "${env:GD32_OPENOCD_BIN}/openocd.exe",
|
||||
"args": [
|
||||
"-s", "${env:GD32_OPENOCD_SCRIPTS}",
|
||||
"-f", "${env:GD32_OPENOCD_SCRIPTS}/${config:gd32.openocd.targetConfig}",
|
||||
"-c", "init; reset halt; ${config:gd32.openocd.massEraseCommand}; exit"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"options": {
|
||||
"cwd": "${command:cmake.buildDirectory}/Application",
|
||||
"environment": {
|
||||
"OPENOCD_SCRIPTS": "${env:GD32_OPENOCD_SCRIPTS}"
|
||||
}
|
||||
},
|
||||
"presentation": {
|
||||
"clear": true
|
||||
},
|
||||
"icon": {
|
||||
"id": "clear-all",
|
||||
"tooltip": "Erase MCU"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "OpenOCD Server",
|
||||
"type": "process",
|
||||
"command": "${env:GD32_OPENOCD_BIN}/openocd.exe",
|
||||
"args": [
|
||||
"-s", "${env:GD32_OPENOCD_SCRIPTS}",
|
||||
"-f", "${env:GD32_OPENOCD_SCRIPTS}/${config:gd32.openocd.targetConfig}"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"options": {
|
||||
"cwd": "${command:cmake.buildDirectory}/Application",
|
||||
"environment": {
|
||||
"OPENOCD_SCRIPTS": "${env:GD32_OPENOCD_SCRIPTS}"
|
||||
}
|
||||
},
|
||||
"presentation": {
|
||||
"clear": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Build and Flash (Jlink)",
|
||||
"dependsOn": [
|
||||
"Build",
|
||||
"Flash MCU (Jlink)"
|
||||
],
|
||||
"dependsOrder": "sequence",
|
||||
"group": "build",
|
||||
"icon": {
|
||||
"id": "insert",
|
||||
"tooltip": "Build and Flash (Jlink)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Flash MCU (Jlink)",
|
||||
"type": "process",
|
||||
"command": "JLink.exe",
|
||||
"args": [
|
||||
"-device", "${config:gd32.jlink.device}",
|
||||
"-if", "${config:gd32.jlink.interface}",
|
||||
"-speed", "${config:gd32.jlink.speed}",
|
||||
"-autoconnect", "1",
|
||||
"-CommanderScript", "${workspaceFolder}/.vscode/flash.jlink"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"options": {
|
||||
"cwd": "${command:cmake.buildDirectory}/Application"
|
||||
},
|
||||
"presentation": {
|
||||
"clear": true
|
||||
},
|
||||
"icon": {
|
||||
"id": "gather",
|
||||
"tooltip": "Flash MCU (Jlink)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Reset MCU (Jlink)",
|
||||
"type": "process",
|
||||
"command": "JLink.exe",
|
||||
"args": [
|
||||
"-device", "${config:gd32.jlink.device}",
|
||||
"-if", "${config:gd32.jlink.interface}",
|
||||
"-speed", "${config:gd32.jlink.speed}",
|
||||
"-autoconnect", "1",
|
||||
"-CommanderScript", "${workspaceFolder}/.vscode/reset.jlink"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
"presentation": {
|
||||
"clear": true
|
||||
},
|
||||
"icon": {
|
||||
"id": "discard",
|
||||
"tooltip": "Reset MCU (Jlink)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Mass Erase MCU (Jlink)",
|
||||
"type": "process",
|
||||
"command": "JLink.exe",
|
||||
"args": [
|
||||
"-device", "${config:gd32.jlink.device}",
|
||||
"-if", "${config:gd32.jlink.interface}",
|
||||
"-speed", "${config:gd32.jlink.speed}",
|
||||
"-autoconnect", "1",
|
||||
"-CommanderScript", "${workspaceFolder}/.vscode/erase.jlink"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
"presentation": {
|
||||
"clear": true
|
||||
},
|
||||
"icon": {
|
||||
"id": "clear-all",
|
||||
"tooltip": "Erase MCU (Jlink)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
# 变更说明
|
||||
|
||||
本文记录本仓库当前未提交改动的背景、目的和验证结果,方便后续维护时快速了解这次迭代做了什么。
|
||||
|
||||
## 1. I2C 底层支持 16 位寄存器地址
|
||||
|
||||
### 改动
|
||||
|
||||
- `Inc/i2c.h`
|
||||
- `Src/i2c.c`
|
||||
|
||||
`i2c_write()` 和 `i2c_read()` 的 `reg_addr` 参数由 `uint8_t` 改为 `uint16_t`,发送寄存器地址时先发高字节、再发低字节。
|
||||
|
||||
### 目的
|
||||
|
||||
VL53L4CD 的所有寄存器地址都是 16 位,例如:
|
||||
|
||||
- `IDENTIFICATION__MODEL_ID = 0x010F`
|
||||
- `RESULT__RANGE_STATUS = 0x0089`
|
||||
- `RESULT__DISTANCE = 0x0096`
|
||||
|
||||
原来的 `uint8_t` 寄存器地址接口无法直接访问这些寄存器。
|
||||
|
||||
### 兼容性
|
||||
|
||||
对于寄存器地址仍为 8 位的普通 I2C 器件,调用方传入的地址会自动被当作低字节发送,高字节为 `0x00`,因此原有行为不变。
|
||||
|
||||
## 2. 新增 VL53L4CD 驱动
|
||||
|
||||
新增文件:
|
||||
|
||||
- `Inc/VL53L4CD_Driver.h`
|
||||
- `Src/VL53L4CD_Driver.c`
|
||||
|
||||
驱动参考 ST VL53L4CD ULD 和 Pololu Arduino 实现,提供:
|
||||
|
||||
- `vl53l4cd_init()`:读取型号、等待固件启动、写入默认配置、VHV 校准、设置 50ms 时序预算
|
||||
- `vl53l4cd_start_ranging()` / `vl53l4cd_stop_ranging()`:启动/停止连续测距
|
||||
- `vl53l4cd_data_ready()`:查询是否有新结果
|
||||
- `vl53l4cd_clear_interrupt()`:清除测距完成中断
|
||||
- `vl53l4cd_get_result()`:读取并解析 15 字节结果块
|
||||
- `vl53l4cd_set_range_timing()`:设置 10-200ms 时序预算,当前只支持连续模式
|
||||
- `vl53l4cd_read_reg8/16()`、`vl53l4cd_write_reg8/16()`:寄存器调试接口
|
||||
|
||||
结果结构体包含:
|
||||
|
||||
- `distance_mm`
|
||||
- `range_status`
|
||||
- `number_of_spad`
|
||||
- `signal_rate_kcps`
|
||||
- `ambient_rate_kcps`
|
||||
- `signal_per_spad_kcps`
|
||||
- `ambient_per_spad_kcps`
|
||||
- `sigma_mm`
|
||||
|
||||
## 3. main.c 集成
|
||||
|
||||
在 `main.c` 中:
|
||||
|
||||
- 初始化完成后调用 `vl53l4cd_init()`
|
||||
- 成功后调用 `vl53l4cd_start_ranging()`
|
||||
- 主循环每 500ms 检查 `vl53l4cd_data_ready()`
|
||||
- 有结果时清除中断、读取结果,并通过 `printf` 输出距离、状态、信号、环境光和 Sigma
|
||||
|
||||
输出示例:
|
||||
|
||||
```text
|
||||
Distance: 830 mm | status: 0 | signal: 1128 kcps | ambient: 72 kcps | sigma: 3 mm
|
||||
```
|
||||
|
||||
## 4. 板级配置
|
||||
|
||||
`Inc/board_config.h` 当前状态:
|
||||
|
||||
- `DEBUG_MODE = CFG_DISABLE`:关闭启动横幅,距离输出仍然保留
|
||||
- `DEBUG_VERBOSE = CFG_DISABLE`:关闭 IIC 调试打印和启动 I2C 扫描
|
||||
- `RS485_MODE = CFG_ENABLE`:使用 RS485 串口输出
|
||||
- I2C 使用 PA9/PA10,复用 AF4
|
||||
|
||||
IIC 源码中的调试 `printf` 已恢复并保留在 `DEBUG_VERBOSE` 条件编译中,需要排查 I2C 时可临时打开。
|
||||
|
||||
## 5. 构建系统
|
||||
|
||||
`CMakeLists.txt` 的 `TARGET_SRC` 已添加:
|
||||
|
||||
```cmake
|
||||
Src/VL53L4CD_Driver.c
|
||||
```
|
||||
|
||||
## 6. 体积与验证
|
||||
|
||||
- MCU:GD32E230,Flash 16K,RAM 4K
|
||||
- Release 编译:`text 14316 / data 116 / bss 2012`
|
||||
- 编译通过,无警告
|
||||
- 已通过 DAPLink/CMSIS-DAP + OpenOCD 烧录
|
||||
- 已在 COM17 @ 115200 验证连续输出,间隔约 500ms
|
||||
- `status: 0` 表示测距有效
|
||||
|
||||
## 7. 当前已知限制
|
||||
|
||||
- 驱动只支持连续测距模式,`inter_measurement_ms` 非 0 会返回失败
|
||||
- 尚未实现 Crosstalk 校准、距离阈值中断、自主低功耗模式、ULP 模式
|
||||
- `DEBUG_VERBOSE` 全开时 IIC 调试打印会让 16K Flash 非常紧张,不建议常开
|
||||
- VL53L4CD 是单区传感器,不是多区测距
|
||||
|
||||
## 8. VL53L4CD 偏移校准
|
||||
|
||||
### 改动
|
||||
|
||||
- `Inc/VL53L4CD_Driver.h`:新增 `RANGE_OFFSET_MM`、`INNER_OFFSET_MM`、`OUTER_OFFSET_MM` 寄存器宏,以及偏移校准接口声明。
|
||||
- `Src/VL53L4CD_Driver.c`:
|
||||
- `vl53l4cd_set_offset()`:写入距离偏移量,寄存器值为 `offset_mm * 4`。
|
||||
- `vl53l4cd_get_offset()`:读取当前生效偏移量。
|
||||
- `vl53l4cd_calibrate_offset()`:清零偏移、热机 10 帧、采样有效测距结果,计算 `offset = 实际距离 - 平均距离` 并写回。
|
||||
- `Inc/board_config.h`:新增校准目标距离、采样次数、自动校准开关和固定偏移配置。
|
||||
- `Src/main.c`:初始化后支持自动校准或写回固定偏移。
|
||||
|
||||
### 当前校准结果
|
||||
|
||||
- 安装位置目标实际距离:44mm
|
||||
- 目标类型:橙色 3D 打印哑光件
|
||||
- 采样次数:20 次
|
||||
- 校准偏移:`-9mm`
|
||||
- 写回后串口输出稳定在约 41-47mm,多数为 44mm
|
||||
- `status: 0`,测距有效
|
||||
|
||||
### 说明
|
||||
|
||||
固定安装场景使用单点偏移校准即可。`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)
|
||||
+3
-1
@@ -36,6 +36,8 @@ set(TARGET_SRC
|
||||
Src/command.c
|
||||
Src/i2c.c
|
||||
Src/board_config.c
|
||||
Src/VL53L4CD_Driver.c
|
||||
Src/calib_store.c
|
||||
)
|
||||
|
||||
# 设置输出目录
|
||||
@@ -92,4 +94,4 @@ add_custom_command(TARGET ${PROJECT_NAME}
|
||||
# 1. If you add new source files (e.g. Src/my_module.c), add them to TARGET_SRC above.
|
||||
# 2. If you add new header files or directories, add them to target_include_directories above.
|
||||
# 3. If you add new SDK modules, use add_subdirectory(SDK/YourModule) and link with target_link_libraries.
|
||||
# =========================
|
||||
# =========================
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* @file VL53L4CD_Driver.h
|
||||
* @brief VL53L4CD 测距传感器驱动接口
|
||||
*
|
||||
* 基于 ST VL53L4CD ULD 和 Pololu 参考实现,寄存器地址均为 16 位,
|
||||
* 通过项目现有硬件 I2C0 (PA9/PA10) 访问。
|
||||
*/
|
||||
#ifndef VL53L4CD_DRIVER_H
|
||||
#define VL53L4CD_DRIVER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* 板载默认 7 位 I2C 地址,VL53L4CD 出厂默认 0x29 */
|
||||
#define VL53L4CD_I2C_ADDR 0x29U
|
||||
|
||||
/* 寄存器地址 */
|
||||
#define VL53L4CD_REG_I2C_SLAVE_DEVICE_ADDRESS 0x0001U
|
||||
#define VL53L4CD_REG_VHV_TIMEOUT_MACROP_LOOP_BOUND 0x0008U
|
||||
#define VL53L4CD_REG_XTALK_PLANE_OFFSET_KCPS 0x0016U
|
||||
#define VL53L4CD_REG_XTALK_X_PLANE_GRADIENT_KCPS 0x0018U
|
||||
#define VL53L4CD_REG_XTALK_Y_PLANE_GRADIENT_KCPS 0x001AU
|
||||
#define VL53L4CD_REG_RANGE_OFFSET_MM 0x001EU
|
||||
#define VL53L4CD_REG_INNER_OFFSET_MM 0x0020U
|
||||
#define VL53L4CD_REG_OUTER_OFFSET_MM 0x0022U
|
||||
#define VL53L4CD_REG_GPIO_HV_MUX_CTRL 0x0030U
|
||||
#define VL53L4CD_REG_GPIO_TIO_HV_STATUS 0x0031U
|
||||
#define VL53L4CD_REG_SYSTEM_INTERRUPT 0x0046U
|
||||
#define VL53L4CD_REG_RANGE_CONFIG_A 0x005EU
|
||||
#define VL53L4CD_REG_RANGE_CONFIG_B 0x0061U
|
||||
#define VL53L4CD_REG_SIGMA_THRESHOLD 0x0064U
|
||||
#define VL53L4CD_REG_MIN_COUNT_RATE_RTN_LIMIT 0x0066U
|
||||
#define VL53L4CD_REG_INTERMEASUREMENT_MS 0x006CU
|
||||
#define VL53L4CD_REG_THRESH_HIGH 0x0072U
|
||||
#define VL53L4CD_REG_THRESH_LOW 0x0074U
|
||||
#define VL53L4CD_REG_SYSTEM_INTERRUPT_CLEAR 0x0086U
|
||||
#define VL53L4CD_REG_SYSTEM_START 0x0087U
|
||||
#define VL53L4CD_REG_RESULT_RANGE_STATUS 0x0089U
|
||||
#define VL53L4CD_REG_RESULT_SPAD_NB 0x008CU
|
||||
#define VL53L4CD_REG_RESULT_SIGNAL_RATE 0x008EU
|
||||
#define VL53L4CD_REG_RESULT_AMBIENT_RATE 0x0090U
|
||||
#define VL53L4CD_REG_RESULT_SIGMA 0x0092U
|
||||
#define VL53L4CD_REG_RESULT_DISTANCE 0x0096U
|
||||
#define VL53L4CD_REG_RESULT_OSC_CALIBRATE_VAL 0x00DEU
|
||||
#define VL53L4CD_REG_FIRMWARE_SYSTEM_STATUS 0x00E5U
|
||||
#define VL53L4CD_REG_IDENTIFICATION_MODEL_ID 0x010FU
|
||||
|
||||
/* 等待固件启动 / 数据就绪的最大时间 */
|
||||
#define VL53L4CD_BOOT_TIMEOUT_MS 200U
|
||||
#define VL53L4CD_DATA_TIMEOUT_MS 500U
|
||||
|
||||
/* 一次结果块读包含的字节数:0x89..0x97 */
|
||||
#define VL53L4CD_RESULT_BLOCK_SIZE 15U
|
||||
|
||||
/* 测距状态码(已由原始状态映射为用户可读状态) */
|
||||
typedef enum {
|
||||
VL53L4CD_STATUS_RANGE_VALID = 0, /* 测距有效 */
|
||||
VL53L4CD_STATUS_SIGMA_FAIL, /* Sigma 超限 */
|
||||
VL53L4CD_STATUS_SIGNAL_FAIL, /* 信号强度不足 */
|
||||
VL53L4CD_STATUS_MIN_RANGE_FAIL, /* 目标过近 */
|
||||
VL53L4CD_STATUS_PHASE_FAIL, /* 相位故障 */
|
||||
VL53L4CD_STATUS_HW_FAIL, /* 硬件故障 */
|
||||
VL53L4CD_STATUS_NO_TARGET, /* 未检测到目标 */
|
||||
VL53L4CD_STATUS_SIGNAL_FAIL_NEAR, /* 近距离信号不足 */
|
||||
VL53L4CD_STATUS_OUT_OF_BOUNDS, /* 超出边界 */
|
||||
VL53L4CD_STATUS_XTALK_SIGNAL_FAIL, /* 串扰信号失败 */
|
||||
VL53L4CD_STATUS_LOW_SIGNAL_FAIL, /* 低信号失败 */
|
||||
VL53L4CD_STATUS_CROSSTALK_SIGNAL_FAIL,/* 串扰信号失败 */
|
||||
VL53L4CD_STATUS_AMBIENT_SIGNAL_FAIL, /* 环境光信号失败 */
|
||||
VL53L4CD_STATUS_SNSPD_AMBIENT_FAIL, /* 单光子雪崩二极管环境光失败 */
|
||||
VL53L4CD_STATUS_UNKNOWN = 255 /* 未知状态 */
|
||||
} vl53l4cd_range_status_t;
|
||||
|
||||
/* 单次测量结果 */
|
||||
typedef struct {
|
||||
uint16_t distance_mm; /* 距离,单位 mm */
|
||||
uint8_t range_status; /* 测距状态码 */
|
||||
uint8_t number_of_spad; /* 有效 SPAD 数 */
|
||||
uint32_t signal_rate_kcps; /* 信号速率,单位 kcps */
|
||||
uint32_t ambient_rate_kcps; /* 环境光速率,单位 kcps */
|
||||
uint32_t signal_per_spad_kcps; /* 每 SPAD 信号速率 */
|
||||
uint32_t ambient_per_spad_kcps; /* 每 SPAD 环境光速率 */
|
||||
uint16_t sigma_mm; /* Sigma,单位 mm */
|
||||
} vl53l4cd_result_t;
|
||||
|
||||
/**
|
||||
* @brief 初始化 VL53L4CD
|
||||
* @param[in] none
|
||||
* @retval true: 初始化成功; false: 初始化失败
|
||||
*/
|
||||
bool vl53l4cd_init(void);
|
||||
|
||||
/**
|
||||
* @brief 启动连续测距
|
||||
* @param[in] none
|
||||
* @retval true: 启动成功; false: 启动失败
|
||||
*/
|
||||
bool vl53l4cd_start_ranging(void);
|
||||
|
||||
/**
|
||||
* @brief 停止连续测距
|
||||
* @param[in] none
|
||||
* @retval true: 停止成功; false: 停止失败
|
||||
*/
|
||||
bool vl53l4cd_stop_ranging(void);
|
||||
|
||||
/**
|
||||
* @brief 检查是否有新测距结果
|
||||
* @param[in] none
|
||||
* @retval true: 有新结果; false: 无新结果或读取失败
|
||||
*/
|
||||
bool vl53l4cd_data_ready(void);
|
||||
|
||||
/**
|
||||
* @brief 清除测距完成中断
|
||||
* @param[in] none
|
||||
* @retval true: 清除成功; false: 清除失败
|
||||
*/
|
||||
bool vl53l4cd_clear_interrupt(void);
|
||||
|
||||
/**
|
||||
* @brief 读取并解析一次测距结果
|
||||
* @param[out] result: 结果结构体指针
|
||||
* @retval true: 读取成功; false: 读取失败
|
||||
*/
|
||||
bool vl53l4cd_get_result(vl53l4cd_result_t *result);
|
||||
|
||||
/**
|
||||
* @brief 设置测距时序预算和测距周期
|
||||
* @param[in] timing_budget_ms: 10..200 ms
|
||||
* @param[in] inter_measurement_ms: 当前驱动只支持 0(连续模式)
|
||||
* @retval true: 设置成功; false: 参数无效或设置失败
|
||||
*/
|
||||
bool vl53l4cd_set_range_timing(uint8_t timing_budget_ms, uint32_t inter_measurement_ms);
|
||||
|
||||
/**
|
||||
* @brief 写入手动指定的距离偏移量
|
||||
* @param[in] offset_mm: 偏移量,单位 mm,可为负
|
||||
* @retval true: 成功; false: 失败
|
||||
*/
|
||||
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
|
||||
* @retval true: 成功; false: 失败
|
||||
*/
|
||||
bool vl53l4cd_get_offset(int16_t *offset_mm);
|
||||
|
||||
/**
|
||||
* @brief 以当前目标执行一次偏移校准
|
||||
* @param[in] target_distance_mm: 目标实际距离,单位 mm,范围 10..1000
|
||||
* @param[in] num_samples: 有效采样次数,范围 5..255
|
||||
* @param[out] measured_offset_mm: 计算得到的偏移量,单位 mm
|
||||
* @retval true: 校准并写入成功; false: 失败
|
||||
*/
|
||||
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 位寄存器地址
|
||||
* @param[out] value: 返回值指针
|
||||
* @retval true: 成功; false: 失败
|
||||
*/
|
||||
bool vl53l4cd_read_reg8(uint16_t reg, uint8_t *value);
|
||||
|
||||
/**
|
||||
* @brief 读一个 16 位寄存器
|
||||
* @param[in] reg: 16 位寄存器地址
|
||||
* @param[out] value: 返回值指针
|
||||
* @retval true: 成功; false: 失败
|
||||
*/
|
||||
bool vl53l4cd_read_reg16(uint16_t reg, uint16_t *value);
|
||||
|
||||
/**
|
||||
* @brief 写一个 8 位寄存器
|
||||
* @param[in] reg: 16 位寄存器地址
|
||||
* @param[in] value: 写入值
|
||||
* @retval true: 成功; false: 失败
|
||||
*/
|
||||
bool vl53l4cd_write_reg8(uint16_t reg, uint8_t value);
|
||||
|
||||
/**
|
||||
* @brief 写一个 16 位寄存器
|
||||
* @param[in] reg: 16 位寄存器地址
|
||||
* @param[in] value: 写入值
|
||||
* @retval true: 成功; false: 失败
|
||||
*/
|
||||
bool vl53l4cd_write_reg16(uint16_t reg, uint16_t value);
|
||||
|
||||
#endif /* VL53L4CD_DRIVER_H */
|
||||
+17
-2
@@ -23,7 +23,7 @@
|
||||
|
||||
/* >>>>>>>>>>>>>>>>>>>>[DEBUG MODE]<<<<<<<<<<<<<<<<<<<< */
|
||||
|
||||
#define DEBUG_MODE CFG_ENABLE // CFG_DISABLE: Release Mode; CFG_ENABLE: Debug Mode
|
||||
#define DEBUG_MODE CFG_DISABLE // CFG_DISABLE: Release Mode; CFG_ENABLE: Debug Mode
|
||||
|
||||
/* >>>>>>>>>>>>>>>>>>>>[COMMAND DEBUG]<<<<<<<<<<<<<<<<<<<< */
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
/* >>>>>>>>>>>>>>>>>>>>[DEBUG ASSERTIONS DEFINE]<<<<<<<<<<<<<<<<<<<< */
|
||||
|
||||
#define DEBUG_VERBOSE CFG_ENABLE // CFG_DISABLE: Verbose debug off; CFG_ENABLE: Verbose debug on
|
||||
#define DEBUG_VERBOSE CFG_DISABLE // CFG_DISABLE: Verbose debug off; CFG_ENABLE: Verbose debug on
|
||||
|
||||
/* >>>>>>>>>>>>>>>>>>>>[SEGGER RTT DETECTION]<<<<<<<<<<<<<<<<<<<< */
|
||||
|
||||
@@ -48,6 +48,21 @@
|
||||
#define RTT_PutChar(ch, c)
|
||||
#endif
|
||||
|
||||
/* >>>>>>>>>>>>>>>>>>>>[VL53L4CD OFFSET CALIBRATION]<<<<<<<<<<<<<<<<<<<< */
|
||||
|
||||
#define VL53L4CD_CALIB_TARGET_MM 44U // 当前安装位置的目标实际距离
|
||||
#define VL53L4CD_CALIB_SAMPLES 20U // 校准采样次数,ST 推荐 20
|
||||
#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
|
||||
|
||||
@@ -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 <stdbool.h>
|
||||
#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
|
||||
|
||||
@@ -93,7 +93,7 @@ void i2c_scan(void);
|
||||
/*!
|
||||
\brief write 16-bit data to I2C device
|
||||
\param[in] slave_addr: 7-bit slave address
|
||||
\param[in] reg_addr: register address
|
||||
\param[in] reg_addr: register address (16-bit, high byte first)
|
||||
\param[in] data: pointer to 2-byte data array
|
||||
\param[out] none
|
||||
\retval i2c_result_t
|
||||
@@ -119,17 +119,17 @@ i2c_result_t i2c_read_16bits(uint8_t slave_addr, uint8_t reg_addr, uint8_t *data
|
||||
\param[out] none
|
||||
\retval i2c_result_t: operation result
|
||||
*/
|
||||
i2c_result_t i2c_write(uint8_t slave_addr, uint8_t reg_addr, uint8_t *data, uint8_t length);
|
||||
i2c_result_t i2c_write(uint8_t slave_addr, uint16_t reg_addr, uint8_t *data, uint8_t length);
|
||||
|
||||
/*!
|
||||
\brief read data from I2C device with configurable length
|
||||
\param[in] slave_addr: slave device address (7-bit)
|
||||
\param[in] reg_addr: register address
|
||||
\param[in] reg_addr: register address (16-bit, high byte first)
|
||||
\param[out] data: pointer to data buffer
|
||||
\param[in] length: number of bytes to read (1-255)
|
||||
\retval i2c_result_t: operation result
|
||||
*/
|
||||
i2c_result_t i2c_read(uint8_t slave_addr, uint8_t reg_addr, uint8_t *data, uint8_t length);
|
||||
i2c_result_t i2c_read(uint8_t slave_addr, uint16_t reg_addr, uint8_t *data, uint8_t length);
|
||||
|
||||
/* Pure read response without sub-address phase (for commands that already sent read request parameters) */
|
||||
/*!
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,396 +1,359 @@
|
||||
# GD32E23x 工程模板
|
||||
# GD32E230 + VL53L4CD 测距项目
|
||||
|
||||
本仓库为兆易创新 GD32E23x 系列 MCU 的 CMake + VSCode 工程模板,适合嵌入式开发快速上手和团队协作。
|
||||
|
||||
---
|
||||
本仓库是基于 GD32E230 的 VL53L4CD 时间飞行(ToF)测距项目,硬件兼容 Adafruit VL53L4CD 模块。固件通过 I2C 驱动传感器,每 500ms 输出一次距离和测距质量参数。
|
||||
|
||||
## 目录
|
||||
|
||||
- [适用范围](#适用范围)
|
||||
- [默认配置](#默认配置)
|
||||
- [快速开始](#快速开始)
|
||||
- [工程结构](#工程结构)
|
||||
- [板级配置](#板级配置)
|
||||
- [Flash 偏移配置(配合 Bootloader)](#flash-偏移配置配合-bootloader)
|
||||
- [工具链准备](#工具链准备)
|
||||
- [使用说明](#使用说明)
|
||||
- [时钟配置说明](#时钟配置说明)
|
||||
- [vcpkg 依赖管理(可选)](#vcpkg-依赖管理可选)
|
||||
- [硬件连接](#硬件连接)
|
||||
- [功能开关](#功能开关)
|
||||
- [编译与烧录](#编译与烧录)
|
||||
- [串口命令(Mx)](#串口命令mx)
|
||||
- [串口输出](#串口输出)
|
||||
- [VL53L4CD 驱动](#vl53l4cd-驱动)
|
||||
- [测距结果字段](#测距结果字段)
|
||||
- [测距状态码](#测距状态码)
|
||||
- [偏移校准](#偏移校准)
|
||||
- [透过玻璃测距(Crosstalk)](#透过玻璃测距crosstalk)
|
||||
- [校准数据 Flash 存储](#校准数据-flash-存储)
|
||||
- [I2C 16 位寄存器说明](#i2c-16-位寄存器说明)
|
||||
- [Flash 体积说明](#flash-体积说明)
|
||||
- [已知限制](#已知限制)
|
||||
- [变更记录](#变更记录)
|
||||
|
||||
---
|
||||
## 硬件连接
|
||||
|
||||
## 适用范围
|
||||
### 主控
|
||||
|
||||
- 适用于兆易创新 GD32E23x 系列 Cortex-M23 内核单片机
|
||||
- 支持标准外设库开发
|
||||
- 推荐开发环境:VSCode + CMake + ARM GCC 工具链
|
||||
- MCU:GD32E230 系列,Cortex-M23
|
||||
- 系统时钟:72MHz
|
||||
- Flash:16K(F4)/ 32K(F6)/ 64K(F8),上电自动识别
|
||||
|
||||
---
|
||||
### VL53L4CD
|
||||
|
||||
## 默认配置
|
||||
| 信号 | 引脚 | 说明 |
|
||||
|---|---|---|
|
||||
| SCL | PA9 | I2C0 时钟,AF4 |
|
||||
| SDA | PA10 | I2C0 数据,AF4 |
|
||||
| VDD | 3.3V | 模块供电 |
|
||||
| GND | GND | 共地 |
|
||||
|
||||
- MCU 主频:内部 RC 振荡器,系统时钟配置为 72MHz
|
||||
- 调试串口:USART0(PA2 TX / PA3 RX),115200 波特率
|
||||
- I2C:默认硬件 I2C0(PF0 SDA / PF1 SCL),可通过 `board_config.h` 切换为软件 I2C
|
||||
传感器 7 位 I2C 地址默认为 `0x29`。
|
||||
|
||||
---
|
||||
### 串口
|
||||
|
||||
## 快速开始
|
||||
| 信号 | 引脚 | 说明 |
|
||||
|---|---|---|
|
||||
| TX | PA2 | RS485 发送 |
|
||||
| RX | PA3 | RS485 接收 |
|
||||
| DE | PA1 | RS485 方向控制,硬件自动控制 |
|
||||
|
||||
### 基于模板创建新项目
|
||||
波特率 115200,8N1。
|
||||
|
||||
1. **克隆或复制本仓库**
|
||||
```bash
|
||||
git clone https://gitea.hulk.wang/hulk/gd32e23x_template_cmake_vscode.git my-new-project
|
||||
cd my-new-project
|
||||
```
|
||||
### 其他
|
||||
|
||||
2. **修改项目配置** — 编辑 `cmake/project_config.cmake`:
|
||||
```cmake
|
||||
set(PROJECT_NAME "MyProject") # 项目名称
|
||||
set(BOARD_TYPE_CODE 20) # 板卡类型码(协议帧中的标识)
|
||||
set(VERSION_MAJOR 1) # 主版本号
|
||||
set(VERSION_MINOR 0) # 次版本号
|
||||
set(VERSION_PATCH 0) # 修订号
|
||||
set(BUILD_VARIANT "APP") # 编译变体
|
||||
```
|
||||
- LED:PB1
|
||||
- SWD:SWDIO、SWCLK、GND,用于 DAPLink/J-Link 烧录与调试
|
||||
|
||||
3. **添加业务源文件** — 编辑 `CMakeLists.txt`,在 `TARGET_SRC` 中添加你的 `.c` 文件。
|
||||
## 功能开关
|
||||
|
||||
4. **配置板级引脚** — 编辑 `Inc/board_config.h`,修改 I2C、UART、LED 等引脚定义。
|
||||
所有开关集中在 `Inc/board_config.h`。
|
||||
|
||||
5. **编译**
|
||||
```bash
|
||||
cmake --preset Debug
|
||||
cmake --build build/Debug
|
||||
```
|
||||
产物在 `build/Debug/` 下,包含 `.elf`、`.hex`、`.bin`、`.map`、`.list`。
|
||||
| 宏 | 当前值 | 作用 |
|
||||
|---|---|---|
|
||||
| `DEBUG_MODE` | `CFG_DISABLE` | 关闭 `Hello World` 启动横幅;距离输出仍保留 |
|
||||
| `DEBUG_VERBOSE` | `CFG_DISABLE` | 关闭 IIC 调试打印和启动 I2C 扫描 |
|
||||
| `COM_DEBUG` | `CFG_DISABLE` | 关闭命令帧调试打印 |
|
||||
| `SEGGER_RTT_DETECTION` | `CFG_DISABLE` | 关闭 SEGGER RTT |
|
||||
| `VL53L4CD_CALIB_TARGET_MM` | `44` | 当前安装位置的目标实际距离 |
|
||||
| `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 |
|
||||
|
||||
### 分支说明
|
||||
如果需要临时扫描 I2C 总线或查看 IIC 调试信息,把 `DEBUG_VERBOSE` 改为 `CFG_ENABLE`。注意 16K Flash 下全开会让固件体积非常紧张。
|
||||
|
||||
| 分支 | 用途 |
|
||||
|------|------|
|
||||
| `template_pc` | **主模板**(推荐),用于桌面端 VSCode 开发 |
|
||||
| `template_xl` | 小琅适配版 |
|
||||
| `main` | 早期版本,不推荐使用 |
|
||||
## 编译与烧录
|
||||
|
||||
---
|
||||
### 环境变量(每台电脑配置一次)
|
||||
|
||||
## 工程结构
|
||||
工程不内置工具链,编译、烧录、调试全部通过用户级环境变量定位工具:
|
||||
|
||||
```
|
||||
.
|
||||
├── CMakeLists.txt # 主构建文件
|
||||
├── CMakePresets.json # CMake 预设(Debug/Release)
|
||||
├── cmake/
|
||||
│ ├── arm-none-eabi-gcc.cmake # ARM GCC 工具链配置
|
||||
│ ├── project.cmake # 编译选项(-Os/-O0, -mcpu=cortex-m23)
|
||||
│ ├── project_config.cmake # 项目名/版本号/编译变体
|
||||
│ └── version.h.in # 自动生成固件版本头
|
||||
├── Inc/ # 头文件
|
||||
│ ├── board_config.h # 板级引脚定义 + 功能开关
|
||||
│ ├── command.h # 串口命令协议
|
||||
│ ├── i2c.h / led.h / systick.h / uart.h
|
||||
│ └── uart_ring_buffer.h / gd32e23x_it.h / gd32e23x_libopt.h
|
||||
├── Src/ # 源码
|
||||
│ ├── main.c # 入口函数
|
||||
│ ├── command.c # 命令解析处理
|
||||
│ ├── board_config.c # MCU 型号自动检测
|
||||
│ ├── i2c.c / led.c / systick.c / uart.c / uart_ring_buffer.c
|
||||
│ └── gd32e23x_it.c / system_gd32e23x.c / syscalls.c
|
||||
├── SDK/
|
||||
│ ├── CMSIS/ # ARM CMSIS Core (Cortex-M23) + GD 启动文件
|
||||
│ └── GD32E23x_standard_peripheral/ # GD32 标准外设库
|
||||
├── LD/gd32e23x_flash.ld # 链接脚本
|
||||
├── doc/ # 芯片数据手册
|
||||
└── .vscode/ # VSCode 调试/烧录配置
|
||||
| 变量名 | 值 | 用途 |
|
||||
|---|---|---|
|
||||
| `GD32_GCC_BIN` | ARM GCC 工具链的 `bin` 目录(也接受根目录) | CMake 编译、GDB 调试 |
|
||||
| `GD32_OPENOCD_BIN` | 包含 `openocd.exe` 的 `bin` 目录 | OpenOCD 烧录/调试 |
|
||||
| `GD32_OPENOCD_SCRIPTS` | OpenOCD `scripts` 根目录 | `-s` 脚本搜索路径 |
|
||||
|
||||
PowerShell 配置示例(路径按本机实际位置填写,设置后重启 VSCode 生效):
|
||||
|
||||
```powershell
|
||||
[Environment]::SetEnvironmentVariable("GD32_GCC_BIN", "D:\toolchain\arm-gnu-toolchain-13.3.rel1\bin", "User")
|
||||
[Environment]::SetEnvironmentVariable("GD32_OPENOCD_BIN", "D:\toolchain\xpack-openocd-0.11.0-3\bin", "User")
|
||||
[Environment]::SetEnvironmentVariable("GD32_OPENOCD_SCRIPTS", "D:\toolchain\xpack-openocd-0.11.0-3\scripts", "User")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 板级配置(`Inc/board_config.h`)
|
||||
|
||||
所有功能开关和引脚定义集中在 `Inc/board_config.h`。功能开关均为单行数值宏:将右侧的 `ENABLE` 或 `DISABLE` 改为另一值即可;其中 `ENABLE` 为 `1`,`DISABLE` 为 `0`。以下为完整的宏开关说明和推荐使用方式。
|
||||
|
||||
### 功能开关速查表
|
||||
|
||||
| 宏 | 作用 | 默认值 | Release 建议 |
|
||||
|---|------|:---:|:---:|
|
||||
| `SOFTWARE_IIC` | I2C 实现方式 | `DISABLE`(硬件) | `DISABLE` |
|
||||
| `DEBUG_MODE` | printf 串口输出 | `DISABLE` | `DISABLE` |
|
||||
| `COM_DEBUG` | 命令帧调试打印 | `DISABLE` | `DISABLE` |
|
||||
| `DEBUG_VERBOSE` | 详细调试信息 | `DISABLE` | `DISABLE` |
|
||||
| `SEGGER_RTT_DETECTION` | SEGGER RTT 支持 | `ENABLE` | `DISABLE` |
|
||||
|
||||
---
|
||||
|
||||
### `SOFTWARE_IIC` — I2C 实现方式
|
||||
|
||||
选择 I2C 使用硬件外设还是软件 GPIO 模拟。
|
||||
|
||||
> 当前工程尚未实现 `SOFTWARE_IIC` 的条件编译驱动选择;该宏已迁移为数值配置,但改值不会在此版本切换 I2C 实现。
|
||||
|
||||
```c
|
||||
#define SOFTWARE_IIC DISABLE // DISABLE: 硬件 I2C(默认);ENABLE: 软件 I2C(GPIO 模拟)
|
||||
```
|
||||
|
||||
| 选项 | 优点 | 缺点 |
|
||||
|------|------|------|
|
||||
| 硬件 I2C | DMA 支持、CPU 占用低 | 仅限固定引脚、调试复杂 |
|
||||
| 软件 I2C | 任意 GPIO、移植方便 | CPU 占用高、速率受限 |
|
||||
|
||||
> 切换后需同步修改下方 I2C 引脚定义。
|
||||
|
||||
---
|
||||
|
||||
### `DEBUG_MODE` — 调试模式
|
||||
|
||||
开启后 USART0 输出 printf 调试信息。**Release 固件必须关闭。**
|
||||
|
||||
```c
|
||||
#define DEBUG_MODE DISABLE // DISABLE: 关闭(默认);ENABLE: 开启调试输出
|
||||
```
|
||||
|
||||
**影响范围:**
|
||||
- 使能 `USART0` 初始化和 `printf` 重定向到串口
|
||||
- 会占用 PA2/PA3 引脚和 USART0 硬件资源
|
||||
- 增加 ROM 约 2~4KB(取决于 printf 调用量)
|
||||
|
||||
---
|
||||
|
||||
### `COM_DEBUG` — 命令帧调试打印
|
||||
|
||||
开启后串口命令解析过程打印每帧的详细内容(地址、长度、数据、校验)。**仅调试通信协议时开启。**
|
||||
|
||||
```c
|
||||
#define COM_DEBUG DISABLE // DISABLE: 关闭(默认);ENABLE: 开启命令帧调试
|
||||
```
|
||||
|
||||
**依赖:** 需要先开启 `DEBUG_MODE`,否则输出无法外发。
|
||||
|
||||
**输出示例:**
|
||||
```text
|
||||
[CMD] ADDR=01 LEN=05 DATA: AA BB CC DD EE CHK=OK
|
||||
```
|
||||
|
||||
> ⚠️ Release 必须关闭,否则大量串口输出会严重拖慢主循环。
|
||||
|
||||
---
|
||||
|
||||
### `DEBUG_VERBOSE` — 详细调试信息
|
||||
|
||||
在 `DEBUG_MODE` 基础上输出更底层的信息,如 I2C 总线扫描结果、MCU 型号识别等。
|
||||
|
||||
```c
|
||||
#define DEBUG_VERBOSE DISABLE // DISABLE: 关闭(默认);ENABLE: 开启详细调试
|
||||
```
|
||||
|
||||
**额外输出:**
|
||||
- 启动时打印 MCU Flash 容量检测结果
|
||||
- I2C 初始化时扫描总线上的设备地址
|
||||
- 其他诊断信息
|
||||
|
||||
> 依赖 `DEBUG_MODE`,开启后 ROM 进一步增加约 1~2KB。
|
||||
|
||||
---
|
||||
|
||||
### `SEGGER_RTT_DETECTION` — SEGGER RTT 支持
|
||||
|
||||
RTT(Real-Time Transfer)是 SEGGER 的调试通道技术,通过 SWD 接口传输数据,不占用串口引脚,速度远超 UART。
|
||||
|
||||
```c
|
||||
#define SEGGER_RTT_DETECTION ENABLE // DISABLE: 禁用 RTT;ENABLE: 启用 RTT(默认)
|
||||
```
|
||||
|
||||
**启用时:**
|
||||
- 自动包含 `SEGGER_RTT.h`,提供 `RTT_printf` / `RTT_WriteString` / `RTT_PutChar` 宏
|
||||
- `SDK/SEGGER_RTT/` 模块参与编译和链接
|
||||
- 可用 J-Link RTT Viewer 或 VSCode + cortex-debug 查看实时日志
|
||||
|
||||
**禁用时:**
|
||||
- 应用层 RTT 头文件引用与调用代码均在预处理阶段排除
|
||||
- RTT 宏展开为空操作;应用目标不生成 RTT 调用代码
|
||||
- 为保持现有 CMake SDK 加载方式,`SDK/SEGGER_RTT/` 仍会参与构建;静态库中未被引用的对象不会被链接器提取
|
||||
|
||||
> **Release 建议关闭** — RTT 依赖调试器连接,量产固件中无意义且占用 ROM。
|
||||
|
||||
**依赖关系总览:**
|
||||
```
|
||||
COM_DEBUG ──── 依赖 ──→ DEBUG_MODE
|
||||
DEBUG_VERBOSE ─ 依赖 ──→ DEBUG_MODE
|
||||
SEGGER_RTT_DETECTION ─ 独立,与 DEBUG_MODE 并行
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 引脚定义
|
||||
|
||||
所有引脚宏集中在 `board_config.h` 尾部,按需修改:
|
||||
|
||||
```c
|
||||
/* I2C */
|
||||
#define I2C_SCL_PORT GPIOF
|
||||
#define I2C_SCL_PIN GPIO_PIN_1
|
||||
#define I2C_SDA_PORT GPIOF
|
||||
#define I2C_SDA_PIN GPIO_PIN_0
|
||||
|
||||
/* LED */
|
||||
#define LED_RCU RCU_GPIOB
|
||||
#define LED_PORT GPIOB
|
||||
#define LED_PIN GPIO_PIN_1
|
||||
|
||||
/* UART */
|
||||
#define UART_GPIO_PORT GPIOA
|
||||
#define UART_TX_PIN GPIO_PIN_2
|
||||
#define UART_RX_PIN GPIO_PIN_3
|
||||
#define UART_BAUDRATE 115200U
|
||||
```
|
||||
|
||||
### MCU 型号自动检测
|
||||
|
||||
`board_config.c` 中的 `mcu_detect_and_config()` 上电自动识别 GD32E230 的 Flash 容量(F4=16K / F6=32K / F8=64K),结果存入全局变量 `g_mcu_flash_size`,并自动选择对应的 UART 外设(USART0 或 USART1)。
|
||||
|
||||
---
|
||||
|
||||
## Flash 偏移配置(配合 Bootloader)
|
||||
|
||||
如果固件需要通过 Bootloader 启动(Bootloader 占用 Flash 前部区域),需修改两处:
|
||||
|
||||
**1. 链接脚本** — `LD/gd32e23x_flash.ld` 第 15 行:
|
||||
```c
|
||||
// 默认:从 Flash 起始运行
|
||||
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 16K
|
||||
|
||||
// 配合 Bootloader:前 8KB 留给 Bootloader,App 从 0x08002000 开始
|
||||
FLASH (rx) : ORIGIN = 0x08002000, LENGTH = 8K
|
||||
```
|
||||
|
||||
**2. 向量表偏移** — `Src/system_gd32e23x.c` 第 44 行:
|
||||
```c
|
||||
// 默认
|
||||
#define VECT_TAB_OFFSET (uint32_t)0x00
|
||||
|
||||
// 配合 Bootloader(值 = Flash 偏移量,不含 0x0800 前缀)
|
||||
#define VECT_TAB_OFFSET (uint32_t)0x2000
|
||||
```
|
||||
|
||||
> ⚠️ 两个偏移值必须对应修改:`LD` 中的 `ORIGIN` 减去 `0x08000000` 应等于 `VECT_TAB_OFFSET`。
|
||||
|
||||
---
|
||||
|
||||
## 工具链准备
|
||||
|
||||
### 1. ARM GCC 工具链
|
||||
|
||||
- **版本**:xpack-arm-none-eabi-gcc-11.3.1-1.1
|
||||
- **建议解压路径**:工程根目录下 `Toolchain/xpack-arm-none-eabi-gcc-11.3.1-1.1`
|
||||
- **官方下载地址**:https://github.com/xpack-dev-tools/arm-none-eabi-gcc-xpack/releases
|
||||
- **路径自定义**:
|
||||
如需自定义工具链路径,修改 `cmake/arm-none-eabi-gcc.cmake` 中的 `_TOOLCHAIN_CANDIDATES` 列表,或通过 CMake 参数传入:
|
||||
```bash
|
||||
cmake --preset Debug -DTOOLCHAIN_DIRECTORY=/your/path/bin
|
||||
```
|
||||
|
||||
### 2. OpenOCD(调试/烧录)
|
||||
|
||||
- **版本**:xpack-openocd-0.11.0-3
|
||||
- **建议解压路径**:任意位置(在 `.vscode/launch.json` 中配置路径)
|
||||
- **获取地址**:https://github.com/burakenez/gd32-tools-xpack-openocd/tree/v0.11.0-3
|
||||
- **说明**:
|
||||
- 本版本提取自 Embedded Builder V1.4.1.23782。
|
||||
- ⚠️ 请勿随意更换版本,因 GD32 MCU 支持有限,推荐严格使用此版本。
|
||||
- **路径自定义**:
|
||||
修改 `.vscode/launch.json` 中 `miDebuggerPath` 和 `serverpath` 指向你的 OpenOCD 路径。
|
||||
|
||||
---
|
||||
|
||||
## 使用说明
|
||||
CMake 也可用 `-DTOOLCHAIN_DIRECTORY=<bin目录>` 临时指定工具链。更换 GCC 版本后需删除 `Build/Debug`、`Build/Release` 重新配置(CMake 缓存了编译器绝对路径)。完整说明见 [gd32e23x_template_cmake_vscode](https://gitea.hulk.wang/hulk/gd32e23x_template_cmake_vscode) 的 README。
|
||||
|
||||
### 编译
|
||||
|
||||
```bash
|
||||
# Debug 构建(-O0, -g3)
|
||||
cmake --preset Debug
|
||||
cmake --build build/Debug
|
||||
在仓库根目录执行:
|
||||
|
||||
# Release 构建(-Os, -g0)
|
||||
```bash
|
||||
cmake --preset Release
|
||||
cmake --build build/Release
|
||||
cmake --build Build/Release
|
||||
```
|
||||
|
||||
### 烧录
|
||||
|
||||
通过 VSCode 任务栏运行 `Flash MCU` 任务,或命令行:
|
||||
Debug 构建:
|
||||
|
||||
```bash
|
||||
openocd -f interface/cmsis-dap.cfg -f target/gd32e23x.cfg -c "program build/Debug/Application.elf verify reset exit"
|
||||
cmake --preset Debug
|
||||
cmake --build Build/Debug
|
||||
```
|
||||
|
||||
### 产物
|
||||
产物位于 `Build/Release/Application/`:
|
||||
|
||||
编译输出位于 `build/<Config>/`:
|
||||
- `Application.elf`
|
||||
- `Application.hex`
|
||||
- `Application.bin`
|
||||
- `Application.map`
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `Application.elf` | ELF 固件(调试用) |
|
||||
| `{项目名}_{版本}_{编译条件}_{日期}.hex` | Hex 文件 |
|
||||
| `{项目名}_{版本}_{编译条件}_{日期}.bin` | 二进制文件 |
|
||||
| `{项目名}_{版本}_{编译条件}_{日期}.list` | 反汇编清单 |
|
||||
| `{项目名}_{版本}_{编译条件}_{日期}.map` | 内存映射 |
|
||||
### 烧录与调试(VSCode 任务)
|
||||
|
||||
---
|
||||
工程自带 `.vscode` 配置(推荐安装扩展:CMake Tools、cortex-debug、C/C++),VSCode 内可直接使用:
|
||||
|
||||
## 时钟配置说明
|
||||
- **OpenOCD 任务**(DAPLink/CMSIS-DAP):`Build and Flash`、`Flash MCU`、`Reset MCU`、`Mass Erase MCU`、`OpenOCD Server`
|
||||
- **J-Link 任务**:`Build and Flash (Jlink)`、`Flash MCU (Jlink)`、`Reset MCU (Jlink)`、`Mass Erase MCU (Jlink)`
|
||||
- **调试**:`Debug with OpenOCD` / `Debug with J-Link`(cortex-debug,带 GD32E230.svd 外设视图)
|
||||
|
||||
本工程默认系统时钟为内部 IRC8M 振荡器经 PLL 倍频后的 72MHz。
|
||||
OpenOCD 使用的 cfg 由 `.vscode/settings.json` 的 `gd32.openocd.targetConfig` 指定(当前为 GD-Link 的 `target/openocd_gdlink_gd32e23x.cfg`);DAPLink 调试器可换成 `interface/cmsis-dap.cfg` + `target/gd32e23x.cfg` 的组合 cfg。J-Link 器件名为 `GD32E230F8`,在 `gd32.jlink.*` 设置中调整。
|
||||
|
||||
如需修改主频或时钟源,请编辑 `Src/system_gd32e23x.c` 文件:
|
||||
命令行烧录(等效 `Flash MCU` 任务):
|
||||
|
||||
1. 查找如下宏定义区:
|
||||
```c
|
||||
// #define __SYSTEM_CLOCK_8M_HXTAL (__HXTAL)
|
||||
// #define __SYSTEM_CLOCK_8M_IRC8M (__IRC8M)
|
||||
// #define __SYSTEM_CLOCK_72M_PLL_HXTAL (uint32_t)(72000000)
|
||||
#define __SYSTEM_CLOCK_72M_PLL_IRC8M_DIV2 (uint32_t)(72000000)
|
||||
```
|
||||
2. 取消你需要的时钟方案的注释,并注释掉其它方案。
|
||||
3. 保存后重新编译工程即可生效。
|
||||
```bash
|
||||
%GD32_OPENOCD_BIN%\openocd.exe ^
|
||||
-s %GD32_OPENOCD_SCRIPTS% ^
|
||||
-f %GD32_OPENOCD_SCRIPTS%\target\openocd_gdlink_gd32e23x.cfg ^
|
||||
-c "init; reset halt; flash write_image erase Build/Release/Application/Application.elf; reset; exit"
|
||||
```
|
||||
|
||||
详细时钟初始化流程可参考 `Src/system_gd32e23x.c` 文件中的 `system_clock_config` 及相关函数实现。
|
||||
## 串口命令(Mx)
|
||||
|
||||
---
|
||||
命令帧格式 `D5 01 LEN [命令] CRC`(LEN 为命令字节数,CRC 为帧内累加和),响应帧 `B5 TYPE LEN [数据] CRC`,`TYPE=0xF0` 表示成功。波特率 115200,8N1。
|
||||
|
||||
## vcpkg 依赖管理(可选)
|
||||
### 基础命令
|
||||
|
||||
本工程可选支持 vcpkg 作为 C/C++ 工具链和构建工具的自动化依赖管理方案。
|
||||
| 命令 | 功能 | 成功响应 |
|
||||
|---|---|---|
|
||||
| `M1` | 开启测距上报,LED 亮 | `ok` |
|
||||
| `M2` | 关闭测距上报,LED 灭 | `ok` |
|
||||
| `M3` | 立即返回最近一帧结果(上报关闭时也可查询) | `d=830;s=0` |
|
||||
| `M888` | 软件重启 | `ok` 后复位 |
|
||||
| `M999` | 输出固件版本号 | `v99.0.0.1` |
|
||||
| `M9999` | 复位进入 OTA/Bootloader | 复位 |
|
||||
|
||||
- 自动下载和管理如 CMake、Ninja 等构建工具,简化环境配置。
|
||||
- 可扩展用于第三方 C/C++ 库的统一管理。
|
||||
### 校准命令
|
||||
|
||||
**启用方法**:
|
||||
1. 在项目根目录创建 `vcpkg-configuration.json` 文件,内容如下:
|
||||
| 命令 | 功能 | 成功响应 |
|
||||
|---|---|---|
|
||||
| `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` |
|
||||
|
||||
```json
|
||||
{
|
||||
"registries": [
|
||||
{
|
||||
"name": "microsoft",
|
||||
"location": "https://aka.ms/vcpkg-ce-default",
|
||||
"kind": "artifact"
|
||||
},
|
||||
{
|
||||
"name": "arm",
|
||||
"location": "https://aka.ms/vcpkg-artifacts-arm",
|
||||
"kind": "artifact"
|
||||
}
|
||||
],
|
||||
"requires": {
|
||||
"arm:tools/ninja-build/ninja": "^1.12.0",
|
||||
"arm:tools/kitware/cmake": "^3.28.4"
|
||||
}
|
||||
}
|
||||
```
|
||||
说明:
|
||||
|
||||
2. 启动 VSCode 或命令行,vcpkg 会自动检测并安装所需工具。
|
||||
- `M102`/`M104` 为阻塞执行,耗时约 2 秒,期间连续上报暂停。
|
||||
- `M101S`/`M103S`/`M102`/`M104` 只改寄存器,掉电丢失;需要掉电保持时接一条 `M105`。
|
||||
- 参数错误返回 `0xFD`,未知命令返回 `0xFE`,校准/Flash 失败返回 `0xFF`。
|
||||
|
||||
如不需要 vcpkg,可忽略本文件。
|
||||
## 串口输出
|
||||
|
||||
连接串口后使用 115200 8N1 查看,例如 COM17:
|
||||
|
||||
```text
|
||||
Distance: 830 mm | status: 0 | signal: 1128 kcps | ambient: 72 kcps | sigma: 3 mm
|
||||
```
|
||||
|
||||
默认每 500ms 输出一次。
|
||||
|
||||
## VL53L4CD 驱动
|
||||
|
||||
驱动文件:
|
||||
|
||||
- `Inc/VL53L4CD_Driver.h`
|
||||
- `Src/VL53L4CD_Driver.c`
|
||||
|
||||
### 主要接口
|
||||
|
||||
| 函数 | 作用 |
|
||||
|---|---|
|
||||
| `vl53l4cd_init()` | 初始化传感器,写入默认配置,默认 50ms 时序预算 |
|
||||
| `vl53l4cd_start_ranging()` | 启动连续测距 |
|
||||
| `vl53l4cd_stop_ranging()` | 停止连续测距 |
|
||||
| `vl53l4cd_data_ready()` | 查询是否有新结果 |
|
||||
| `vl53l4cd_clear_interrupt()` | 清除测距完成中断 |
|
||||
| `vl53l4cd_get_result()` | 读取并解析测距结果 |
|
||||
| `vl53l4cd_set_range_timing()` | 设置 10-200ms 时序预算,当前仅支持连续模式 |
|
||||
| `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 位寄存器 |
|
||||
|
||||
## 偏移校准
|
||||
|
||||
VL53L4CD 出厂带有默认偏移,但器件之间存在差异,固定安装场景建议做一次单点校准。
|
||||
|
||||
当前安装位置的目标距离为 44mm,已通过 20 帧有效采样完成校准,校准得到的偏移量为 `-9mm`。固件在 `vl53l4cd_init()` 后调用 `vl53l4cd_set_offset(VL53L4CD_CALIB_OFFSET_MM)` 写回,因此每次上电都会保持校准结果。
|
||||
|
||||
需要重新校准时:
|
||||
|
||||
1. 把目标放在已知距离,例如 44mm。
|
||||
2. 将 `Inc/board_config.h` 中 `VL53L4CD_AUTO_CALIB` 改为 `CFG_ENABLE`。
|
||||
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()` 末尾:
|
||||
|
||||
```c
|
||||
return vl53l4cd_set_range_timing(50U, 0U);
|
||||
```
|
||||
|
||||
第一个参数是时序预算,范围 10-200ms。第二个参数当前只能传 `0`,表示连续测距模式。
|
||||
|
||||
### 修改输出间隔
|
||||
|
||||
在 `Src/main.c` 主循环中:
|
||||
|
||||
```c
|
||||
delay_ms(500);
|
||||
```
|
||||
|
||||
改为需要的毫秒数即可。
|
||||
|
||||
## I2C 16 位寄存器说明
|
||||
|
||||
## 测距结果字段
|
||||
|
||||
`vl53l4cd_get_result()` 返回 `vl53l4cd_result_t`:
|
||||
|
||||
| 字段 | 含义 |
|
||||
|---|---|
|
||||
| `distance_mm` | 距离,单位 mm |
|
||||
| `range_status` | 测距状态码 |
|
||||
| `number_of_spad` | 有效 SPAD 数 |
|
||||
| `signal_rate_kcps` | 目标反射信号强度 |
|
||||
| `ambient_rate_kcps` | 环境光/背景噪声强度 |
|
||||
| `signal_per_spad_kcps` | 每个 SPAD 的信号强度 |
|
||||
| `ambient_per_spad_kcps` | 每个 SPAD 的环境光强度 |
|
||||
| `sigma_mm` | 测距噪声/不确定度,单位 mm |
|
||||
|
||||
## 测距状态码
|
||||
|
||||
| 状态码 | 含义 |
|
||||
|---:|---|
|
||||
| 0 | 测距有效 |
|
||||
| 1 | Sigma 超限,结果不稳定 |
|
||||
| 2 | 信号太弱,目标太远、太黑或反射率低 |
|
||||
| 3 | 目标过近 |
|
||||
| 4 | 相位故障 |
|
||||
| 5 | 硬件故障 |
|
||||
| 6 | 未检测到目标 |
|
||||
| 7 | 近距离信号不足 |
|
||||
| 255 | 未知状态 |
|
||||
|
||||
## I2C 16 位寄存器说明
|
||||
|
||||
VL53L4CD 的寄存器地址是 16 位。本项目的 `i2c_read()` / `i2c_write()` 已改为支持 `uint16_t` 寄存器地址,发送时先发高字节、再发低字节。
|
||||
|
||||
对原有 8 位寄存器地址的 I2C 器件仍然兼容:地址高字节为 `0x00`,发送序列与原来一致。
|
||||
|
||||
## Flash 体积说明
|
||||
|
||||
链接脚本代码区为 15K(16K 容量器件预留最后 1KB 页存放校准数据)。当前 Release 固件约:
|
||||
|
||||
```text
|
||||
text 12908
|
||||
data 24
|
||||
bss 1704
|
||||
```
|
||||
|
||||
15K 内仍有约 2.4K 余量。为省体积,主循环测距上报与启动信息改为手写格式化输出(不再链接 vfprintf,省约 2.7K),输出格式与旧版本逐字节一致。Debug 构建优化等级由 `-O0` 调整为 `-Og`(-O0 已无法放下),text 约 14876。保留 `printf` 会增加约 2.5K,因此 `DEBUG_VERBOSE` 全开仍会让 Flash 非常紧张。
|
||||
|
||||
## 已知限制
|
||||
|
||||
- 驱动只支持连续测距模式
|
||||
- 距离阈值中断、自主低功耗模式、ULP 模式尚未实现
|
||||
- VL53L4CD 是单区传感器,不是多区测距
|
||||
- 串扰补偿为单区平面补偿(梯度寄存器清零),玻璃需正对传感器且尽量平行
|
||||
- `DEBUG_VERBOSE` 全开时 IIC 调试打印会让 Flash 非常紧张
|
||||
|
||||
## 变更记录
|
||||
|
||||
当前迭代的详细说明见 [CHANGES.md](CHANGES.md)。
|
||||
|
||||
@@ -0,0 +1,508 @@
|
||||
/**
|
||||
* @file VL53L4CD_Driver.c
|
||||
* @brief VL53L4CD 测距传感器驱动实现
|
||||
*
|
||||
* 初始化配置参考 ST VL53L4CD ULD / Pololu Arduino 库,
|
||||
* 使用 16 位寄存器地址,底层走项目现有硬件 I2C0。
|
||||
*/
|
||||
#include "VL53L4CD_Driver.h"
|
||||
#include "i2c.h"
|
||||
#include "systick.h"
|
||||
|
||||
/* 当前传感器 I2C 地址,默认 0x29 */
|
||||
static uint8_t g_vl53l4cd_addr = VL53L4CD_I2C_ADDR;
|
||||
|
||||
/* VL53L4CD 默认配置,0x30..0x87 共 88 字节,来自 ST ULD */
|
||||
static const uint8_t g_vl53l4cd_default_config[88] = {
|
||||
0x11, 0x02, 0x00, 0x02, 0x08, 0x00, 0x08, 0x10,
|
||||
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00,
|
||||
0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0B,
|
||||
0x00, 0x00, 0x02, 0x14, 0x21, 0x00, 0x00, 0x05,
|
||||
0x00, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x38,
|
||||
0xFF, 0x01, 0x00, 0x08, 0x00, 0x00, 0x01, 0xCC,
|
||||
0x07, 0x01, 0xF1, 0x05, 0x00, 0xA0, 0x00, 0x80,
|
||||
0x08, 0x38, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x89,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0x07, 0x05, 0x06, 0x06, 0x00, 0x00, 0x02, 0xC7,
|
||||
0xFF, 0x9B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 连续写多个寄存器字节
|
||||
* @param[in] reg: 起始 16 位寄存器地址
|
||||
* @param[in] data: 数据指针
|
||||
* @param[in] length: 字节数
|
||||
* @retval true: 成功; false: 失败
|
||||
*/
|
||||
static bool vl53l4cd_write_regs(uint16_t reg, const uint8_t *data, uint8_t length)
|
||||
{
|
||||
if (data == NULL || length == 0U) {
|
||||
return false;
|
||||
}
|
||||
return (i2c_write(g_vl53l4cd_addr, reg, (uint8_t *)data, length) == I2C_RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
bool vl53l4cd_read_reg8(uint16_t reg, uint8_t *value)
|
||||
{
|
||||
if (value == NULL) {
|
||||
return false;
|
||||
}
|
||||
return (i2c_read(g_vl53l4cd_addr, reg, value, 1U) == I2C_RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
bool vl53l4cd_read_reg16(uint16_t reg, uint16_t *value)
|
||||
{
|
||||
uint8_t buf[2];
|
||||
|
||||
if (value == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (i2c_read(g_vl53l4cd_addr, reg, buf, 2U) != I2C_RESULT_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
*value = (uint16_t)(((uint16_t)buf[0] << 8) | buf[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vl53l4cd_write_reg8(uint16_t reg, uint8_t value)
|
||||
{
|
||||
return vl53l4cd_write_regs(reg, &value, 1U);
|
||||
}
|
||||
|
||||
bool vl53l4cd_write_reg16(uint16_t reg, uint16_t value)
|
||||
{
|
||||
uint8_t buf[2];
|
||||
|
||||
buf[0] = (uint8_t)(value >> 8);
|
||||
buf[1] = (uint8_t)(value & 0xFF);
|
||||
return vl53l4cd_write_regs(reg, buf, 2U);
|
||||
}
|
||||
|
||||
bool vl53l4cd_data_ready(void)
|
||||
{
|
||||
uint8_t status = 0U;
|
||||
|
||||
if (!vl53l4cd_read_reg8(VL53L4CD_REG_GPIO_TIO_HV_STATUS, &status)) {
|
||||
return false;
|
||||
}
|
||||
/* 默认中断极性配置下,bit0=0 表示新结果就绪 */
|
||||
return ((status & 0x01U) == 0U);
|
||||
}
|
||||
|
||||
bool vl53l4cd_clear_interrupt(void)
|
||||
{
|
||||
return vl53l4cd_write_reg8(VL53L4CD_REG_SYSTEM_INTERRUPT_CLEAR, 0x01U);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 等待新的测距结果就绪
|
||||
* @param[in] timeout_ms: 超时时间,单位 ms
|
||||
* @retval true: 结果就绪; false: 超时或读取失败
|
||||
*/
|
||||
static bool vl53l4cd_wait_for_data_ready(uint32_t timeout_ms)
|
||||
{
|
||||
uint32_t elapsed_ms = 0U;
|
||||
|
||||
while (elapsed_ms < timeout_ms) {
|
||||
if (vl53l4cd_data_ready()) {
|
||||
return true;
|
||||
}
|
||||
delay_ms(1);
|
||||
elapsed_ms++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool vl53l4cd_get_result(vl53l4cd_result_t *result)
|
||||
{
|
||||
static const uint8_t status_rtn[24] = {
|
||||
255, 255, 255, 5, 2, 4, 1, 7, 3, 0,
|
||||
255, 255, 9, 13, 255, 255, 255, 255, 10, 6,
|
||||
255, 255, 11, 12
|
||||
};
|
||||
uint8_t buf[VL53L4CD_RESULT_BLOCK_SIZE];
|
||||
uint8_t raw_status;
|
||||
|
||||
if (result == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (i2c_read(g_vl53l4cd_addr, VL53L4CD_REG_RESULT_RANGE_STATUS,
|
||||
buf, VL53L4CD_RESULT_BLOCK_SIZE) != I2C_RESULT_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
raw_status = buf[0] & 0x1FU;
|
||||
result->range_status = (raw_status < 24U) ? status_rtn[raw_status]
|
||||
: (uint8_t)VL53L4CD_STATUS_UNKNOWN;
|
||||
result->number_of_spad = buf[3];
|
||||
result->signal_rate_kcps = ((uint32_t)buf[5] << 8 | buf[6]) * 8U;
|
||||
result->ambient_rate_kcps = ((uint32_t)buf[7] << 8 | buf[8]) * 8U;
|
||||
result->sigma_mm = ((uint16_t)buf[9] << 8 | buf[10]) / 4U;
|
||||
result->distance_mm = (uint16_t)(((uint16_t)buf[13] << 8) | buf[14]);
|
||||
|
||||
if (result->number_of_spad != 0U) {
|
||||
result->signal_per_spad_kcps = result->signal_rate_kcps / result->number_of_spad;
|
||||
result->ambient_per_spad_kcps = result->ambient_rate_kcps / result->number_of_spad;
|
||||
} else {
|
||||
result->signal_per_spad_kcps = 0U;
|
||||
result->ambient_per_spad_kcps = 0U;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vl53l4cd_set_range_timing(uint8_t timing_budget_ms, uint32_t inter_measurement_ms)
|
||||
{
|
||||
uint16_t osc_frequency = 0U;
|
||||
uint32_t timing_budget_us;
|
||||
uint32_t macro_period_us;
|
||||
uint32_t tmp;
|
||||
uint16_t ls_byte;
|
||||
uint8_t ms_byte;
|
||||
|
||||
if (timing_budget_ms < 10U || timing_budget_ms > 200U) {
|
||||
return false;
|
||||
}
|
||||
if (inter_measurement_ms != 0U) {
|
||||
/* 为节省 flash,本版本只支持连续模式 */
|
||||
return false;
|
||||
}
|
||||
if (!vl53l4cd_read_reg16(0x0006U, &osc_frequency) || osc_frequency == 0U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
timing_budget_us = (uint32_t)timing_budget_ms * 1000U;
|
||||
macro_period_us = ((uint32_t)2304U * (0x40000000U / osc_frequency)) >> 6U;
|
||||
|
||||
/* 连续模式:测量周期写 0 */
|
||||
{
|
||||
uint8_t zero[4] = {0U, 0U, 0U, 0U};
|
||||
if (!vl53l4cd_write_regs(VL53L4CD_REG_INTERMEASUREMENT_MS, zero, 4U)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
timing_budget_us -= 2500U;
|
||||
|
||||
timing_budget_us <<= 12U;
|
||||
|
||||
/* RANGE_CONFIG_A: 基于 16 的宏周期 */
|
||||
tmp = (macro_period_us * 16U) >> 6U;
|
||||
ls_byte = (uint16_t)(((timing_budget_us + (tmp >> 1U)) / tmp) - 1U);
|
||||
ms_byte = 0U;
|
||||
while (ls_byte > 0xFFU) {
|
||||
ls_byte >>= 1U;
|
||||
ms_byte++;
|
||||
}
|
||||
if (!vl53l4cd_write_reg16(VL53L4CD_REG_RANGE_CONFIG_A,
|
||||
(uint16_t)((uint16_t)ms_byte << 8 | ls_byte))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* RANGE_CONFIG_B: 基于 12 的宏周期 */
|
||||
tmp = (macro_period_us * 12U) >> 6U;
|
||||
ls_byte = (uint16_t)(((timing_budget_us + (tmp >> 1U)) / tmp) - 1U);
|
||||
ms_byte = 0U;
|
||||
while (ls_byte > 0xFFU) {
|
||||
ls_byte >>= 1U;
|
||||
ms_byte++;
|
||||
}
|
||||
return vl53l4cd_write_reg16(VL53L4CD_REG_RANGE_CONFIG_B,
|
||||
(uint16_t)((uint16_t)ms_byte << 8 | ls_byte));
|
||||
}
|
||||
|
||||
bool vl53l4cd_set_offset(int16_t offset_mm)
|
||||
{
|
||||
uint16_t reg_value;
|
||||
|
||||
reg_value = (uint16_t)((int16_t)offset_mm * 4);
|
||||
|
||||
return vl53l4cd_write_reg16(VL53L4CD_REG_RANGE_OFFSET_MM, reg_value) &&
|
||||
vl53l4cd_write_reg16(VL53L4CD_REG_INNER_OFFSET_MM, 0U) &&
|
||||
vl53l4cd_write_reg16(VL53L4CD_REG_OUTER_OFFSET_MM, 0U);
|
||||
}
|
||||
|
||||
bool vl53l4cd_get_offset(int16_t *offset_mm)
|
||||
{
|
||||
uint16_t reg_value;
|
||||
int16_t raw;
|
||||
|
||||
if (offset_mm == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (!vl53l4cd_read_reg16(VL53L4CD_REG_RANGE_OFFSET_MM, ®_value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
raw = (int16_t)reg_value;
|
||||
*offset_mm = (int16_t)(raw / 4);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vl53l4cd_calibrate_offset(uint16_t target_distance_mm, uint16_t num_samples,
|
||||
int16_t *measured_offset_mm)
|
||||
{
|
||||
vl53l4cd_result_t result;
|
||||
int32_t distance_sum = 0;
|
||||
uint16_t valid_samples = 0;
|
||||
uint16_t i;
|
||||
int16_t average_distance;
|
||||
int16_t offset;
|
||||
|
||||
if (measured_offset_mm == NULL ||
|
||||
target_distance_mm < 10U || target_distance_mm > 1000U ||
|
||||
num_samples < 5U || num_samples > 255U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!vl53l4cd_stop_ranging() ||
|
||||
!vl53l4cd_set_offset(0) ||
|
||||
!vl53l4cd_start_ranging()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 热机 10 帧,丢弃 */
|
||||
for (i = 0U; i < 10U; i++) {
|
||||
if (!vl53l4cd_wait_for_data_ready(VL53L4CD_DATA_TIMEOUT_MS)) {
|
||||
return false;
|
||||
}
|
||||
(void)vl53l4cd_clear_interrupt();
|
||||
(void)vl53l4cd_get_result(&result);
|
||||
}
|
||||
|
||||
/* 统计有效测距样本 */
|
||||
for (i = 0U; i < num_samples; i++) {
|
||||
if (!vl53l4cd_wait_for_data_ready(VL53L4CD_DATA_TIMEOUT_MS)) {
|
||||
return false;
|
||||
}
|
||||
(void)vl53l4cd_clear_interrupt();
|
||||
if (vl53l4cd_get_result(&result) &&
|
||||
result.range_status == VL53L4CD_STATUS_RANGE_VALID) {
|
||||
distance_sum += (int32_t)result.distance_mm;
|
||||
valid_samples++;
|
||||
}
|
||||
}
|
||||
|
||||
(void)vl53l4cd_stop_ranging();
|
||||
|
||||
if (valid_samples == 0U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
average_distance = (int16_t)(distance_sum / (int32_t)valid_samples);
|
||||
offset = (int16_t)((int16_t)target_distance_mm - average_distance);
|
||||
|
||||
if (!vl53l4cd_set_offset(offset)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*measured_offset_mm = offset;
|
||||
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;
|
||||
uint8_t firmware_status = 0U;
|
||||
uint32_t elapsed_ms = 0U;
|
||||
uint16_t reg;
|
||||
uint8_t count;
|
||||
|
||||
/* 上电后固件需要一点启动时间,先重试读取型号 */
|
||||
while (elapsed_ms < VL53L4CD_BOOT_TIMEOUT_MS) {
|
||||
if (vl53l4cd_read_reg16(VL53L4CD_REG_IDENTIFICATION_MODEL_ID, &model_id) &&
|
||||
model_id == 0xEBAAU) {
|
||||
break;
|
||||
}
|
||||
delay_ms(2);
|
||||
elapsed_ms += 2U;
|
||||
}
|
||||
if (model_id != 0xEBAAU) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 等待固件启动完成,FIRMWARE__SYSTEM_STATUS == 0x03 */
|
||||
elapsed_ms = 0U;
|
||||
while (elapsed_ms < VL53L4CD_BOOT_TIMEOUT_MS) {
|
||||
if (vl53l4cd_read_reg8(VL53L4CD_REG_FIRMWARE_SYSTEM_STATUS, &firmware_status) &&
|
||||
firmware_status == 0x03U) {
|
||||
break;
|
||||
}
|
||||
delay_ms(2);
|
||||
elapsed_ms += 2U;
|
||||
}
|
||||
if (firmware_status != 0x03U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* I2C/GPIO 上拉配置:本板按 3.3V AVDD 上拉处理 */
|
||||
if (!vl53l4cd_write_reg8(0x002DU, 0x00U) ||
|
||||
!vl53l4cd_write_reg8(0x002EU, 0x00U) ||
|
||||
!vl53l4cd_write_reg8(0x002FU, 0x00U)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 写入 0x30..0x87 默认配置,分 3 次块写 */
|
||||
for (reg = 0x0030U; reg <= 0x0087U; reg += 30U) {
|
||||
uint8_t block[30];
|
||||
count = 0U;
|
||||
while (count < 30U && (reg + count) <= 0x0087U) {
|
||||
block[count] = g_vl53l4cd_default_config[reg + count - 0x0030U];
|
||||
count++;
|
||||
}
|
||||
if (!vl53l4cd_write_regs(reg, block, count)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* 启动 VHV 校准并等待首个结果 */
|
||||
if (!vl53l4cd_write_reg8(VL53L4CD_REG_SYSTEM_START, 0x40U)) {
|
||||
return false;
|
||||
}
|
||||
elapsed_ms = 0U;
|
||||
while (!vl53l4cd_data_ready()) {
|
||||
delay_ms(5);
|
||||
elapsed_ms += 5U;
|
||||
if (elapsed_ms >= VL53L4CD_DATA_TIMEOUT_MS) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!vl53l4cd_clear_interrupt() ||
|
||||
!vl53l4cd_stop_ranging() ||
|
||||
!vl53l4cd_write_reg8(VL53L4CD_REG_VHV_TIMEOUT_MACROP_LOOP_BOUND, 0x09U) ||
|
||||
!vl53l4cd_write_reg8(0x000BU, 0x00U) ||
|
||||
!vl53l4cd_write_reg16(0x0024U, 0x0500U)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 默认 50ms 时序预算,连续模式 */
|
||||
return vl53l4cd_set_range_timing(50U, 0U);
|
||||
}
|
||||
|
||||
bool vl53l4cd_start_ranging(void)
|
||||
{
|
||||
/* 本驱动固定使用连续模式 */
|
||||
return vl53l4cd_write_reg8(VL53L4CD_REG_SYSTEM_START, 0x21U);
|
||||
}
|
||||
|
||||
bool vl53l4cd_stop_ranging(void)
|
||||
{
|
||||
return vl53l4cd_write_reg8(VL53L4CD_REG_SYSTEM_START, 0x80U);
|
||||
}
|
||||
@@ -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 "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<value>: 设置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<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));
|
||||
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 软件重启命令
|
||||
|
||||
@@ -640,13 +640,13 @@ i2c_result_t i2c_read_16bits(uint8_t slave_addr, uint8_t reg_addr, uint8_t *data
|
||||
/*!
|
||||
\brief write data to I2C device with configurable length
|
||||
\param[in] slave_addr: slave device address (7-bit)
|
||||
\param[in] reg_addr: register address
|
||||
\param[in] reg_addr: register address (16-bit, high byte first)
|
||||
\param[in] data: pointer to data buffer
|
||||
\param[in] length: number of bytes to write (1-255)
|
||||
\param[out] none
|
||||
\retval i2c_result_t: operation result
|
||||
*/
|
||||
i2c_result_t i2c_write(uint8_t slave_addr, uint8_t reg_addr, uint8_t *data, uint8_t length) {
|
||||
i2c_result_t i2c_write(uint8_t slave_addr, uint16_t reg_addr, uint8_t *data, uint8_t length) {
|
||||
i2c_state_t state = I2C_STATE_START;
|
||||
uint16_t timeout = 0;
|
||||
uint8_t retry_count = 0;
|
||||
@@ -727,8 +727,18 @@ i2c_result_t i2c_write(uint8_t slave_addr, uint8_t reg_addr, uint8_t *data, uint
|
||||
break;
|
||||
}
|
||||
|
||||
/* send register address */
|
||||
i2c_data_transmit(I2C0, reg_addr);
|
||||
/* send register address, high byte first */
|
||||
if(_i2c_wait_flag_timeout(I2C_FLAG_TBE) != I2C_RESULT_SUCCESS) {
|
||||
state = I2C_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
i2c_data_transmit(I2C0, (uint8_t)(reg_addr >> 8));
|
||||
|
||||
if(_i2c_wait_flag_timeout(I2C_FLAG_TBE) != I2C_RESULT_SUCCESS) {
|
||||
state = I2C_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
i2c_data_transmit(I2C0, (uint8_t)(reg_addr & 0xFF));
|
||||
state = I2C_STATE_TRANSMIT_DATA;
|
||||
break;
|
||||
|
||||
@@ -814,12 +824,12 @@ i2c_result_t i2c_write(uint8_t slave_addr, uint8_t reg_addr, uint8_t *data, uint
|
||||
/*!
|
||||
\brief read data from I2C device with configurable length
|
||||
\param[in] slave_addr: slave device address (7-bit)
|
||||
\param[in] reg_addr: register address
|
||||
\param[in] reg_addr: register address (16-bit, high byte first)
|
||||
\param[out] data: pointer to data buffer
|
||||
\param[in] length: number of bytes to read (1-255)
|
||||
\retval i2c_result_t: operation result
|
||||
*/
|
||||
i2c_result_t i2c_read(uint8_t slave_addr, uint8_t reg_addr, uint8_t *data, uint8_t length) {
|
||||
i2c_result_t i2c_read(uint8_t slave_addr, uint16_t reg_addr, uint8_t *data, uint8_t length) {
|
||||
i2c_state_t state = I2C_STATE_START;
|
||||
uint16_t timeout = 0;
|
||||
uint8_t retry_count = 0;
|
||||
@@ -937,8 +947,18 @@ i2c_result_t i2c_read(uint8_t slave_addr, uint8_t reg_addr, uint8_t *data, uint8
|
||||
break;
|
||||
}
|
||||
|
||||
/* send register address */
|
||||
i2c_data_transmit(I2C0, reg_addr);
|
||||
/* send register address, high byte first */
|
||||
if(_i2c_wait_flag_timeout(I2C_FLAG_TBE) != I2C_RESULT_SUCCESS) {
|
||||
state = I2C_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
i2c_data_transmit(I2C0, (uint8_t)(reg_addr >> 8));
|
||||
|
||||
if(_i2c_wait_flag_timeout(I2C_FLAG_TBE) != I2C_RESULT_SUCCESS) {
|
||||
state = I2C_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
i2c_data_transmit(I2C0, (uint8_t)(reg_addr & 0xFF));
|
||||
state = I2C_STATE_RESTART;
|
||||
break;
|
||||
|
||||
|
||||
+162
-17
@@ -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,9 +38,100 @@ OF SUCH DAMAGE.
|
||||
#include "uart_ring_buffer.h"
|
||||
#include "led.h"
|
||||
#include "command.h"
|
||||
#include <stdio.h>
|
||||
#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
|
||||
@@ -76,12 +167,66 @@ int main(void)
|
||||
i2c_bus_reset();
|
||||
#endif
|
||||
|
||||
if (vl53l4cd_init()) {
|
||||
#if VL53L4CD_AUTO_CALIB == CFG_ENABLE
|
||||
int16_t cal_offset = 0;
|
||||
|
||||
if (vl53l4cd_calibrate_offset(VL53L4CD_CALIB_TARGET_MM,
|
||||
VL53L4CD_CALIB_SAMPLES,
|
||||
&cal_offset)) {
|
||||
uart_write_str("VL53L4CD offset calibrated: ");
|
||||
uart_write_i32(cal_offset);
|
||||
uart_write_str(" mm\r\n");
|
||||
} else {
|
||||
uart_write_str("VL53L4CD offset calibration failed\r\n");
|
||||
}
|
||||
#else
|
||||
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();
|
||||
uart_write_str("VL53L4CD ready\r\n");
|
||||
} else {
|
||||
uart_write_str("VL53L4CD init failed\r\n");
|
||||
}
|
||||
|
||||
/* ========== Command Testing ========== */
|
||||
|
||||
|
||||
/* ========== */
|
||||
|
||||
while(1){
|
||||
command_process(); /* Process UART commands */
|
||||
|
||||
if (vl53l4cd_data_ready()) {
|
||||
vl53l4cd_result_t result;
|
||||
|
||||
vl53l4cd_clear_interrupt();
|
||||
if (vl53l4cd_get_result(&result)) {
|
||||
command_set_last_result(&result); /* 供 M3 命令查询 */
|
||||
if (get_sensor_report_enabled()) {
|
||||
report_result(&result);
|
||||
}
|
||||
}
|
||||
delay_ms(500);
|
||||
}
|
||||
|
||||
delay_ms(10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,62 @@
|
||||
# You can change TOOLCHAIN_DIRECTORY if you want to use different toolchain.
|
||||
# set(TOOLCHAIN_DIRECTORY "${CMAKE_SOURCE_DIR}/Toolchain/xpack-arm-none-eabi-gcc-11.3.1-1.1/bin")
|
||||
# The project never stores a compiler package. Each machine supplies a path.
|
||||
# TOOLCHAIN_DIRECTORY (CMake cache) has priority over the environment variable.
|
||||
# GD32_GCC_ENV_VAR lets a workstation use a different variable name if needed.
|
||||
set(TOOLCHAIN_DIRECTORY "" CACHE PATH "Path to arm-none-eabi toolchain bin directory")
|
||||
set(GD32_GCC_ENV_VAR "GD32_GCC_BIN" CACHE STRING "Environment variable containing the ARM GCC path")
|
||||
set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES TOOLCHAIN_DIRECTORY GD32_GCC_ENV_VAR)
|
||||
|
||||
if(GD32_GCC_ENV_VAR AND NOT GD32_GCC_ENV_VAR MATCHES "^GD32_[A-Za-z0-9_]+$")
|
||||
message(FATAL_ERROR "GD32_GCC_ENV_VAR must use the GD32_ prefix: ${GD32_GCC_ENV_VAR}")
|
||||
endif()
|
||||
|
||||
if(NOT TOOLCHAIN_DIRECTORY AND GD32_GCC_ENV_VAR)
|
||||
if(DEFINED ENV{${GD32_GCC_ENV_VAR}} AND NOT "$ENV{${GD32_GCC_ENV_VAR}}" STREQUAL "")
|
||||
file(TO_CMAKE_PATH "$ENV{${GD32_GCC_ENV_VAR}}" TOOLCHAIN_DIRECTORY)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT TOOLCHAIN_DIRECTORY)
|
||||
# Add extra auto-detect paths in this list when using another machine.
|
||||
# Example: "C:/your/path/xpack-arm-none-eabi-gcc-11.3.1-1.1/bin"
|
||||
set(_TOOLCHAIN_CANDIDATES
|
||||
"D:/toolchain/xpack-arm-none-eabi-gcc-11.3.1-1.1/bin"
|
||||
"C:/toolchain/xpack-arm-none-eabi-gcc-11.3.1-1.1/bin"
|
||||
"${CMAKE_SOURCE_DIR}/Toolchain/xpack-arm-none-eabi-gcc-11.3.1-1.1/bin"
|
||||
)
|
||||
|
||||
foreach(_candidate IN LISTS _TOOLCHAIN_CANDIDATES)
|
||||
if(EXISTS "${_candidate}/arm-none-eabi-gcc.exe" OR EXISTS "${_candidate}/arm-none-eabi-gcc")
|
||||
set(TOOLCHAIN_DIRECTORY "${_candidate}")
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
message(FATAL_ERROR
|
||||
"Cannot find ARM GCC toolchain. Set ${GD32_GCC_ENV_VAR} in the machine environment "
|
||||
"or pass -DTOOLCHAIN_DIRECTORY=<toolchain-bin>.")
|
||||
endif()
|
||||
|
||||
file(TO_CMAKE_PATH "${TOOLCHAIN_DIRECTORY}" TOOLCHAIN_DIRECTORY)
|
||||
get_filename_component(TOOLCHAIN_DIRECTORY "${TOOLCHAIN_DIRECTORY}" ABSOLUTE)
|
||||
|
||||
if(WIN32)
|
||||
set(TOOLCHAIN_SUFFIX ".exe")
|
||||
else()
|
||||
set(TOOLCHAIN_SUFFIX "")
|
||||
endif()
|
||||
|
||||
# Accept either the package root or its bin directory.
|
||||
if(NOT EXISTS "${TOOLCHAIN_DIRECTORY}/arm-none-eabi-gcc${TOOLCHAIN_SUFFIX}" AND
|
||||
EXISTS "${TOOLCHAIN_DIRECTORY}/bin/arm-none-eabi-gcc${TOOLCHAIN_SUFFIX}")
|
||||
set(TOOLCHAIN_DIRECTORY "${TOOLCHAIN_DIRECTORY}/bin")
|
||||
endif()
|
||||
|
||||
set(_ARM_GCC_EXECUTABLE "${TOOLCHAIN_DIRECTORY}/arm-none-eabi-gcc${TOOLCHAIN_SUFFIX}")
|
||||
|
||||
set(_TOOLCHAIN_BINARIES
|
||||
arm-none-eabi-gcc
|
||||
arm-none-eabi-g++
|
||||
arm-none-eabi-gdb
|
||||
arm-none-eabi-ar
|
||||
arm-none-eabi-ranlib
|
||||
arm-none-eabi-as
|
||||
arm-none-eabi-ld
|
||||
arm-none-eabi-objcopy
|
||||
arm-none-eabi-objdump
|
||||
arm-none-eabi-size
|
||||
)
|
||||
foreach(_binary IN LISTS _TOOLCHAIN_BINARIES)
|
||||
if(NOT EXISTS "${TOOLCHAIN_DIRECTORY}/${_binary}${TOOLCHAIN_SUFFIX}")
|
||||
message(FATAL_ERROR
|
||||
"ARM GCC toolchain is incomplete; missing ${_binary}${TOOLCHAIN_SUFFIX} in ${TOOLCHAIN_DIRECTORY}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(CMAKE_C_FLAGS_DEBUG "")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "")
|
||||
set(CMAKE_ASM_FLAGS_DEBUG "")
|
||||
@@ -26,18 +64,10 @@ set(CMAKE_C_FLAGS_RELEASE "")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "")
|
||||
set(CMAKE_ASM_FLAGS_RELEASE "")
|
||||
|
||||
if(NOT TOOLCHAIN_DIRECTORY)
|
||||
message(FATAL_ERROR "Cannot find ARM GCC toolchain. Set TOOLCHAIN_DIRECTORY cache variable or install toolchain in one of the default locations.")
|
||||
endif()
|
||||
|
||||
set(CMAKE_COLOR_DIAGNOSTICS ON)
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "")
|
||||
|
||||
if(WIN32)
|
||||
set(TOOLCHAIN_SUFFIX ".exe")
|
||||
endif()
|
||||
|
||||
set(CMAKE_SYSTEM_NAME Generic)
|
||||
set(CMAKE_SYSTEM_PROCESSOR arm)
|
||||
|
||||
@@ -50,21 +80,48 @@ set(FLAGS "-mcpu=cortex-m23 -std=gnu11 -fstack-usage -
|
||||
set(ASM_FLAGS "-x assembler-with-cpp")
|
||||
set(CPP_FLAGS "-fno-rtti -fno-exceptions -fno-threadsafe-statics")
|
||||
|
||||
set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}gcc${TOOLCHAIN_SUFFIX})
|
||||
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}g++${TOOLCHAIN_SUFFIX})
|
||||
set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}gcc${TOOLCHAIN_SUFFIX})
|
||||
set(_REQUESTED_C_COMPILER "${TOOLCHAIN_PREFIX}gcc${TOOLCHAIN_SUFFIX}")
|
||||
if(DEFINED CMAKE_C_COMPILER AND CMAKE_C_COMPILER AND
|
||||
IS_ABSOLUTE "${CMAKE_C_COMPILER}" AND
|
||||
NOT CMAKE_C_COMPILER STREQUAL "${_REQUESTED_C_COMPILER}")
|
||||
message(FATAL_ERROR
|
||||
"The build directory is already configured for a different ARM GCC compiler:\n"
|
||||
" cached: ${CMAKE_C_COMPILER}\n"
|
||||
" selected: ${_REQUESTED_C_COMPILER}\n"
|
||||
"Delete the build directory or run CMake Tools 'Delete Cache and Reconfigure'.")
|
||||
endif()
|
||||
|
||||
set(CMAKE_C_COMPILER "${_REQUESTED_C_COMPILER}")
|
||||
set(CMAKE_CXX_COMPILER "${TOOLCHAIN_PREFIX}g++${TOOLCHAIN_SUFFIX}")
|
||||
set(CMAKE_ASM_COMPILER "${TOOLCHAIN_PREFIX}gcc${TOOLCHAIN_SUFFIX}")
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FLAGS} ${CPP_FLAGS}")
|
||||
set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} ${FLAGS} ${ASM_FLAGS}")
|
||||
set(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}objcopy${TOOLCHAIN_SUFFIX})
|
||||
set(CMAKE_SIZE ${TOOLCHAIN_PREFIX}size${TOOLCHAIN_SUFFIX})
|
||||
set(CMAKE_OBJDUMP ${TOOLCHAIN_PREFIX}objdump${TOOLCHAIN_SUFFIX})
|
||||
set(CMAKE_AS ${TOOLCHAIN_PREFIX}as${TOOLCHAIN_SUFFIX})
|
||||
set(CMAKE_LD ${TOOLCHAIN_PREFIX}ld${TOOLCHAIN_SUFFIX})
|
||||
set(CMAKE_AR "${TOOLCHAIN_PREFIX}ar${TOOLCHAIN_SUFFIX}")
|
||||
set(CMAKE_RANLIB "${TOOLCHAIN_PREFIX}ranlib${TOOLCHAIN_SUFFIX}")
|
||||
set(CMAKE_OBJCOPY "${TOOLCHAIN_PREFIX}objcopy${TOOLCHAIN_SUFFIX}")
|
||||
set(CMAKE_SIZE "${TOOLCHAIN_PREFIX}size${TOOLCHAIN_SUFFIX}")
|
||||
set(CMAKE_OBJDUMP "${TOOLCHAIN_PREFIX}objdump${TOOLCHAIN_SUFFIX}")
|
||||
set(CMAKE_AS "${TOOLCHAIN_PREFIX}as${TOOLCHAIN_SUFFIX}")
|
||||
set(CMAKE_LD "${TOOLCHAIN_PREFIX}ld${TOOLCHAIN_SUFFIX}")
|
||||
set(CMAKE_LINKER "${TOOLCHAIN_PREFIX}ld${TOOLCHAIN_SUFFIX}")
|
||||
|
||||
execute_process(
|
||||
COMMAND "${_ARM_GCC_EXECUTABLE}" --version
|
||||
OUTPUT_VARIABLE _ARM_GCC_VERSION_OUTPUT
|
||||
ERROR_VARIABLE _ARM_GCC_VERSION_ERROR
|
||||
RESULT_VARIABLE _ARM_GCC_VERSION_RESULT
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
if(NOT _ARM_GCC_VERSION_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Unable to execute ${_ARM_GCC_EXECUTABLE}: ${_ARM_GCC_VERSION_ERROR}")
|
||||
endif()
|
||||
string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" _ARM_GCC_VERSION "${_ARM_GCC_VERSION_OUTPUT}")
|
||||
message(STATUS "Using ARM GCC ${_ARM_GCC_VERSION} from ${TOOLCHAIN_DIRECTORY}")
|
||||
|
||||
set(CMAKE_EXECUTABLE_SUFFIX_ASM ".elf")
|
||||
set(CMAKE_EXECUTABLE_SUFFIX_C ".elf")
|
||||
set(CMAKE_EXECUTABLE_SUFFIX_CXX ".elf")
|
||||
|
||||
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
|
||||
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
|
||||
|
||||
+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:CXX>>:-g0>"
|
||||
|
||||
"$<$<AND:$<CONFIG:Debug>,$<COMPILE_LANGUAGE:C>>:-O0>"
|
||||
"$<$<AND:$<CONFIG:Debug>,$<COMPILE_LANGUAGE:CXX>>:-O0>"
|
||||
"$<$<AND:$<CONFIG:Debug>,$<COMPILE_LANGUAGE:C>>:-Og>"
|
||||
"$<$<AND:$<CONFIG:Debug>,$<COMPILE_LANGUAGE:CXX>>:-Og>"
|
||||
"$<$<AND:$<NOT:$<CONFIG:Debug>>,$<COMPILE_LANGUAGE:C>>:-Os>"
|
||||
"$<$<AND:$<NOT:$<CONFIG:Debug>>,$<COMPILE_LANGUAGE:CXX>>:-Os>"
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Project basic info
|
||||
set(PROJECT_NAME "GD32E23x_StdPeriph_Template")
|
||||
set(BOARD_TYPE_CODE 20)
|
||||
# 板卡类型码(协议帧中的标识):1-89 正式产品;90-99 预研专用,99=通用预研/试验
|
||||
set(BOARD_TYPE_CODE 99)
|
||||
set(VERSION_MAJOR 0)
|
||||
set(VERSION_MINOR 0)
|
||||
set(VERSION_PATCH 1)
|
||||
|
||||
Reference in New Issue
Block a user