Compare commits

...
7 Commits
Author SHA1 Message Date
hulk baa3e6310b chore(config): BOARD_TYPE_CODE 改为 99 标记预研工程 2026-09-14 17:18:09 +08:00
hulk 5723f521d7 feat(build): 工具链改用 GD32_GCC_BIN 环境变量方案并新增 .vscode 烧录调试配置 2026-09-14 17:17:57 +08:00
hulk ec412b1ffd feat(VL53L4CD): 支持透过玻璃串扰补偿、串口校准命令与校准数据Flash存储 2026-09-07 00:29:41 +08:00
hulk ef37553b18 feat(VL53L4CD): 增加偏移校准支持并固化44mm安装偏移
- 新增 vl53l4cd_set_offset/get_offset/calibrate_offset
- main 支持开机自动校准或启动时写回固定偏移
- 当前44mm哑光目标校准结果 offset=-9mm
- 同步更新 README 与 CHANGES
2026-08-07 00:16:50 +08:00
hulk 782fe2b2b0 feat(VL53L4CD): 添加VL53L4CD测距驱动、I2C 16位寄存器支持与项目文档 2026-08-06 00:52:32 +08:00
hulk 6eb9f304d3 添加传感器数据手册 2026-08-06 00:13:03 +08:00
hulk 12a1707132 修改GPIO定义 2026-08-06 00:06:36 +08:00
26 changed files with 2369 additions and 431 deletions
-1
View File
@@ -1,6 +1,5 @@
# 忽略构建输出目录
Build/
.vscode/
# 忽略 Toolchain 目录下所有内容,但保留目录本身
Toolchain/*
+2
View File
@@ -0,0 +1,2 @@
erase
exit
+8
View File
@@ -0,0 +1,8 @@
{
"recommendations": [
"marus25.cortex-debug",
"ms-vscode.cmake-tools",
"twxs.cmake",
"ms-vscode.cpptools"
]
}
+4
View File
@@ -0,0 +1,4 @@
loadfile Application.hex
r
go
exit
+58
View File
@@ -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"
]
}
]
}
+2
View File
@@ -0,0 +1,2 @@
r
exit
+15
View File
@@ -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"
}
}
+229
View File
@@ -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
View File
@@ -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. 体积与验证
- MCUGD32E230Flash 16KRAM 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
View File
@@ -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.
# =========================
# =========================
+222
View File
@@ -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..255ST 建议至少 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 */
+23 -8
View File
@@ -35,7 +35,7 @@
/* >>>>>>>>>>>>>>>>>>>>[SEGGER RTT DETECTION]<<<<<<<<<<<<<<<<<<<< */
#define SEGGER_RTT_DETECTION CFG_ENABLE // CFG_DISABLE: SEGGER RTT off; CFG_ENABLE: SEGGER RTT on
#define SEGGER_RTT_DETECTION CFG_DISABLE // CFG_DISABLE: SEGGER RTT off; CFG_ENABLE: SEGGER RTT on
#if SEGGER_RTT_DETECTION == CFG_ENABLE
#include "SEGGER_RTT.h"
@@ -48,9 +48,24 @@
#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_DISABLE // CFG_DISABLE: RS485 off; CFG_ENABLE: RS485 on
#define RS485_MODE CFG_ENABLE // CFG_DISABLE: RS485 off; CFG_ENABLE: RS485 on
#if RS485_MODE == CFG_ENABLE
#define RS485_DE_RCU RCU_GPIOA
@@ -77,13 +92,13 @@ void usart1_irq_handler(void);
/******************************************************************************/
#define RCU_GPIO_I2C RCU_GPIOF
#define RCU_GPIO_I2C RCU_GPIOA
#define RCU_I2C RCU_I2C0
#define I2C_SCL_PORT GPIOF
#define I2C_SCL_PIN GPIO_PIN_1
#define I2C_SDA_PORT GPIOF
#define I2C_SDA_PIN GPIO_PIN_0
#define I2C_GPIO_AF GPIO_AF_1
#define I2C_SCL_PORT GPIOA
#define I2C_SCL_PIN GPIO_PIN_9
#define I2C_SDA_PORT GPIOA
#define I2C_SDA_PIN GPIO_PIN_10
#define I2C_GPIO_AF GPIO_AF_4
/******************************************************************************/
+37
View File
@@ -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: 串扰值,单位 kcps0..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 */
+22
View File
@@ -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 开启上报并点亮 LEDfalse 关闭上报并熄灭 LED
* @ingroup Command
*/
void set_sensor_report_enabled(bool enabled);
/** @} */ // end of Command group
#endif // COMMAND_H
+4 -4
View File
@@ -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) */
/*!
+2 -1
View File
@@ -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
}
+307 -344
View File
@@ -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 工具链
- MCUGD32E230 系列Cortex-M23
- 系统时钟:72MHz
- Flash16KF4/ 32KF6/ 64KF8),上电自动识别
---
### VL53L4CD
## 默认配置
| 信号 | 引脚 | 说明 |
|---|---|---|
| SCL | PA9 | I2C0 时钟,AF4 |
| SDA | PA10 | I2C0 数据,AF4 |
| VDD | 3.3V | 模块供电 |
| GND | GND | 共地 |
- MCU 主频:内部 RC 振荡器,系统时钟配置为 72MHz
- 调试串口:USART0PA2 TX / PA3 RX),115200 波特率
- I2C:默认硬件 I2C0PF0 SDA / PF1 SCL),可通过 `board_config.h` 切换为软件 I2C
传感器 7 位 I2C 地址默认为 `0x29`
---
### 串口
## 快速开始
| 信号 | 引脚 | 说明 |
|---|---|---|
| TX | PA2 | RS485 发送 |
| RX | PA3 | RS485 接收 |
| DE | PA1 | RS485 方向控制,硬件自动控制 |
### 基于模板创建新项目
波特率 1152008N1。
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") # 编译变体
```
- LEDPB1
- SWDSWDIO、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` | 编译期固定串扰值,单位 kcps0..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: 软件 I2CGPIO 模拟)
```
| 选项 | 优点 | 缺点 |
|------|------|------|
| 硬件 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 支持
RTTReal-Time Transfer)是 SEGGER 的调试通道技术,通过 SWD 接口传输数据,不占用串口引脚,速度远超 UART。
```c
#define SEGGER_RTT_DETECTION ENABLE // DISABLE: 禁用 RTTENABLE: 启用 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 留给 BootloaderApp 从 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` 管理:
| 器件 | 校准页地址 |
|---|---|
| 16KF4 | `0x0800 3C00` |
| 32KF6 | `0x0800 7C00` |
| 64KF8 | `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)。
+508
View File
@@ -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, &reg_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 kcps65536/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, &reg_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);
}
+137
View File
@@ -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
View File
@@ -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 写入串扰补偿(kcps0..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 软件重启命令
+28 -8
View File
@@ -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
View File
@@ -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);
}
}
+90 -33
View File
@@ -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
View File
@@ -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>"
+2 -1
View File
@@ -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)
BIN
View File
Binary file not shown.