diff --git a/CHANGES.md b/CHANGES.md index 0ebf545..4ddcc90 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -99,6 +99,31 @@ Src/VL53L4CD_Driver.c ## 7. 当前已知限制 - 驱动只支持连续测距模式,`inter_measurement_ms` 非 0 会返回失败 -- 尚未实现距离阈值中断、Offset/Crosstalk 校准、自主低功耗模式、ULP 模式 +- 尚未实现 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` 中的固定值,避免每次开机都依赖目标存在。 diff --git a/Inc/VL53L4CD_Driver.h b/Inc/VL53L4CD_Driver.h index d2a4df2..eb69d52 100644 --- a/Inc/VL53L4CD_Driver.h +++ b/Inc/VL53L4CD_Driver.h @@ -17,6 +17,12 @@ /* 寄存器地址 */ #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 @@ -127,6 +133,30 @@ bool vl53l4cd_get_result(vl53l4cd_result_t *result); */ 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 读取当前生效的距离偏移量 + * @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 读一个 8 位寄存器 * @param[in] reg: 16 位寄存器地址 diff --git a/Inc/board_config.h b/Inc/board_config.h index e92aa03..46278f0 100644 --- a/Inc/board_config.h +++ b/Inc/board_config.h @@ -48,6 +48,13 @@ #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,可为负 + /* >>>>>>>>>>>>>>>>>>>>[RS485 MODE]<<<<<<<<<<<<<<<<<<<< */ #define RS485_MODE CFG_ENABLE // CFG_DISABLE: RS485 off; CFG_ENABLE: RS485 on diff --git a/README.md b/README.md index 573f740..165d153 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,10 @@ | `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` | 当前固定的距离偏移量 | | `RS485_MODE` | `CFG_ENABLE` | 使用 RS485 串口输出 | | `SOFTWARE_IIC` | `CFG_DISABLE` | 使用硬件 I2C | @@ -132,9 +136,29 @@ Distance: 830 mm | status: 0 | signal: 1128 kcps | ambient: 72 kcps | sigma: 3 m | `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_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`。 + +也可以直接调用 `vl53l4cd_calibrate_offset()`,该函数会先清零偏移、热机 10 帧,再统计有效测距样本并计算 `offset = 实际距离 - 平均距离`,最后写入 `RANGE_OFFSET_MM (0x001E)`。 + +注意:单点偏移只修正当前固定距离的绝对误差。近距离高反射金属目标存在非线性,建议使用哑光目标。 + ### 修改时序预算 在 `Src/VL53L4CD_Driver.c` 的 `vl53l4cd_init()` 末尾: @@ -205,7 +229,7 @@ bss 2012 ## 已知限制 - 驱动只支持连续测距模式 -- 尚未实现距离阈值中断、Offset/Crosstalk 校准、自主低功耗模式、ULP 模式 +- 尚未实现 Crosstalk 校准、距离阈值中断、自主低功耗模式、ULP 模式 - VL53L4CD 是单区传感器,不是多区测距 - `DEBUG_VERBOSE` 全开时 IIC 调试打印会让 Flash 非常紧张 diff --git a/Src/VL53L4CD_Driver.c b/Src/VL53L4CD_Driver.c index d1bc4af..a691847 100644 --- a/Src/VL53L4CD_Driver.c +++ b/Src/VL53L4CD_Driver.c @@ -94,6 +94,26 @@ 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] = { @@ -191,6 +211,95 @@ bool vl53l4cd_set_range_timing(uint8_t timing_budget_ms, uint32_t inter_measurem (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_init(void) { uint16_t model_id = 0U; diff --git a/Src/main.c b/Src/main.c index 9140353..e335158 100644 --- a/Src/main.c +++ b/Src/main.c @@ -78,6 +78,19 @@ int main(void) #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)) { + printf("VL53L4CD offset calibrated: %d mm\r\n", cal_offset); + } else { + printf("VL53L4CD offset calibration failed\r\n"); + } +#else + (void)vl53l4cd_set_offset(VL53L4CD_CALIB_OFFSET_MM); +#endif vl53l4cd_start_ranging(); printf("VL53L4CD ready\r\n"); } else {