From 95a46c130dbf254602b17a7eb2ad36fb1f19f370 Mon Sep 17 00:00:00 2001 From: spdis Date: Wed, 17 Jun 2026 13:47:13 +0800 Subject: [PATCH] =?UTF-8?q?C=E8=B7=AF:=20VL53L0X=20=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=80=81I2C=E7=9B=B4=E9=A9=B1=20+=20=E9=AB=98=E9=80=9F?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=2029.6fps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 搬运 ST API C 源码到 lib/vl53l0x/, 替换 I2C 层为用户态 i2c-dev - 新增 vl53l0x_platform_user.cpp: WriteMulti/ReadMulti 等全平台接口 - 重写 vl53l0x.cpp: start时unbind内核驱动, 单次测距, 无后台轮询 - 高速模式 20000μs timing budget, 模型缓存零污染(41-43ms) - LiDAR 每5帧一读(~20ms), 隔帧模型推理, fps 25.1→29.6 (+18%) - 同事的LiDAR避障集成: 12个配置参数, 中线变形绕行, 刹车注释 - nice -10 + sched_yield 优先级优化 --- AGENTS.md | 8 +- CMakeLists.txt | 13 +- ctl.sh | 13 + docs/ARCHITECTURE.md | 377 +- docs/LIDAR_AVOID.md | 343 ++ lib/global.h | 26 + lib/vl53l0x.h | 20 +- lib/vl53l0x/vl53l0x_api.c | 3141 +++++++++++++++++ lib/vl53l0x/vl53l0x_api.h | 1926 ++++++++++ lib/vl53l0x/vl53l0x_api_calibration.c | 1288 +++++++ lib/vl53l0x/vl53l0x_api_calibration.h | 85 + lib/vl53l0x/vl53l0x_api_core.c | 2128 +++++++++++ lib/vl53l0x/vl53l0x_api_core.h | 113 + lib/vl53l0x/vl53l0x_api_ranging.c | 42 + lib/vl53l0x/vl53l0x_api_ranging.h | 47 + lib/vl53l0x/vl53l0x_api_strings.h | 21 + lib/vl53l0x/vl53l0x_def.h | 663 ++++ lib/vl53l0x/vl53l0x_device.h | 262 ++ lib/vl53l0x/vl53l0x_i2c_platform.h | 402 +++ .../vl53l0x_interrupt_threshold_settings.h | 194 + lib/vl53l0x/vl53l0x_platform.h | 41 + lib/vl53l0x/vl53l0x_platform_log.h | 14 + lib/vl53l0x/vl53l0x_tuning.h | 146 + lib/vl53l0x/vl53l0x_types.h | 13 + lib/vl53l0x_direct.h | 23 + main/main.cpp | 14 + mild_v12.bin | Bin 105014 -> 105014 bytes src/camera.cpp | 141 + src/global.cpp | 13 + src/vl53l0x.cpp | 160 +- src/vl53l0x_platform_user.cpp | 159 + 31 files changed, 11645 insertions(+), 191 deletions(-) create mode 100644 docs/LIDAR_AVOID.md create mode 100644 lib/vl53l0x/vl53l0x_api.c create mode 100644 lib/vl53l0x/vl53l0x_api.h create mode 100644 lib/vl53l0x/vl53l0x_api_calibration.c create mode 100644 lib/vl53l0x/vl53l0x_api_calibration.h create mode 100644 lib/vl53l0x/vl53l0x_api_core.c create mode 100644 lib/vl53l0x/vl53l0x_api_core.h create mode 100644 lib/vl53l0x/vl53l0x_api_ranging.c create mode 100644 lib/vl53l0x/vl53l0x_api_ranging.h create mode 100644 lib/vl53l0x/vl53l0x_api_strings.h create mode 100644 lib/vl53l0x/vl53l0x_def.h create mode 100644 lib/vl53l0x/vl53l0x_device.h create mode 100644 lib/vl53l0x/vl53l0x_i2c_platform.h create mode 100644 lib/vl53l0x/vl53l0x_interrupt_threshold_settings.h create mode 100644 lib/vl53l0x/vl53l0x_platform.h create mode 100644 lib/vl53l0x/vl53l0x_platform_log.h create mode 100644 lib/vl53l0x/vl53l0x_tuning.h create mode 100644 lib/vl53l0x/vl53l0x_types.h create mode 100644 lib/vl53l0x_direct.h create mode 100644 src/vl53l0x_platform_user.cpp diff --git a/AGENTS.md b/AGENTS.md index 7175c2f..d61aa1e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,6 +38,8 @@ These source files exist but are **excluded from build**: - `vl53l0x.cpp` — laser ranging module, hardware not connected - `zebra_detect.cpp` — classic zebra-crossing detection (replaced by Mild model) +- `PIDController.cpp` — unused (motor is open-loop direct drive) +- `serial.cpp` — Vofa/serial image streaming, unused ## Device-side operation @@ -52,7 +54,7 @@ sh ctl.sh stop # stop and zero PWM `ctl.sh` sets default config values by writing to files like `./speed`, `./kp`, `./steer_gain`, etc. The running binary reads these files at startup and optionally re-reads them every 7 frames (when `./debug` = 1). -`start.sh` is an alternative launcher using `LD_PRELOAD=./gpio_fix_final.so` for the GPIO 74 workaround. +`start.sh` is an alternative launcher using `LD_PRELOAD=./gpio_fix_final.so` for the GPIO 74 workaround. **Note**: `start.sh` references the binary as `smartcar_demo` but the actual executable is `smartcar_demo1` — this is a known bug, use `ctl.sh` instead. ## Configuration system @@ -94,7 +96,7 @@ All config is via single-value text files in the working directory: ## Architecture -### Per-frame pipeline in `CameraHandler()` ([`src/camera.cpp`](file:///D:\PPPProgram\smartcar\smartcar2\src\camera.cpp)) +### Per-frame pipeline in `CameraHandler()` (`src/camera.cpp:578`) CameraHandler 现已拆分为多个函数,主函数 ~40 行仅做编排: @@ -117,7 +119,7 @@ CameraHandler() - **Open-loop**: `MotorController::updateduty(spd)` — direct PWM duty cycle, no encoder feedback in normal driving. - **Encoder brake**: When `zebra_block=true` (red light or zebra stop), reads `g_enc_speed` from the encoder thread and applies reverse braking proportional to current speed. -- **Encoder thread** ([`control.cpp:32`](file:///D:\PPPProgram\smartcar\smartcar2\src\control.cpp#L32)): polls GPIO67 (LSB pulse) + GPIO72 (direction) at ~2kHz, 100ms window speed calculation → `g_enc_speed` (pulses/sec). Now includes 500µs sleep to prevent CPU starvation. +- **Encoder thread** (`control.cpp:32`): polls GPIO67 (LSB pulse) + GPIO72 (direction) at ~2kHz, 100ms window speed calculation → `g_enc_speed` (pulses/sec). Now includes 500µs sleep to prevent CPU starvation. - **Curve slowdown**: `speed *= (1.0 - |deviation| × 0.4)`, clamped to ≥ 60%. ### Model: Mild v12 diff --git a/CMakeLists.txt b/CMakeLists.txt index ac307f2..ca73f0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ message(STATUS "OpenCV Include Directories: ${OpenCV_INCLUDE_DIRS}") # 项目头文件路径 include_directories(src) include_directories(lib) +include_directories(lib/vl53l0x) # 收集 src 下所有 .cpp (自动发现) aux_source_directory(src DIR_SRCS) @@ -30,8 +31,16 @@ aux_source_directory(src DIR_SRCS) # zebra_detect.cpp — 经典斑马线检测, 已被 Mild 模型替代 # PIDController.cpp — PID 类未使用 (电机开环直驱) # serial.cpp — Vofa/串口图传未使用 -list(FILTER DIR_SRCS EXCLUDE REGEX "(vl53l0x\\.cpp|zebra_detect\\.cpp|PIDController\\.cpp|serial\\.cpp)") +list(FILTER DIR_SRCS EXCLUDE REGEX "(zebra_detect\\.cpp|PIDController\\.cpp|serial\\.cpp)") + +# VL53L0X ST API 源码 (纯 C) +set(VL53L0X_API_SRCS + lib/vl53l0x/vl53l0x_api.c + lib/vl53l0x/vl53l0x_api_core.c + lib/vl53l0x/vl53l0x_api_calibration.c + lib/vl53l0x/vl53l0x_api_ranging.c +) # 静态库 + 主程序 -add_library(common_lib STATIC ${DIR_SRCS}) +add_library(common_lib STATIC ${DIR_SRCS} ${VL53L0X_API_SRCS}) add_subdirectory(main) diff --git a/ctl.sh b/ctl.sh index ae2420f..f1e08c0 100644 --- a/ctl.sh +++ b/ctl.sh @@ -49,6 +49,19 @@ init_pins() { echo 10 > "$DIR/brake_scale" 2>/dev/null echo 10000 > "$DIR/brake_max" 2>/dev/null + echo 300 > "$DIR/lidar_thresh" 2>/dev/null + echo 50 > "$DIR/lidar_near" 2>/dev/null + echo 1200 > "$DIR/lidar_far" 2>/dev/null + echo 1 > "$DIR/lidar_near_start" 2>/dev/null + echo 4 > "$DIR/lidar_near_end" 2>/dev/null + echo 6 > "$DIR/lidar_far_span" 2>/dev/null + echo 3 > "$DIR/lidar_min_frames" 2>/dev/null + echo 0.4 > "$DIR/lidar_avoid_gain" 2>/dev/null + echo 30 > "$DIR/lidar_avoid_range" 2>/dev/null + echo 0.4 > "$DIR/lidar_speed" 2>/dev/null + echo 30 > "$DIR/lidar_hold_frames" 2>/dev/null + echo 1 > "$DIR/lidar_enable" 2>/dev/null + echo "[demo] 引脚初始化完成" } diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 156ee7c..5b5bf62 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -3,119 +3,98 @@ ## 坐标系统一览 ``` - Camera 输出 640×480 MJPEG - │ - CONVERT_RGB=0 + IMREAD_REDUCED_COLOR_4 - → 1/4 MJPEG 解码 → raw_frame = 160×120 - │ - ┌───────────┤ - ▼ ▼ (raw_frame.cols/rows 决定分母) - 巡线空间 显示空间 模型空间 - (line_tracking (newWidth × newHeight) 160×120 (正常) - _width × calc_scale = 2 640×480 (回退模式) - line_tracking newWidth = lt_w × 2 - _height) newHeight = lt_h × 2 - 典型值 80×60 + Camera 输出 640×480 MJPEG + │ + CONVERT_RGB=0 + IMREAD_REDUCED_COLOR_4 + → 1/4 MJPEG 解码 → raw_frame = 160×120 + │ + ┌───────────┤ + ▼ ▼ (raw_frame.cols/rows 决定分母) + 巡线空间 显示空间 模型空间 + (line_tracking (newWidth × newHeight) 160×120 (正常) + _width × calc_scale = 2 640×480 (回退模式) + line_tracking newWidth = lt_w × 2 + _height) newHeight = lt_h × 2 + 典型值 80×60 ``` **关键换算关系(正常模式 raw_frame = 160×120):** - 模型空间 → 巡线空间:`col_lt = cx * line_tracking_width / 160`,`row_lt = cy * line_tracking_height / 120` - 巡线空间 → 显示空间:`pixel = value * calc_scale`(calc_scale = 2) -- newWidth / newHeight 取决于屏幕物理分辨率,由 `CameraInit` 动态计算 -- line_tracking_width / height = newWidth/calc_scale, newHeight/calc_scale +- `line_tracking_width / height` = `newWidth / calc_scale`, `newHeight / calc_scale` +- newWidth / newHeight 由 `CameraInit` 读取 `/dev/fb0` 屏幕分辨率后动态计算 **⚠ 回退模式:** 当 CONVERT_RGB=0 未生效时,raw_frame = 640×480,模型框坐标也在 640×480 空间。 -此时 LCD 渲染仍硬编码除以 160/120(会出错),但正常运行时不会进入此模式。 +此时 LCD 渲染的硬编码缩放 `newWidth/160.0f` 会出错,但正常运行时不会进入此模式。 --- -## 主循环 (main.cpp → CameraHandler) +## 主循环 (`main.cpp`) ``` main() - ├── cfg_load_all() # 从工作目录文件读取全部配置 - ├── CameraInit(0, dest_fps, 320, 240) # 打开摄像头, LCD, 模型, I2C - ├── ControlInit() # 初始化双电机 GPIO + PWM + ├── cfg_load_all() # 从工作目录文件读取全部配置 + ├── 补读 cone_avoid_gain/range/hold_frames + ├── CameraInit(0) # 打开摄像头, LCD, 模型, I2C, 计算巡线尺寸 + ├── ControlInit() # 初始化双电机 GPIO + PWM + 启动编码器线程 │ └── while(running): - └── CameraHandler() # ★ 以下逐帧执行 + ├── CameraHandler() # ★ 逐帧执行 + └── target_speed = g_cfg.speed # (debug 模式下每 7 帧重载) +``` + +`CameraInit` 现在只接受 `camera_id` 一个参数(之前有 dest_fps, width, height 三个遗留参数已移除)。 +巡线分辨率由屏幕自适应算法决定:`line_tracking_w = newWidth/2`, `line_tracking_h = newHeight/2`。 + +--- + +## CameraHandler 11 步流水线 + +``` +Step 1 capture_frame() 640×480 MJPEG → IMREAD_REDUCED_COLOR_4 + 取帧+解码 → raw_frame = 160×120 BGR + (回退: raw_mat 三通道 → 直用 640×480) + +Step 2 save_image_if_requested() debug=1 && saveImg 文件=1 → 存 ./image/XXXXX.jpg + 保存图像 (条件) + +Step 3 image_main() raw_frame → resize(lt_w×lt_h) → HSV 双通道Otsu + 视觉巡线 → floodFill(种子 (lt_w/2, lt_h-10), 半径5) + → 逐行最长连续段搜索 → 丢线补全(row 10~59) + 输出: left_line[], right_line[], mid_line[] + +Step 4 run_model_inference() 每 2 帧推理一次 + 模型推理 raw_frame (160×120) → model_v10_detect() + 4 类: 0=锥桶 1=红灯 2=绿灯 3=斑马线 + g_thresh = {0.90, 0.75, 0.80, 0.90} + → g_boxes[16], g_box_count + +Step 5a zebra_process() 去抖计数 + ZNORMAL→ZSTOP(4s)→ZCOOLDOWN(5s) + 斑马线状态机 触发: 连续5帧 + 起源于远(cy≤50) + 当前近(cy>zebrasee) + 刹停时 I2C 语音播报 + +Step 5b traffic_light_process() TLNORMAL→TLSTOP→TLWAIT_GREEN + 红绿灯状态机 红灯≥3帧 → 停车; 红灯消失 → 等绿灯; 绿灯≥3帧 → 通行 + +Step 5c cone_detect_and_deform() 去抖确认 + 中线变形 + 消失后保持衰减 + 锥桶检测 & 中线变形 cone_speed 减速 + cone_hold_frames 保持 + +Step 6 steering_update() foresee/2 → check_row → mid_line[row]×2 - newWidth/2 + 舵机控制 deadband 过滤 → servo duty: 1500000 ± offset ns + +Step 7 motor_update() 开环PWM + 编码器比例刹车 + 弯道减速 + 锥桶减速 + 电机控制 + +Step 8 lcd_render() track→BGR→ROI + 边界线(红/绿/蓝) + 检测框 + 状态指示 + LCD 渲染 RGB565 → /dev/fb0 mmap + +Step 9 fps_log() 每 15 帧输出总帧率 + 分步耗时 ms + FPS 统计 ``` --- -## CameraHandler 9 步流水线 - -``` - ┌─────────────────────┐ -Step 1 │ cap.read(raw_mat) │ 640×480 MJPEG 原始字节流 - 取帧+解码 │ raw_mat 单通道(字节) │ → IMREAD_REDUCED_COLOR_4 - │ → cv::imdecode │ → raw_frame = 160×120 BGR - │ raw_frame = decoded │ (回退: raw_mat 三通道→直用 640×480) - └────────┬────────────┘ - │ -Step 2 ┌────────▼────────────┐ - 保存图像 (条件) │ g_cfg.debug==1 │ 写入 ./image/image_XXXXX.jpg - │ && saveImg==1 → │ (双重条件,缺一不可) - │ saveCameraImage() │ - └────────┬────────────┘ - │ -Step 3 ┌────────▼────────────┐ - 视觉巡线 │ image_main() │ raw_frame → resize(lt_w×lt_h) - (每帧都跑) │ │ → HSV双通道Otsu → floodFill - │ 输出: left_line[] │ → 逐行最长连续段搜索 - │ right_line[] │ → 丢线补全(仅 row 10~59) - │ mid_line[] │ → track (赛道蒙版 128/0) - └────────┬────────────┘ - │ -Step 4 ┌────────▼────────────┐ - 模型推理 │ model_v10_detect() │ 每 2 帧推理一次 - (每 2 帧) │ raw_frame (160×120) │ 4 类: 0=锥桶 1=红灯 2=绿灯 3=斑马线 - │ → g_boxes[16] │ g_thresh = [0.80,0.80,0.80,0.75] - │ → g_box_count │ 框坐标 ∈ raw_frame 空间 - └────────┬────────────┘ - │ -Step 5 ┌────────▼────────────┐ - 斑马线状态机 │ 遍历 g_boxes │ 仅处理 cls=3,取第一个斑马线框 - │ 去抖计数+远近判断 │ NORMAL→STOP(4s)→COOLDOWN(5s) - │ │ I2C 语音播报 @ 0x34 - └────────┬────────────┘ - │ -Step 6 ┌────────▼────────────┐ - 舵机控制 │ if g_cfg.start: │ - │ foresee→check_row │ 偏差 = mid_line[row]×2 - newWidth/2 - │ mid_line[check_row]│ → g_steer_deviation ∈ [-1, 1] - │ → deviation │ → servo duty: 1500000 ± offset - │ → deadband 过滤 │ clamp [1.2M, 1.8M] ns - │ → servo.setDuty()│ mid_val==255 → 跳过(无效行) - └────────┬────────────┘ - │ -Step 7 ┌────────▼────────────┐ - 电机控制 │ ControlUpdate( │ - (开环) │ target_speed, │ if zebra_STOP: duty=0 (刹停)+GPIO73=0 - │ g_zstate==Z_STOP) │ else: speed × curve - │ │ curve = 1 - |deviation|×0.4 - │ │ curve ∈ [0.6, 1.0] - │ │ 左右电机同速(无差速) - └────────┬────────────┘ - │ -Step 8 ┌────────▼────────────┐ - LCD 渲染 │ if g_lcd_on: │ g_lcd_on = g_cfg.showImg 的缓存 - │ track→BGR→ROI │ (每10帧刷新一次缓存) - │ + 边界线(红) │ 检测框坐标 bx=newWidth/160 缩放 - │ + 中线(蓝) │ 状态指示 N(绿)/S(红)/C(黄) - │ + 检测框标注 │ → RGB565 → /dev/fb0 mmap - │ (L1条状:跳过红灯) │ - └────────┬────────────┘ - │ -Step 9 ┌────────▼────────────┐ - FPS 统计 │ 每 15 帧输出 │ stdout: "fps=XX.X|rd=X vi=X md=X ct=X ms" - │ 分步计时+总帧率 │ 15帧平均 - └─────────────────────┘ -``` - ---- - -## 视觉巡线深度展开 (image_main) +## 视觉巡线深度展开 (`image_main`) ``` raw_frame (160×120 或 640×480 BGR) @@ -130,7 +109,7 @@ raw_frame (160×120 或 640×480 BGR) │ ├── find_road(): │ MORPH_OPEN(2×2 CROSS) → 种子点(lt_w/2, lt_h-10) - │ → 种子点处画实心圆(半径10, 255) 防种子落在黑色区域 + │ → 种子点处画实心圆(半径5, 255) 防种子落在黑色区域 │ → floodFill(loDiff=20, upDiff=20, 8邻域, newVal=128) │ → mask 提取 ROI → track (赛道内部=128, 外部=0) │ @@ -141,28 +120,29 @@ raw_frame (160×120 或 640×480 BGR) │ → left_line[row], right_line[row] │ → 全零行: left=right=-1 │ - └── 中线 + 丢线补全 (row = lt_h-1 → 10): + └── 中线 + 丢线补全 (row = lt_h-2 → 10): 有边界: mid = (left+right)/2, int 整除 丢线: mid[row] = mid[row+1] (继承下行) 若 mid > lt_w/2 → left=mid, right=lt_w-1 (偏右) 若 mid ≤ lt_w/2 → left=0, right=mid (偏左) + 底行兜底: 丢线时用 lt_w/2 ``` -**边界线区间有效性:** row 10~59 有可靠数据;row 0~9 的 mid_line 保持初始值 -1(不可靠,不要写入 box 过滤逻辑)。 +**边界线区间有效性:** row 10~59 有数据;row 0~9 的 mid_line 保持初始值 -1(不可靠)。 --- ## 舵机控制数学 ``` -foresee = g_cfg.foresee ← 前瞻行索引(像素,显示空间),默认 40 -check_row = foresee / calc_scale ← 转为巡线空间行号(calc_scale=2, int 整除) -mid_val = mid_line[check_row] ← 该行中线列坐标 (0~79) +foresee = g_cfg.foresee ← 前瞻行索引(显示空间像素),默认 40 +check_row = (int)foresee / calc_scale ← 转为巡线空间行号(calc_scale=2, int 整除) +mid_val = mid_line[check_row] ← 该行中线列坐标 (0~79) -如果 mid_val == 255 → 跳过(丢线补全也写不到 255,仅作防御) +如果 mid_val == -1 → 跳过(丢线行无效) 否则: deviation = mid_val × 2 - newWidth / 2 ← 转为显示空间偏差(px) - deviation -= g_cfg.center_bias ← 中心偏置修正 + deviation -= g_cfg.center_bias ← 中心偏置修正 g_steer_deviation = deviation / (newWidth/2) ← 归一化到 [-1, 1] if |deviation| < deadband: @@ -179,19 +159,32 @@ mid_val = mid_line[check_row] ← 该行中线列坐标 (0~79) --- -## 电机控制数学 +## 电机控制数学 (`ControlUpdate`) ``` -ControlUpdate(speed, zebra_block): +ControlUpdate(speed, zebra_or_tl_block): - if zebra_block || !g_cfg.start: + if !g_cfg.start: motor[0].updateduty(0) → 两电机停转 motor[1].updateduty(0) - if !g_cfg.start: mortorEN.setValue(0) → GPIO73 拉低 + mortorEN.setValue(0) → GPIO73 拉低 return - curve = 1.0 - |g_steer_deviation| × 0.4 - curve = max(curve, 0.6) ← 最低不低于 60% 速度 + if zebra_or_tl_block: ← 斑马线或红灯刹停 + 读取 g_enc_speed (编码器线程, 脉冲/秒) + 读取 g_enc_dir (编码器方向) + if cur_speed > 0.5: + brake_ns = cur_speed × g_cfg.brake_scale ← 比例刹车 + brake_ns = clamp(brake_ns, 0, g_cfg.brake_max) + brake_pct = brake_ns / 500 ← ns → % (period=50000ns) + dir = cur_dir ? 1 : 0 + motor[i]->updateduty(dir ? -brake_pct : brake_pct) ← 反转刹车 + else: + motor[i]->updateduty(0) ← 已静止,不刹车 + return + + curve = 1.0 - |g_steer_deviation| × 0.4 ← 弯道减速 + curve = max(curve, 0.6) ← 最低 ≥ 60% spd = speed × curve motor[0].updateduty(spd) → 左电机 (pwmchip8/pwm2, gpio12 方向) @@ -199,22 +192,31 @@ ControlUpdate(speed, zebra_block): mortorEN.setValue(1) → GPIO73 拉高使能 ``` +**编码器刹车(encoder brake):** 当斑马线或红灯触发停车时,根据实时编码器速度计算反向刹车占空比, +刹车力度与当前车速成正比(`brake_scale`),有上限(`brake_max` ns)。 +速度低于 0.5 pps 时不再施加刹车。 + +**motor_update 额外逻辑:** `motor_update()` 在调用 `ControlUpdate` 之前,若锥桶已触发(`cone_is_slow()`), +会将 speed 乘以 `g_cfg.cone_speed`(默认 0.5),实现锥桶路段减速。 + **motor[0] vs motor[1]:** 左/右区别仅在于 gpioNum(12 vs 13)和 pwm通道(2 vs 1),函数调用完全相同。 **updateduty(duty) 内部:** - `pwm_period * |duty| / 100` → 转占空比 ns 写入 sysfs - duty > 0 → GPIO 方向 = 1(正转),duty ≤ 0 → GPIO 方向 = 0(反转) -**速度控制是开环的:** `MotorController` 仅包含 `updateduty()` 方法,无编码器反馈。 +**速度控制是开环的:** `MotorController` 仅包含 `updateduty()` 方法,正常行驶无编码器反馈。 `PIDController` 类存在但从未被实例化或调用。 -MotorController.h 中也无 `updateSpeed()` 方法(之前的文档中误记了此函数)。 + +**编码器线程** (`ControlInit` → `encoder_thread`):独立线程高频轮询 GPIO67(LSB脉冲)+ GPIO72(方向), +每 100ms 计算一次速度 → `g_enc_speed`(脉冲/秒),含 500µs sleep 防止 CPU 饿死。 --- ## 斑马线状态机 ``` -检测到 cls=3 → 取第一个斑马线框 → 去抖计数+追踪最远cy +检测到 cls=3 → 取第一个斑马线框(之后 break)→ 去抖计数+追踪最远cy ZNORMAL 期间: zebra_seen: g_zc_frames++, g_zc_min_cy = min(g_zc_min_cy, zebra_cy) @@ -231,14 +233,14 @@ ZNORMAL 期间: │NORMAL│ ─────────────────────────────────────► ┌──────┐ │ │ ◄───────────────────────────────────── │ STOP │ └──────┘ 冷却5秒结束 │ │ - ▲ └──┬───┘ - │ 4秒后 │ - │ ┌──────────┐ ◄─────────────────────┘ - └───────────│ COOLDOWN │ - └──────────┘ + ▲ └──┬───┘ + │ 4秒后 │ + │ ┌──────────┐ ◄─────────────────────┘ + └──────────│ COOLDOWN │ + └──────────┘ NORMAL: 允许通行,检测斑马线 -STOP: 刹停 4 秒,g_zstate==Z_STOP 传给 ControlUpdate 第二个参数 +STOP: 刹停 4 秒,g_zstate==Z_STOP 传给 motor_update → 编码器刹车 COOLDOWN: 恢复行驶但斑马线状态机停止检测 5 秒(防止重复触发) 仅跳过检测,不影响其他功能(舵机/巡线正常) ``` @@ -247,14 +249,99 @@ COOLDOWN: 恢复行驶但斑马线状态机停止检测 5 秒(防止重复触 - `g_zc_min_cy`:NORMAL 期间追踪斑马线出现时的最小 cy(越远值越小)。未检测到斑马线时衰减减2/帧,归零后重置为 120。 - `ZEBRA_FAR_CY = 50`:斑马线的 cy 必须曾在 ≤50 处出现过(即"从远处来") - `zebrasee`(默认 60):当前斑马线 cy > 此值视为"足够近",触发停车 -- 去抖衰减速度:未检测到时每次 `-2`(比锥桶设计中的 `-1` 更快下降) +- 去抖衰减速度:未检测到时每次 `-2`(比锥桶设计的 `-1` 更快下降) - Box 遍历在找到第一个 cls=3 后 `break`,忽略同帧其他斑马线框 --- +## 红绿灯状态机 + +``` +检测到 cls=1 (红灯) / cls=2 (绿灯) → 去抖计数 → 状态转移 + + ┌──────┐ 红灯≥3帧 ┌──────┐ 红灯消失 ┌───────────┐ + │NORMAL│ ───────────► │ STOP │ ──────────► │WAIT_GREEN │ + │ │ ◄─────────── │ │ │ │ + └──────┘ 绿灯≥3帧 └──────┘ └─────┬─────┘ + ▲ │ + └───────────────────────────────────────────┘ + +TL_NORMAL: 允许通行,检测红绿灯 +TL_STOP: 红灯→停车(编码器刹车),等红灯消失 +TL_WAIT_GREEN: 红灯已消失,等待绿灯出现(≥3帧)→ 恢复通行 +``` + +**返回值:** `traffic_light_process()` 返回 `true` 当 `g_tl_state != TL_NORMAL`, +传递给 `motor_update` → `ControlUpdate` 触发编码器刹车。 + +--- + +## 锥桶检测 & 中线变形 + +``` +cone_detect_and_deform(): + (仅在 Z_NORMAL && TL_NORMAL 时运行) + + ┌─ 寻找最近锥桶 ─┐ + │ 遍历 g_boxes, cls=0, conf >= cone_thresh + │ 模型空间坐标 → 巡线空间坐标 + │ 过滤: rl < 10 或 无边界线的行 → 跳过 + │ cone_margin==0 → 中心点; ==1 → 框边缘 (左右均在边界线内) + │ 取 cy 最大(最近)的锥桶 + │ + ├─ 去抖确认 ────────────────────────────── + │ 位置容忍: row_tol = max(2, lt_h/12), col_tol = max(3, lt_w/8) + │ 连续出现且位置接近 → g_cone_frames++ + │ 丢失 → g_cone_frames = max(0, g_cone_frames - 1) + │ g_cone_confirmed = (g_cone_frames >= cone_min_frames) + │ + ├─ 保持/衰减 ──────────────────────────── + │ 确认时: g_cone_hold_ctr=0, 记录锥桶位置 + │ 未确认但有历史: g_cone_hold_ctr++, 衰减推离量 + │ 超过 cone_hold_frames → 停止影响 + │ + └─ 中线变形 ───────────────────────────── + dir = (锥桶偏左) ? +1.0 : -1.0 ← 向远离锥桶方向推 + decay = (确认) 1.0 : (1 - hold_ctr/hold_frames) + for row = 10 → src_row: + t = clamp((row-10) / cone_avoid_range, 0, 1) ← 斜坡上升 + push = t × cone_avoid_gain × half_w × dir × decay + mid_line[row] = clamp(mid_line[row] + push, + left_line[row]+2, right_line[row]-2) +``` + +**速度联动:** `cone_is_slow()` 在锥桶确认或保持期间返回 true, +`motor_update` 将当前速度乘以 `cone_speed`(默认 0.5)。 + +--- + +## LCD 渲染 + +``` +lcd_render(): + g_lcd_on 每 10 帧从 g_cfg.showImg 刷新 + + track (128/0 灰度) → resize(newWidth×newHeight) → GRAY→BGR + → 居中拷贝到 lcd_fbImage (screenHeight × screenWidth) 的 ROI 区域 + + 边界线绘制: 红=左边界, 绿=右边界, 蓝=中线 + (跳过 mid_line[row]==-1 的行) + + 检测框绘制: 遍历 g_boxes, bx=newWidth/raw_frame.cols + 跳过 cls=1(红灯) 和 cls=2(绿灯) 的框 + cls=0 锥桶 → 橙色框, cls=3 斑马线 → 紫色框 + 标注框类别+置信度 + + 状态指示 (左下角): N=正常, S=斑马线停车, C=斑马线冷却, + R=红灯停车, G=等绿灯 + → convertMatToRGB565 → /dev/fb0 mmap +``` + +--- + ## 配置系统 -所有配置通过工作目录下的纯文本文件读写。文件不存在时 readDoubleFromFile 返回 0: +所有配置通过工作目录下的纯文本文件读写。文件不存在时 `readDoubleFromFile` 返回 0: | 文件 | 类型 | 代码默认 | ctl.sh 写入 | 含义 | |---|---|---|---|---| @@ -271,32 +358,54 @@ COOLDOWN: 恢复行驶但斑马线状态机停止检测 5 秒(防止重复触 | `./zebrasee` | double | 60 | (不写入) | 斑马线近界阈值 (模型空间cy) | | `./destfps` | double | 30* | (不写入) | 目标帧率 (*代码兜底值) | | `./saveImg` | int(0/1) | - | (不写入) | 保存帧图像 (需 debug=1) | +| `./cone_avoid_gain` | double | 0.3 | 0.3 | 锥桶中线变形推离量 (归一化) | +| `./cone_avoid_range` | int | 30 | 30 | 变形斜坡陡峭度 | +| `./cone_speed` | double | 0.5 | 0.5 | 锥桶触发时速度倍率 | +| `./cone_min_frames` | int | 3 | 3 | 锥桶连续确认帧数 | +| `./cone_margin` | int | 0 | 0 | 0=中心点, 1=框边缘检查 | +| `./cone_thresh` | double | 0.80 | 0.80 | 锥桶置信度阈值 | +| `./cone_hold_frames` | int | 30 | 30 | 锥桶消失后保持变形帧数 | +| `./brake_scale` | double | 10 | 10 | 刹车速度→占空比系数 | +| `./brake_max` | int | 10000 | 10000 | 最大刹车占空比 (ns) | **debug 模式:** `./debug` = 1 时,主循环每7帧调用一次 `cfg_load_all()` 重读全部配置,支持热更新调参。同时允许 `saveImg` 保存图像。 -**注意**:`global.h` 中的 `CfgCache` 默认值和 `ctl.sh` 写入的值不一致(如 speed: 60 vs 11)。`ctl.sh` 写入的值覆盖代码默认值,是实际运行参数。 +**注意:** `global.h` 中的 `CfgCache` 默认值和 `ctl.sh` 写入的值不一致(如 speed: 60 vs 11)。`ctl.sh` 写入的值覆盖代码默认值,是实际运行参数。 --- ## 代码中未使用的模块 -以下 `.cpp` 文件已编译进 `common_lib` 但主循环中从未调用: +以下 `.cpp` 文件存在于 `src/` 中但已被 `CMakeLists.txt` 的 `list(FILTER ... EXCLUDE)` 排除编译: -| 文件 | 功能 | 状态 | +| 文件 | 功能 | 排除原因 | |---|---|---| -| `PIDController.cpp` | 位置式/增量式 PID | 类存在但无实例化,无调用 | -| `serial.cpp` | VOFA 串口可视化 (vofa_justfloat/vofa_image) | 已实现但无调用 | -| `Timer.cpp` | 定时器线程 | 已实现但无调用(Video 依赖它但 Video 也未用) | -| `video.cpp` | 视频文件流读取 | 已实现但无调用 | +| `vl53l0x.cpp` | 激光测距模块 | 硬件未连接 | +| `zebra_detect.cpp` | 经典斑马线检测 | 已被 Mild 模型替代 | +| `PIDController.cpp` | 位置式/增量式 PID | 电机开环直驱,无调用点 | +| `serial.cpp` | VOFA 串口可视化 | 未使用 | + +**注意:** `PIDController.h` 仍在 `lib/` 中,`MotorController` 仅提供 `updateduty()`(开环占空比), +不提供 `updateSpeed()` 方法。 --- -## 当前已知缺陷 / 未利用能力 +## 模型:Mild v12 -1. **cls=0 锥桶** — 模型已检测但被忽略(CONE_DESIGN.md 设计了方案) -2. **cls=1 红灯 / cls=2 绿灯** — 检测框跳过不画(LCD 渲染中 `if g_boxes[i].cls == 1 || g_boxes[i].cls == 2: continue`),无决策 -3. **电机开环** — 无编码器反馈,PID 类已实现但无调用点,`MotorController` 无 `updateSpeed()` 方法 -4. **舵机死区** — 用 `abs(deviation) < deadband` 比较像素值,deadband 单位是显示空间像素 -5. **中线 row 范围** — 丢线补全仅覆盖 row 10~59,row 0~9 的 mid_line 保持 -1 不可靠 -6. **mid_val == 255 防御** — 代码中写死但 255 永远不会出现在 mid_line 中(当前实现下) -7. **LCD 坐标缩放硬编码** — 检测框绘制用 `newWidth/160.0f`,假设 raw_frame 宽=160。回退模式下 raw_frame=640 时出错 +- **推理引擎**:`src/model_v10.{hpp,cpp}`(名称为兼容保留,内部为 Mild v3) +- **输入**:160×120 BGR +- **输出**:4 类 + 背景 → 9 通道:0=锥桶, 1=红灯, 2=绿灯, 3=斑马线 +- **架构**:3→9→9→13→13→19→19→19→44→44→64→64 → head(64→64,dw) → out(64→9) +- **阈值**:`{0.90, 0.75, 0.80, 0.90}`(锥桶/红灯/绿灯/斑马线) +- **权重文件**:`mild_v12.bin`(105KB 自定义二进制格式) +- **推理频率**:每 2 帧一次(跳帧节省 CPU) + +--- + +## 已知局限 + +1. **电机开环** — 正常行驶无编码器反馈,仅制动时使用编码器速度做比例刹车。PID 类已实现但无调用点。 +2. **舵机死区** — 用 `abs(deviation) < deadband` 比较像素值,deadband 单位是显示空间像素。 +3. **中线 row 范围** — 丢线补全仅覆盖 row 10~59,row 0~9 的 mid_line 保持 -1 不可靠。 +4. **LCD 坐标缩放硬编码** — 检测框绘制用 `newWidth/160.0f`,假设 raw_frame 宽=160。回退模式(raw_frame=640)下会出错。 +5. **锥桶变形可能跳过 row 0~9** — 变形循环从 row=10 开始,row 0~9 不会改变。 diff --git a/docs/LIDAR_AVOID.md b/docs/LIDAR_AVOID.md new file mode 100644 index 0000000..81e7d62 --- /dev/null +++ b/docs/LIDAR_AVOID.md @@ -0,0 +1,343 @@ +# 激光雷达挡板避障设计 + +## 问题 + +VL53L0X 是单点 ToF 激光测距传感器,仅返回沿光束方向的一个距离值(mm),没有扇形扫描能力。 +当激光测到近处有物体时,无法直接从距离值判断它是: + +- A) **挡板/障碍物** — 横在赛道上的物体,需要绕行 +- B) **赛道边墙** — 弯道处车头对准了侧墙,这是正常行驶状态 + +**解决思路:用视觉巡线的边界线(left_line / right_line)来区分 A 和 B。** + +--- + +## 关键约束:挡板会导致丢线 + +挡板立在赛道上时,不仅触发激光近距读数,还会**遮挡摄像头视野中的赛道边界**, +导致从挡板所在位置开始边界线丢失(left_line / right_line = -1)。 + +``` +Camera 俯视视角 (图像坐标): + row 0 (远) : ░░░░░░░ ← 被挡板挡住,丢线 + row 15 : ░░░░░░░ ← 挡板上方,丢线 + row 25 : ░░░░░░░ ← 挡板顶部附近,丢线 + row 30 : ███████ ← ★ 挡板所在行,边界线丢失 + row 35 : ■■■■■■■ ← 挡板下方,赛道可见,边界线有效 + row 59 (近) : ■■■■■■■ ← 车前方,赛道清晰 +``` + +**这意味着:不能像锥桶检测那样"往更远处看边线是否还开着",因为挡板后面的边线必然丢失。** + +正确的判定是检测 **"有效边界 → 丢线"的过渡位置是否与激光近距读数对应**: +- 紧贴着挡板下方(更近处):赛道应该可见,边界有效 +- 挡板位置及上方(更远处):边界丢失 +- 激光读数:短距离 → 同一位置有物理障碍物 + +三个条件同时成立 → 挡板确认。 + +--- + +## 几何模型 + +``` + Camera + Laser + | (高度 H, 俯角 θ) + |╲ + | ╲ laser beam + | ╲ + ground ──────────────┴────███████──── 挡板 at distance d_mm + (挡板后方赛道被遮挡) +``` + +- 激光光束沿车体正前方(图像中轴线) +- 距离 d_mm 越小 → 物体越近 → 映射到图像中越靠下的行(row 大) +- 距离 d_mm 越大 → 物体越远 → 映射到图像中越靠上的行(row 小) + +--- + +## 核心算法 + +### 第一步:读取激光距离 + +``` +d_mm = vl53l0x.readRange().RangeMilliMeter + +if d_mm >= LIDAR_THRESHOLD_MM: + return CLEAR // 远处无障碍,不做任何处理 +``` + +`LIDAR_THRESHOLD_MM` 是触发阈值。只有距离小于此值才进入判定。建议默认 ~300mm。 + +### 第二步:距离 → 图像行映射 + +``` +// 线性模型: +// row = lt_h-1 (底行, 最近) 对应 D_NEAR +// row = 10 (最远有效行) 对应 D_FAR +// clamp 到 [10, lt_h-1] + +row = lt_h - 1 - (d_mm - D_NEAR) / (D_FAR - D_NEAR) * (lt_h - 11) +row = clamp(row, 10, lt_h - 1) +``` + +**标定值(需根据实际安装位置测量):** + +| 参数 | 含义 | 建议初值 | +|------|------|----------| +| `D_NEAR` | row = lt_h-1 对应的物理距离 | 50 mm | +| `D_FAR` | row = 10 对应的物理距离 | 1200 mm | + +标定方法:在赛道前方 300mm、600mm、900mm 处各放一个挡板,记录图像中挡板出现的 row,线性拟合。 + +### 第三步:用边线判定障碍物(核心) + +``` +// 设 row = distance_to_row(d_mm),即激光测距对应的图像行 + +// ── 3a. 检查"挡板下方"(更近处):赛道应该仍可见 ── +near_valid = false +near_ref_row = -1 +for r = min(row + NEAR_START, lt_h-1) down to max(row + NEAR_END, lt_h-1): + if left_line[r] != -1 && right_line[r] != -1: + near_valid = true + near_ref_row = r // 记录有效行,后续绕行时判断宽侧 + break + +// ── 3b. 检查"挡板位置及上方"(更远处):边界应该已丢失 ── +far_lost = false +for r = row down to max(row - FAR_SPAN, 10): + if left_line[r] == -1 || right_line[r] == -1: + far_lost = true + break + +// ── 3c. 联合判定 ── +if near_valid && far_lost: + → OBSTACLE_CONFIRMED + 记录: g_lidar_obstacle_row = row + g_lidar_ref_row = near_ref_row // 用于判断绕行方向 +else: + → CLEAR +``` + +**判定原理(三种典型场景):** + +``` +场景 A: 挡板挡路 → 触发绕行 + d_mm = 300mm → row = 35 + row 36~39 (近处): ✓ 赛道可见 + row 30~35 (挡板处): ✗ 丢线 (挡板遮挡) + → near_valid=true, far_lost=true → ★ 触发 + +场景 B: 正常直道,无障碍 + d_mm = 1200mm → row = 10, 且 d_mm > THRESHOLD + → 不进入判定,直接 CLEAR + +场景 C: 弯道,激光打到边墙 + d_mm = 200mm → row = 40 + row 41~44 (近处): ✓ 赛道可见 + row 37~40 (弯道处): ✓ 赛道也可能可见(边墙不一定导致丢线) + → near_valid=true, far_lost=false → CLEAR +``` + +### 第四步:去抖确认 + +``` +if OBSTACLE_CONFIRMED: + g_lidar_frames++ +else: + g_lidar_frames = max(0, g_lidar_frames - 1) + +g_lidar_confirmed = (g_lidar_frames >= LIDAR_MIN_FRAMES) +``` + +--- + +## 绕行策略:中线变形 + +挡板通常只挡赛道的一部分(偏左或偏右),通过判断挡板下方有效行中哪一侧空间更大, +将中线推向宽侧实现绕行。 + +### 判断绕行方向 + +``` +// 在 near_ref_row(挡板下方最近的有效行)中判断左右空间 +left_space = mid_line[near_ref_row] - left_line[near_ref_row] +right_space = right_line[near_ref_row] - mid_line[near_ref_row] + +// 往宽侧推 +dir = (left_space > right_space) ? +1.0 : -1.0 +``` + +### 中线变形 + +对从 row=10 到挡板位置 row 的所有行施加变形,变形量从远到近线性斜坡上升: + +``` +// gain = lidar_avoid_gain (推离力度, 归一化) +// range = lidar_avoid_range (斜坡陡峭度, 行数) +// half_w = lt_w / 2 + +for r = 10 to g_lidar_obstacle_row: + t = clamp((r - 10) / range, 0.0, 1.0) // 斜率上升 + push = t * gain * half_w * dir + mid_line[r] = clamp(mid_line[r] + push, + left_line[r] + 2.0, + right_line[r] - 2.0) +``` + +**变形示意图:** + +``` + row 10 ───────●──────────────────────────○ 赛道中心线 + row 20 ────────●─────────────────────────○ (往右绕行) + row 30 ───────────●──────────────────────○ + row 35 ──────────────●───────────────────○ ← 挡板位置 + row 40 ────────────────████████████──────── ← 挡板 (丢线) + row 59 ─■────────●───■■■■■■■■■■■■■■───●───■ ← 车底 (近处可见) + left mid 方向盘往右打 right +``` + +### 速度联动 + +绕行时降速以保证安全: + +``` +if g_lidar_confirmed || g_lidar_hold_ctr > 0: + spd *= lidar_speed // 默认 0.4,即降至 40% 速度 +``` + +### 消失后保持衰减 + +挡板不再被检测到后,变形不会立即消失,而是线性衰减(类似锥桶 hold 逻辑): + +``` +// 确认状态 +if g_lidar_confirmed: + g_lidar_hold_ctr = 0 + g_lidar_hold_src_row = g_lidar_obstacle_row + g_lidar_hold_dir = dir + +// 衰减状态(挡板消失后) +else if g_lidar_hold_src_row > 0 && g_lidar_hold_ctr < lidar_hold_frames: + g_lidar_hold_ctr++ + decay = 1.0 - (double)g_lidar_hold_ctr / lidar_hold_frames // 线性衰减到 0 + + for r = 10 to g_lidar_hold_src_row: + t = clamp((r - 10) / range, 0.0, 1.0) + push = t * gain * half_w * g_lidar_hold_dir * decay + mid_line[r] = clamp(mid_line[r] + push, left+2, right-2) +``` + +--- + +## 状态机 + +``` + ┌────────────────────────┐ + │ │ + ▼ │ + ┌──────┐ 确认 ┌──────┐ 消失 ┌──────────┐ 保持结束 + │NORMAL│ ─────► │AVOID │ ─────► │HOLD_DECAY│ ────────► NORMAL + │ │ │ │ │ (衰减) │ + └──────┘ └──────┘ └──────────┘ + ▲ │ + └────────────────────────────────┘ 测距恢复正常 + +NORMAL: 正常行驶,激光测距 > THRESHOLD +AVOID: 挡板确认 → 中线变形绕行 + 减速 × lidar_speed +HOLD_DECAY: 挡板消失 → 变形量线性衰减 (lidar_hold_frames 帧内归零) + 衰减期间若再次检测到挡板 → 立即切回 AVOID +``` + +| 状态 | 电机 | 舵机 | 说明 | +|------|------|------|------| +| NORMAL | 正常速度 | 正常巡线 | 无变形 | +| AVOID | speed × lidar_speed | 中线偏转绕行 | 基于 left/right 空间判断方向 | +| HOLD_DECAY | speed × lidar_speed | 变形量线性衰减 | 确保车完全通过后再恢复 | + +--- + +## 与现有流水线的集成 + +`lidar_avoid_process()` 放在 cone_detect_and_deform 之前执行(两者都修改 mid_line, +lidar 优先级更高): + +``` +// CameraHandler 中 Step 5: +bool zebra_block = zebra_process(); +bool tl_block = traffic_light_process(); +lidar_avoid_process(); // ★ 新增:直接修改 mid_line +cone_detect_and_deform(); + +steering_update(); +motor_update(zebra_block, tl_block); +``` + +### 中线修改的优先级 + +lidar 和 cone 都会修改 `mid_line[]`。由于 lidar 绕行是结构性避障(避开整个挡板), +应**先执行 lidar 变形,再执行 cone 变形**。 +cone 的 clamp 到 `[left+2, right-2]` 会确保不超出 lidar 变形后的安全区间。 + +### motor_update 速度联动 + +```cpp +// motor_update 中新增: +if (g_lidar_confirmed || g_lidar_hold_ctr > 0) { + spd *= g_cfg.lidar_speed; +} +``` + +不通过 `block` 参数刹车(绕行不需要停车),而是降速 + 变形。 + +--- + +## 复用现有 VL53L0X 驱动 + +已有驱动(当前被 `CMakeLists.txt` 排除编译): + +```cpp +// lib/vl53l0x.h +VL53L0X sensor; +sensor.init(); // open /dev/stmvl53l0x_ranging +VL53L0X_RangingMeasurementData_t data; +sensor.readRange(data); // data.RangeMilliMeter +sensor.stop(); +``` + +**集成时需做的事:** +1. 从 `CMakeLists.txt` 的 EXCLUDE 列表中移除 `vl53l0x.cpp` +2. 在 `CameraInit()` 中调用 `sensor.init()`(硬件未连接时优雅降级) +3. 在 `cameraDeInit()` 中调用 `sensor.stop()` + +--- + +## 配置参数 + +| 文件 | 类型 | 建议默认 | 含义 | +|------|------|----------|------| +| `./lidar_thresh` | int | 300 | 障碍判定距离阈值 (mm),低于此值触发判定 | +| `./lidar_near` | int | 50 | row=lt_h-1 对应的物理距离 (mm) | +| `./lidar_far` | int | 1200 | row=10 对应的物理距离 (mm) | +| `./lidar_near_start` | int | 1 | near_valid 检测起始偏移 (row + N) | +| `./lidar_near_end` | int | 4 | near_valid 检测终止偏移 | +| `./lidar_far_span` | int | 6 | far_lost 检测跨度 (从 row 往上检查 N 行) | +| `./lidar_min_frames` | int | 3 | 连续确认帧数 | +| `./lidar_avoid_gain` | double | 0.4 | 绕行中线推离力度 (归一化) | +| `./lidar_avoid_range` | int | 30 | 绕行斜坡陡峭度 (行数) | +| `./lidar_speed` | double | 0.4 | 绕行时速度倍率 | +| `./lidar_hold_frames` | int | 30 | 挡板消失后变形保持帧数 | +| `./lidar_enable` | int(0/1) | 1 | 激光避障总开关(0=禁用) | + +--- + +## 边界情况 & 注意事项 + +1. **激光硬件未连接** — `sensor.init()` 失败时,`lidar_avoid_process()` 直接返回 false,不阻塞正常行驶。 +2. **弯道边墙误判** — 急弯处赛道边墙可能导致激光近距 + 边线丢失同时出现。依赖 `lidar_thresh` 和 `lidar_min_frames` 调参抑制。误判时车会短暂绕向一侧,弯道通过后立即恢复。 +3. **左右空间相等** — `left_space == right_space` 时默认 `dir = -1.0`(往右绕),可通过配置 `lidar_default_dir` 调整。 +4. **上坡/下坡** — 车辆俯仰变化会影响距离→行的映射精度。建议在平坦路段标定。 +5. **多传感器优先级** — lidar 先于 cone 修改 mid_line。zebra/tl 的 block 刹车优先级最高(红灯/斑马线停车不绕行)。 +6. **首次集成建议** — 先调通数据采集:打印 `d_mm`、对应 `row`、以及 `near_ref_row` 处的 `left/right/mid` 值,跑几圈确认标定参数无误后再开启绕行。 +7. **挡板材质** — 深色挡板不会被 HSV-Otsu 判为赛道(白色 255),但仍会遮挡背景赛道导致丢线。判定逻辑依赖的是**丢线**而非**像素颜色**。 diff --git a/lib/global.h b/lib/global.h index 170b0ff..df29b44 100644 --- a/lib/global.h +++ b/lib/global.h @@ -31,6 +31,19 @@ const std::string cone_hold_frames_file = "./cone_hold_frames"; const std::string brake_scale_file = "./brake_scale"; const std::string brake_max_file = "./brake_max"; +const std::string lidar_thresh_file = "./lidar_thresh"; +const std::string lidar_near_file = "./lidar_near"; +const std::string lidar_far_file = "./lidar_far"; +const std::string lidar_near_start_file = "./lidar_near_start"; +const std::string lidar_near_end_file = "./lidar_near_end"; +const std::string lidar_far_span_file = "./lidar_far_span"; +const std::string lidar_min_frames_file = "./lidar_min_frames"; +const std::string lidar_avoid_gain_file = "./lidar_avoid_gain"; +const std::string lidar_avoid_range_file = "./lidar_avoid_range"; +const std::string lidar_speed_file = "./lidar_speed"; +const std::string lidar_hold_frames_file = "./lidar_hold_frames"; +const std::string lidar_enable_file = "./lidar_enable"; + double readDoubleFromFile(const std::string &filename); bool readFlag(const std::string &filename); void cfg_load_all(); @@ -57,6 +70,19 @@ struct CfgCache { double brake_scale = 10; // 速度(pps) → 刹车占空比(ns) 缩放系数 int brake_max = 10000; // 最大刹车占空比 (ns) + + int lidar_thresh = 300; // 激光障碍判定距离阈值 (mm) + int lidar_near = 50; // row=lt_h-1 对应的物理距离 (mm) + int lidar_far = 1200; // row=10 对应的物理距离 (mm) + int lidar_near_start = 1; // near_valid 检测起始偏移 + int lidar_near_end = 4; // near_valid 检测终止偏移 + int lidar_far_span = 6; // far_lost 检测跨度 + int lidar_min_frames = 3; // 连续确认帧数 + double lidar_avoid_gain = 0.4; // 绕行中线推离力度 (归一化) + int lidar_avoid_range = 30; // 绕行斜坡陡峭度 (行数) + double lidar_speed = 0.4; // 绕行时速度倍率 + int lidar_hold_frames = 30; // 消失后变形保持帧数 + int lidar_enable = 1; // 激光避障总开关 }; extern CfgCache g_cfg; diff --git a/lib/vl53l0x.h b/lib/vl53l0x.h index 816a9c6..b943873 100644 --- a/lib/vl53l0x.h +++ b/lib/vl53l0x.h @@ -2,21 +2,25 @@ #define VL53L0X_H #include -#include "vl53l0x_def.h" + +extern "C" { +#include "vl53l0x_platform.h" +} class VL53L0X { public: - VL53L0X(); - ~VL53L0X(); + VL53L0X(); + ~VL53L0X(); - bool init(); - bool readRange(VL53L0X_RangingMeasurementData_t &data); - bool stop(); - bool isOpen() const { return fd > 0; } + bool init(); + bool readRange(VL53L0X_RangingMeasurementData_t &data); + bool stop(); private: - int fd; + int fd; + bool initialized; + vl53l0x_dev_t dev; }; #endif diff --git a/lib/vl53l0x/vl53l0x_api.c b/lib/vl53l0x/vl53l0x_api.c new file mode 100644 index 0000000..9adbe16 --- /dev/null +++ b/lib/vl53l0x/vl53l0x_api.c @@ -0,0 +1,3141 @@ +/******************************************************************************* + * Copyright 2016, STMicroelectronics International N.V. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of STMicroelectronics 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, FITNESS FOR A PARTICULAR PURPOSE, AND + NON-INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS ARE DISCLAIMED. + IN NO EVENT SHALL STMICROELECTRONICS INTERNATIONAL N.V. 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 "vl53l0x_api.h" +#include "vl53l0x_tuning.h" +#include "vl53l0x_interrupt_threshold_settings.h" +#include "vl53l0x_api_core.h" +#include "vl53l0x_api_calibration.h" +#include "vl53l0x_api_strings.h" + +#ifndef __KERNEL__ +#include +#endif +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(TRACE_MODULE_API, fmt, ##__VA_ARGS__) +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(TRACE_MODULE_API, status, ##__VA_ARGS__) +#define LOG_FUNCTION_END_FMT(status, fmt, ...) \ + _LOG_FUNCTION_END_FMT(TRACE_MODULE_API, status, fmt, ##__VA_ARGS__) + +#ifdef VL53L0X_LOG_ENABLE +#define trace_print(level, ...) trace_print_module_function(TRACE_MODULE_API, \ + level, TRACE_FUNCTION_NONE, ##__VA_ARGS__) +#endif + +/* Group PAL General Functions */ + +VL53L0X_Error VL53L0X_GetVersion(VL53L0X_Version_t *pVersion) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + pVersion->major = VL53L0X_IMPLEMENTATION_VER_MAJOR; + pVersion->minor = VL53L0X_IMPLEMENTATION_VER_MINOR; + pVersion->build = VL53L0X_IMPLEMENTATION_VER_SUB; + + pVersion->revision = VL53L0X_IMPLEMENTATION_VER_REVISION; + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetPalSpecVersion(VL53L0X_Version_t *pPalSpecVersion) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + pPalSpecVersion->major = VL53L0X_SPECIFICATION_VER_MAJOR; + pPalSpecVersion->minor = VL53L0X_SPECIFICATION_VER_MINOR; + pPalSpecVersion->build = VL53L0X_SPECIFICATION_VER_SUB; + + pPalSpecVersion->revision = VL53L0X_SPECIFICATION_VER_REVISION; + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetProductRevision(VL53L0X_DEV Dev, + uint8_t *pProductRevisionMajor, uint8_t *pProductRevisionMinor) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t revision_id; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_RdByte(Dev, VL53L0X_REG_IDENTIFICATION_REVISION_ID, + &revision_id); + *pProductRevisionMajor = 1; + *pProductRevisionMinor = (revision_id & 0xF0) >> 4; + + LOG_FUNCTION_END(Status); + return Status; + +} + +VL53L0X_Error VL53L0X_GetDeviceInfo(VL53L0X_DEV Dev, + VL53L0X_DeviceInfo_t *pVL53L0X_DeviceInfo) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_get_device_info(Dev, pVL53L0X_DeviceInfo); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetDeviceErrorStatus(VL53L0X_DEV Dev, + VL53L0X_DeviceError *pDeviceErrorStatus) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t RangeStatus; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_RdByte(Dev, VL53L0X_REG_RESULT_RANGE_STATUS, + &RangeStatus); + + *pDeviceErrorStatus = (VL53L0X_DeviceError)((RangeStatus & 0x78) >> 3); + + LOG_FUNCTION_END(Status); + return Status; +} + + +VL53L0X_Error VL53L0X_GetDeviceErrorString(VL53L0X_DeviceError ErrorCode, + char *pDeviceErrorString) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_get_device_error_string(ErrorCode, pDeviceErrorString); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetRangeStatusString(uint8_t RangeStatus, + char *pRangeStatusString) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_get_range_status_string(RangeStatus, + pRangeStatusString); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetPalErrorString(VL53L0X_Error PalErrorCode, + char *pPalErrorString) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_get_pal_error_string(PalErrorCode, pPalErrorString); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetPalStateString(VL53L0X_State PalStateCode, + char *pPalStateString) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_get_pal_state_string(PalStateCode, pPalStateString); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetPalState(VL53L0X_DEV Dev, VL53L0X_State *pPalState) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + *pPalState = PALDevDataGet(Dev, PalState); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetPowerMode(VL53L0X_DEV Dev, + VL53L0X_PowerModes PowerMode) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + /* Only level1 of Power mode exists */ + if ((PowerMode != VL53L0X_POWERMODE_STANDBY_LEVEL1) + && (PowerMode != VL53L0X_POWERMODE_IDLE_LEVEL1)) { + Status = VL53L0X_ERROR_MODE_NOT_SUPPORTED; + } else if (PowerMode == VL53L0X_POWERMODE_STANDBY_LEVEL1) { + /* set the standby level1 of power mode */ + Status = VL53L0X_WrByte(Dev, 0x80, 0x00); + if (Status == VL53L0X_ERROR_NONE) { + /* Set PAL State to standby */ + PALDevDataSet(Dev, PalState, VL53L0X_STATE_STANDBY); + PALDevDataSet(Dev, PowerMode, + VL53L0X_POWERMODE_STANDBY_LEVEL1); + } + + } else { + /* VL53L0X_POWERMODE_IDLE_LEVEL1 */ + Status = VL53L0X_WrByte(Dev, 0x80, 0x00); + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_StaticInit(Dev); + + if (Status == VL53L0X_ERROR_NONE) + PALDevDataSet(Dev, PowerMode, + VL53L0X_POWERMODE_IDLE_LEVEL1); + + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetPowerMode(VL53L0X_DEV Dev, + VL53L0X_PowerModes *pPowerMode) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t Byte; + + LOG_FUNCTION_START(""); + + /* Only level1 of Power mode exists */ + Status = VL53L0X_RdByte(Dev, 0x80, &Byte); + + if (Status == VL53L0X_ERROR_NONE) { + if (Byte == 1) { + PALDevDataSet(Dev, PowerMode, + VL53L0X_POWERMODE_IDLE_LEVEL1); + } else { + PALDevDataSet(Dev, PowerMode, + VL53L0X_POWERMODE_STANDBY_LEVEL1); + } + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetOffsetCalibrationDataMicroMeter(VL53L0X_DEV Dev, + int32_t OffsetCalibrationDataMicroMeter) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_set_offset_calibration_data_micro_meter(Dev, + OffsetCalibrationDataMicroMeter); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetOffsetCalibrationDataMicroMeter(VL53L0X_DEV Dev, + int32_t *pOffsetCalibrationDataMicroMeter) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_get_offset_calibration_data_micro_meter(Dev, + pOffsetCalibrationDataMicroMeter); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetLinearityCorrectiveGain(VL53L0X_DEV Dev, + int16_t LinearityCorrectiveGain) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + if ((LinearityCorrectiveGain < 0) || (LinearityCorrectiveGain > 1000)) + Status = VL53L0X_ERROR_INVALID_PARAMS; + else { + PALDevDataSet(Dev, LinearityCorrectiveGain, + LinearityCorrectiveGain); + + if (LinearityCorrectiveGain != 1000) { + /* Disable FW Xtalk */ + Status = VL53L0X_WrWord(Dev, + VL53L0X_REG_CROSSTALK_COMPENSATION_PEAK_RATE_MCPS, 0); + } + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetLinearityCorrectiveGain(VL53L0X_DEV Dev, + uint16_t *pLinearityCorrectiveGain) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + *pLinearityCorrectiveGain = PALDevDataGet(Dev, LinearityCorrectiveGain); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetGroupParamHold(VL53L0X_DEV Dev, uint8_t GroupParamHold) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NOT_IMPLEMENTED; + + LOG_FUNCTION_START(""); + + /* not implemented on VL53L0X */ + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetUpperLimitMilliMeter(VL53L0X_DEV Dev, + uint16_t *pUpperLimitMilliMeter) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NOT_IMPLEMENTED; + + LOG_FUNCTION_START(""); + + /* not implemented on VL53L0X */ + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetTotalSignalRate(VL53L0X_DEV Dev, + FixPoint1616_t *pTotalSignalRate) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + VL53L0X_RangingMeasurementData_t LastRangeDataBuffer; + + LOG_FUNCTION_START(""); + + LastRangeDataBuffer = PALDevDataGet(Dev, LastRangeMeasure); + + Status = VL53L0X_get_total_signal_rate( + Dev, &LastRangeDataBuffer, pTotalSignalRate); + + LOG_FUNCTION_END(Status); + return Status; +} + +/* End Group PAL General Functions */ + +/* Group PAL Init Functions */ +VL53L0X_Error VL53L0X_SetDeviceAddress(VL53L0X_DEV Dev, uint8_t DeviceAddress) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_WrByte(Dev, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, + DeviceAddress / 2); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_DataInit(VL53L0X_DEV Dev) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + VL53L0X_DeviceParameters_t CurrentParameters; + int i; + uint8_t StopVariable; + + LOG_FUNCTION_START(""); + + /* by default the I2C is running at 1V8 if you want to change it you + * need to include this define at compilation level. + */ +#ifdef USE_I2C_2V8 + Status = VL53L0X_UpdateByte(Dev, + VL53L0X_REG_VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV, + 0xFE, + 0x01); +#endif + + /* Set I2C standard mode */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_WrByte(Dev, 0x88, 0x00); + + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, ReadDataFromDeviceDone, 0); + +#ifdef USE_IQC_STATION + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_apply_offset_adjustment(Dev); +#endif + + /* Default value is 1000 for Linearity Corrective Gain */ + PALDevDataSet(Dev, LinearityCorrectiveGain, 1000); + + /* Set Default static parameters + *set first temporary values 9.44MHz * 65536 = 618660 + */ + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, OscFrequencyMHz, 618660); + + /* Set Default XTalkCompensationRateMegaCps to 0 */ + VL53L0X_SETPARAMETERFIELD(Dev, XTalkCompensationRateMegaCps, 0); + + /* Get default parameters */ + Status = VL53L0X_GetDeviceParameters(Dev, &CurrentParameters); + if (Status == VL53L0X_ERROR_NONE) { + /* initialize PAL values */ + CurrentParameters.DeviceMode = + VL53L0X_DEVICEMODE_SINGLE_RANGING; + CurrentParameters.HistogramMode = + VL53L0X_HISTOGRAMMODE_DISABLED; + + /* Dmax lookup table */ + /* 0.0 */ + CurrentParameters.dmax_lut.ambRate_mcps[0] = (FixPoint1616_t)0x00000000; + /* 1200 */ + CurrentParameters.dmax_lut.dmax_mm[0] = (FixPoint1616_t)0x04B00000; + /* 0.7 */ + CurrentParameters.dmax_lut.ambRate_mcps[1] = (FixPoint1616_t)0x0000B333; + /* 1100 */ + CurrentParameters.dmax_lut.dmax_mm[1] = (FixPoint1616_t)0x044C0000; + /* 2 */ + CurrentParameters.dmax_lut.ambRate_mcps[2] = (FixPoint1616_t)0x00020000; + /* 900 */ + CurrentParameters.dmax_lut.dmax_mm[2] = (FixPoint1616_t)0x03840000; + /* 3.8 */ + CurrentParameters.dmax_lut.ambRate_mcps[3] = (FixPoint1616_t)0x0003CCCC; + /* 750 */ + CurrentParameters.dmax_lut.dmax_mm[3] = (FixPoint1616_t)0x02EE0000; + /* 7.3 */ + CurrentParameters.dmax_lut.ambRate_mcps[4] = (FixPoint1616_t)0x00074CCC; + /* 550 */ + CurrentParameters.dmax_lut.dmax_mm[4] = (FixPoint1616_t)0x02260000; + /* 10 */ + CurrentParameters.dmax_lut.ambRate_mcps[5] = (FixPoint1616_t)0x000A0000; + /* 500 */ + CurrentParameters.dmax_lut.dmax_mm[5] = (FixPoint1616_t)0x01F40000; + /* 15 */ + CurrentParameters.dmax_lut.ambRate_mcps[6] = (FixPoint1616_t)0x000F0000; + /* 400 */ + CurrentParameters.dmax_lut.dmax_mm[6] = (FixPoint1616_t)0x01900000; + + PALDevDataSet(Dev, CurrentParameters, CurrentParameters); + } + + /* Sigma estimator variable */ + PALDevDataSet(Dev, SigmaEstRefArray, 100); + PALDevDataSet(Dev, SigmaEstEffPulseWidth, 900); + PALDevDataSet(Dev, SigmaEstEffAmbWidth, 500); + PALDevDataSet(Dev, targetRefRate, 0x0A00); /* 20 MCPS in 9:7 format */ + + /* Use internal default settings */ + PALDevDataSet(Dev, UseInternalTuningSettings, 1); + + Status |= VL53L0X_WrByte(Dev, 0x80, 0x01); + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x01); + Status |= VL53L0X_WrByte(Dev, 0x00, 0x00); + Status |= VL53L0X_RdByte(Dev, 0x91, &StopVariable); + PALDevDataSet(Dev, StopVariable, StopVariable); + Status |= VL53L0X_WrByte(Dev, 0x00, 0x01); + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x00); + Status |= VL53L0X_WrByte(Dev, 0x80, 0x00); + + /* Enable all check */ + for (i = 0; i < VL53L0X_CHECKENABLE_NUMBER_OF_CHECKS; i++) { + if (Status == VL53L0X_ERROR_NONE) + Status |= VL53L0X_SetLimitCheckEnable(Dev, i, 1); + else + break; + + } + + /* Disable the following checks */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_SetLimitCheckEnable(Dev, + VL53L0X_CHECKENABLE_SIGNAL_REF_CLIP, 0); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_SetLimitCheckEnable(Dev, + VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD, 0); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_SetLimitCheckEnable(Dev, + VL53L0X_CHECKENABLE_SIGNAL_RATE_MSRC, 0); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_SetLimitCheckEnable(Dev, + VL53L0X_CHECKENABLE_SIGNAL_RATE_PRE_RANGE, 0); + + /* Limit default values */ + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_SetLimitCheckValue(Dev, + VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE, + (FixPoint1616_t)(18 * 65536)); + } + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_SetLimitCheckValue(Dev, + VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE, + (FixPoint1616_t)(25 * 65536 / 100)); + /* 0.25 * 65536 */ + } + + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_SetLimitCheckValue(Dev, + VL53L0X_CHECKENABLE_SIGNAL_REF_CLIP, + (FixPoint1616_t)(35 * 65536)); + } + + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_SetLimitCheckValue(Dev, + VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD, + (FixPoint1616_t)(0 * 65536)); + } + + if (Status == VL53L0X_ERROR_NONE) { + + PALDevDataSet(Dev, SequenceConfig, 0xFF); + Status = VL53L0X_WrByte(Dev, VL53L0X_REG_SYSTEM_SEQUENCE_CONFIG, + 0xFF); + + /* Set PAL state to tell that we are waiting for call to + * VL53L0X_StaticInit + */ + PALDevDataSet(Dev, PalState, VL53L0X_STATE_WAIT_STATICINIT); + } + + if (Status == VL53L0X_ERROR_NONE) + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, RefSpadsInitialised, 0); + + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetTuningSettingBuffer(VL53L0X_DEV Dev, + uint8_t *pTuningSettingBuffer, uint8_t UseInternalTuningSettings) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + if (UseInternalTuningSettings == 1) { + /* Force use internal settings */ + PALDevDataSet(Dev, UseInternalTuningSettings, 1); + } else { + + /* check that the first byte is not 0 */ + if (*pTuningSettingBuffer != 0) { + PALDevDataSet(Dev, pTuningSettingsPointer, + pTuningSettingBuffer); + PALDevDataSet(Dev, UseInternalTuningSettings, 0); + + } else { + Status = VL53L0X_ERROR_INVALID_PARAMS; + } + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetTuningSettingBuffer(VL53L0X_DEV Dev, + uint8_t **ppTuningSettingBuffer, uint8_t *pUseInternalTuningSettings) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + *ppTuningSettingBuffer = PALDevDataGet(Dev, pTuningSettingsPointer); + *pUseInternalTuningSettings = PALDevDataGet(Dev, + UseInternalTuningSettings); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_StaticInit(VL53L0X_DEV Dev) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + VL53L0X_DeviceParameters_t CurrentParameters = {0}; + uint8_t *pTuningSettingBuffer; + uint16_t tempword = 0; + uint8_t tempbyte = 0; + uint8_t UseInternalTuningSettings = 0; + uint32_t count = 0; + uint8_t isApertureSpads = 0; + uint32_t refSpadCount = 0; + uint8_t ApertureSpads = 0; + uint8_t vcselPulsePeriodPCLK; + uint32_t seqTimeoutMicroSecs; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_get_info_from_device(Dev, 1); + + /* set the ref spad from NVM */ + count = (uint32_t)VL53L0X_GETDEVICESPECIFICPARAMETER(Dev, + ReferenceSpadCount); + ApertureSpads = VL53L0X_GETDEVICESPECIFICPARAMETER(Dev, + ReferenceSpadType); + + /* NVM value invalid */ + if ((ApertureSpads > 1) || + ((ApertureSpads == 1) && (count > 32)) || + ((ApertureSpads == 0) && (count > 12))) + Status = VL53L0X_perform_ref_spad_management(Dev, &refSpadCount, + &isApertureSpads); + else + Status = VL53L0X_set_reference_spads(Dev, count, ApertureSpads); + + + /* Initialize tuning settings buffer to prevent compiler warning. */ + pTuningSettingBuffer = DefaultTuningSettings; + + if (Status == VL53L0X_ERROR_NONE) { + UseInternalTuningSettings = PALDevDataGet(Dev, + UseInternalTuningSettings); + + if (UseInternalTuningSettings == 0) + pTuningSettingBuffer = PALDevDataGet(Dev, + pTuningSettingsPointer); + else + pTuningSettingBuffer = DefaultTuningSettings; + + } + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_load_tuning_settings(Dev, + pTuningSettingBuffer); + + + /* Set interrupt config to new sample ready */ + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_SetGpioConfig(Dev, 0, 0, + VL53L0X_REG_SYSTEM_INTERRUPT_GPIO_NEW_SAMPLE_READY, + VL53L0X_INTERRUPTPOLARITY_LOW); + } + + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_WrByte(Dev, 0xFF, 0x01); + Status |= VL53L0X_RdWord(Dev, 0x84, &tempword); + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x00); + } + + if (Status == VL53L0X_ERROR_NONE) { + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, OscFrequencyMHz, + VL53L0X_FIXPOINT412TOFIXPOINT1616(tempword)); + } + + /* After static init, some device parameters may be changed, + * so update them + */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_GetDeviceParameters(Dev, &CurrentParameters); + + + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_GetFractionEnable(Dev, &tempbyte); + if (Status == VL53L0X_ERROR_NONE) + PALDevDataSet(Dev, RangeFractionalEnable, tempbyte); + + } + + if (Status == VL53L0X_ERROR_NONE) + PALDevDataSet(Dev, CurrentParameters, CurrentParameters); + + + /* read the sequence config and save it */ + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_RdByte(Dev, + VL53L0X_REG_SYSTEM_SEQUENCE_CONFIG, &tempbyte); + if (Status == VL53L0X_ERROR_NONE) + PALDevDataSet(Dev, SequenceConfig, tempbyte); + + } + + /* Disable MSRC and TCC by default */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_SetSequenceStepEnable(Dev, + VL53L0X_SEQUENCESTEP_TCC, 0); + + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_SetSequenceStepEnable(Dev, + VL53L0X_SEQUENCESTEP_MSRC, 0); + + + /* Set PAL State to standby */ + if (Status == VL53L0X_ERROR_NONE) + PALDevDataSet(Dev, PalState, VL53L0X_STATE_IDLE); + + + + /* Store pre-range vcsel period */ + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_GetVcselPulsePeriod( + Dev, + VL53L0X_VCSEL_PERIOD_PRE_RANGE, + &vcselPulsePeriodPCLK); + } + + if (Status == VL53L0X_ERROR_NONE) { + VL53L0X_SETDEVICESPECIFICPARAMETER( + Dev, + PreRangeVcselPulsePeriod, + vcselPulsePeriodPCLK); + } + + /* Store final-range vcsel period */ + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_GetVcselPulsePeriod( + Dev, + VL53L0X_VCSEL_PERIOD_FINAL_RANGE, + &vcselPulsePeriodPCLK); + } + + if (Status == VL53L0X_ERROR_NONE) { + VL53L0X_SETDEVICESPECIFICPARAMETER( + Dev, + FinalRangeVcselPulsePeriod, + vcselPulsePeriodPCLK); + } + + /* Store pre-range timeout */ + if (Status == VL53L0X_ERROR_NONE) { + Status = get_sequence_step_timeout( + Dev, + VL53L0X_SEQUENCESTEP_PRE_RANGE, + &seqTimeoutMicroSecs); + } + + if (Status == VL53L0X_ERROR_NONE) { + VL53L0X_SETDEVICESPECIFICPARAMETER( + Dev, + PreRangeTimeoutMicroSecs, + seqTimeoutMicroSecs); + } + + /* Store final-range timeout */ + if (Status == VL53L0X_ERROR_NONE) { + Status = get_sequence_step_timeout( + Dev, + VL53L0X_SEQUENCESTEP_FINAL_RANGE, + &seqTimeoutMicroSecs); + } + + if (Status == VL53L0X_ERROR_NONE) { + VL53L0X_SETDEVICESPECIFICPARAMETER( + Dev, + FinalRangeTimeoutMicroSecs, + seqTimeoutMicroSecs); + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_WaitDeviceBooted(VL53L0X_DEV Dev) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NOT_IMPLEMENTED; + + LOG_FUNCTION_START(""); + + /* not implemented on VL53L0X */ + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_ResetDevice(VL53L0X_DEV Dev) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t Byte; + + LOG_FUNCTION_START(""); + + /* Set reset bit */ + Status = VL53L0X_WrByte(Dev, VL53L0X_REG_SOFT_RESET_GO2_SOFT_RESET_N, + 0x00); + + /* Wait for some time */ + if (Status == VL53L0X_ERROR_NONE) { + do { + Status = VL53L0X_RdByte(Dev, + VL53L0X_REG_IDENTIFICATION_MODEL_ID, &Byte); + } while (Byte != 0x00); + } + + VL53L0X_PollingDelay(Dev); + + /* Release reset */ + Status = VL53L0X_WrByte(Dev, VL53L0X_REG_SOFT_RESET_GO2_SOFT_RESET_N, + 0x01); + + /* Wait until correct boot-up of the device */ + if (Status == VL53L0X_ERROR_NONE) { + do { + Status = VL53L0X_RdByte(Dev, + VL53L0X_REG_IDENTIFICATION_MODEL_ID, &Byte); + } while (Byte == 0x00); + } + + VL53L0X_PollingDelay(Dev); + + /* Set PAL State to VL53L0X_STATE_POWERDOWN */ + if (Status == VL53L0X_ERROR_NONE) + PALDevDataSet(Dev, PalState, VL53L0X_STATE_POWERDOWN); + + + LOG_FUNCTION_END(Status); + return Status; +} +/* End Group PAL Init Functions */ + +/* Group PAL Parameters Functions */ +VL53L0X_Error VL53L0X_SetDeviceParameters(VL53L0X_DEV Dev, + const VL53L0X_DeviceParameters_t *pDeviceParameters) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + int i; + + LOG_FUNCTION_START(""); + Status = VL53L0X_SetDeviceMode(Dev, pDeviceParameters->DeviceMode); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_SetInterMeasurementPeriodMilliSeconds(Dev, + pDeviceParameters->InterMeasurementPeriodMilliSeconds); + + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_SetXTalkCompensationRateMegaCps(Dev, + pDeviceParameters->XTalkCompensationRateMegaCps); + + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_SetOffsetCalibrationDataMicroMeter(Dev, + pDeviceParameters->RangeOffsetMicroMeters); + + + for (i = 0; i < VL53L0X_CHECKENABLE_NUMBER_OF_CHECKS; i++) { + if (Status == VL53L0X_ERROR_NONE) + Status |= VL53L0X_SetLimitCheckEnable(Dev, i, + pDeviceParameters->LimitChecksEnable[i]); + else + break; + + if (Status == VL53L0X_ERROR_NONE) + Status |= VL53L0X_SetLimitCheckValue(Dev, i, + pDeviceParameters->LimitChecksValue[i]); + else + break; + + } + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_SetWrapAroundCheckEnable(Dev, + pDeviceParameters->WrapAroundCheckEnable); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_SetMeasurementTimingBudgetMicroSeconds(Dev, + pDeviceParameters->MeasurementTimingBudgetMicroSeconds); + + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetDeviceParameters(VL53L0X_DEV Dev, + VL53L0X_DeviceParameters_t *pDeviceParameters) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + int i; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_GetDeviceMode(Dev, &(pDeviceParameters->DeviceMode)); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_GetInterMeasurementPeriodMilliSeconds(Dev, + &(pDeviceParameters->InterMeasurementPeriodMilliSeconds)); + + + if (Status == VL53L0X_ERROR_NONE) + pDeviceParameters->XTalkCompensationEnable = 0; + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_GetXTalkCompensationRateMegaCps(Dev, + &(pDeviceParameters->XTalkCompensationRateMegaCps)); + + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_GetOffsetCalibrationDataMicroMeter(Dev, + &(pDeviceParameters->RangeOffsetMicroMeters)); + + + if (Status == VL53L0X_ERROR_NONE) { + for (i = 0; i < VL53L0X_CHECKENABLE_NUMBER_OF_CHECKS; i++) { + /* get first the values, then the enables. + * VL53L0X_GetLimitCheckValue will modify the enable + * flags + */ + if (Status == VL53L0X_ERROR_NONE) { + Status |= VL53L0X_GetLimitCheckValue(Dev, i, + &(pDeviceParameters->LimitChecksValue[i])); + } else { + break; + } + if (Status == VL53L0X_ERROR_NONE) { + Status |= VL53L0X_GetLimitCheckEnable(Dev, i, + &(pDeviceParameters->LimitChecksEnable[i])); + } else { + break; + } + } + } + + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_GetWrapAroundCheckEnable(Dev, + &(pDeviceParameters->WrapAroundCheckEnable)); + } + + /* Need to be done at the end as it uses VCSELPulsePeriod */ + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_GetMeasurementTimingBudgetMicroSeconds(Dev, + &(pDeviceParameters->MeasurementTimingBudgetMicroSeconds)); + } + + if (Status == VL53L0X_ERROR_NONE) { + for (i = 0; i < VL53L0X_DMAX_LUT_SIZE; i++) { + pDeviceParameters->dmax_lut.ambRate_mcps[i] = + Dev->Data.CurrentParameters.dmax_lut.ambRate_mcps[i]; + pDeviceParameters->dmax_lut.dmax_mm[i] = + Dev->Data.CurrentParameters.dmax_lut.dmax_mm[i]; + } + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetDeviceMode(VL53L0X_DEV Dev, + VL53L0X_DeviceModes DeviceMode) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START("%d", (int)DeviceMode); + + switch (DeviceMode) { + case VL53L0X_DEVICEMODE_SINGLE_RANGING: + case VL53L0X_DEVICEMODE_CONTINUOUS_RANGING: + case VL53L0X_DEVICEMODE_CONTINUOUS_TIMED_RANGING: + case VL53L0X_DEVICEMODE_GPIO_DRIVE: + case VL53L0X_DEVICEMODE_GPIO_OSC: + /* Supported modes */ + VL53L0X_SETPARAMETERFIELD(Dev, DeviceMode, DeviceMode); + break; + default: + /* Unsupported mode */ + Status = VL53L0X_ERROR_MODE_NOT_SUPPORTED; + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetDeviceMode(VL53L0X_DEV Dev, + VL53L0X_DeviceModes *pDeviceMode) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + VL53L0X_GETPARAMETERFIELD(Dev, DeviceMode, *pDeviceMode); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetRangeFractionEnable(VL53L0X_DEV Dev, uint8_t Enable) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START("%d", (int)Enable); + + Status = VL53L0X_WrByte(Dev, VL53L0X_REG_SYSTEM_RANGE_CONFIG, Enable); + + if (Status == VL53L0X_ERROR_NONE) + PALDevDataSet(Dev, RangeFractionalEnable, Enable); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetFractionEnable(VL53L0X_DEV Dev, uint8_t *pEnabled) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_RdByte(Dev, VL53L0X_REG_SYSTEM_RANGE_CONFIG, pEnabled); + + if (Status == VL53L0X_ERROR_NONE) + *pEnabled = (*pEnabled & 1); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetHistogramMode(VL53L0X_DEV Dev, + VL53L0X_HistogramModes HistogramMode) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NOT_IMPLEMENTED; + + LOG_FUNCTION_START(""); + + /* not implemented on VL53L0X */ + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetHistogramMode(VL53L0X_DEV Dev, + VL53L0X_HistogramModes *pHistogramMode) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NOT_IMPLEMENTED; + + LOG_FUNCTION_START(""); + + /* not implemented on VL53L0X */ + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetMeasurementTimingBudgetMicroSeconds(VL53L0X_DEV Dev, + uint32_t MeasurementTimingBudgetMicroSeconds) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_set_measurement_timing_budget_micro_seconds(Dev, + MeasurementTimingBudgetMicroSeconds); + + LOG_FUNCTION_END(Status); + + return Status; +} + +VL53L0X_Error VL53L0X_GetMeasurementTimingBudgetMicroSeconds(VL53L0X_DEV Dev, + uint32_t *pMeasurementTimingBudgetMicroSeconds) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_get_measurement_timing_budget_micro_seconds(Dev, + pMeasurementTimingBudgetMicroSeconds); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetVcselPulsePeriod(VL53L0X_DEV Dev, + VL53L0X_VcselPeriod VcselPeriodType, uint8_t VCSELPulsePeriodPCLK) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_set_vcsel_pulse_period(Dev, VcselPeriodType, + VCSELPulsePeriodPCLK); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetVcselPulsePeriod(VL53L0X_DEV Dev, + VL53L0X_VcselPeriod VcselPeriodType, uint8_t *pVCSELPulsePeriodPCLK) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_get_vcsel_pulse_period(Dev, VcselPeriodType, + pVCSELPulsePeriodPCLK); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetSequenceStepEnable(VL53L0X_DEV Dev, + VL53L0X_SequenceStepId SequenceStepId, uint8_t SequenceStepEnabled) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t SequenceConfig = 0; + uint8_t SequenceConfigNew = 0; + uint32_t MeasurementTimingBudgetMicroSeconds; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_RdByte(Dev, VL53L0X_REG_SYSTEM_SEQUENCE_CONFIG, + &SequenceConfig); + + SequenceConfigNew = SequenceConfig; + + if (Status == VL53L0X_ERROR_NONE) { + if (SequenceStepEnabled == 1) { + + /* Enable requested sequence step + */ + switch (SequenceStepId) { + case VL53L0X_SEQUENCESTEP_TCC: + SequenceConfigNew |= 0x10; + break; + case VL53L0X_SEQUENCESTEP_DSS: + SequenceConfigNew |= 0x28; + break; + case VL53L0X_SEQUENCESTEP_MSRC: + SequenceConfigNew |= 0x04; + break; + case VL53L0X_SEQUENCESTEP_PRE_RANGE: + SequenceConfigNew |= 0x40; + break; + case VL53L0X_SEQUENCESTEP_FINAL_RANGE: + SequenceConfigNew |= 0x80; + break; + default: + Status = VL53L0X_ERROR_INVALID_PARAMS; + } + } else { + /* Disable requested sequence step + */ + switch (SequenceStepId) { + case VL53L0X_SEQUENCESTEP_TCC: + SequenceConfigNew &= 0xef; + break; + case VL53L0X_SEQUENCESTEP_DSS: + SequenceConfigNew &= 0xd7; + break; + case VL53L0X_SEQUENCESTEP_MSRC: + SequenceConfigNew &= 0xfb; + break; + case VL53L0X_SEQUENCESTEP_PRE_RANGE: + SequenceConfigNew &= 0xbf; + break; + case VL53L0X_SEQUENCESTEP_FINAL_RANGE: + SequenceConfigNew &= 0x7f; + break; + default: + Status = VL53L0X_ERROR_INVALID_PARAMS; + } + } + } + + if (SequenceConfigNew != SequenceConfig) { + /* Apply New Setting */ + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_SYSTEM_SEQUENCE_CONFIG, SequenceConfigNew); + } + if (Status == VL53L0X_ERROR_NONE) + PALDevDataSet(Dev, SequenceConfig, SequenceConfigNew); + + + /* Recalculate timing budget */ + if (Status == VL53L0X_ERROR_NONE) { + VL53L0X_GETPARAMETERFIELD(Dev, + MeasurementTimingBudgetMicroSeconds, + MeasurementTimingBudgetMicroSeconds); + + VL53L0X_SetMeasurementTimingBudgetMicroSeconds(Dev, + MeasurementTimingBudgetMicroSeconds); + } + } + + LOG_FUNCTION_END(Status); + + return Status; +} + +VL53L0X_Error sequence_step_enabled(VL53L0X_DEV Dev, + VL53L0X_SequenceStepId SequenceStepId, uint8_t SequenceConfig, + uint8_t *pSequenceStepEnabled) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + *pSequenceStepEnabled = 0; + LOG_FUNCTION_START(""); + + switch (SequenceStepId) { + case VL53L0X_SEQUENCESTEP_TCC: + *pSequenceStepEnabled = (SequenceConfig & 0x10) >> 4; + break; + case VL53L0X_SEQUENCESTEP_DSS: + *pSequenceStepEnabled = (SequenceConfig & 0x08) >> 3; + break; + case VL53L0X_SEQUENCESTEP_MSRC: + *pSequenceStepEnabled = (SequenceConfig & 0x04) >> 2; + break; + case VL53L0X_SEQUENCESTEP_PRE_RANGE: + *pSequenceStepEnabled = (SequenceConfig & 0x40) >> 6; + break; + case VL53L0X_SEQUENCESTEP_FINAL_RANGE: + *pSequenceStepEnabled = (SequenceConfig & 0x80) >> 7; + break; + default: + Status = VL53L0X_ERROR_INVALID_PARAMS; + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetSequenceStepEnable(VL53L0X_DEV Dev, + VL53L0X_SequenceStepId SequenceStepId, uint8_t *pSequenceStepEnabled) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t SequenceConfig = 0; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_RdByte(Dev, VL53L0X_REG_SYSTEM_SEQUENCE_CONFIG, + &SequenceConfig); + + if (Status == VL53L0X_ERROR_NONE) { + Status = sequence_step_enabled(Dev, SequenceStepId, + SequenceConfig, pSequenceStepEnabled); + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetSequenceStepEnables(VL53L0X_DEV Dev, + VL53L0X_SchedulerSequenceSteps_t *pSchedulerSequenceSteps) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t SequenceConfig = 0; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_RdByte(Dev, VL53L0X_REG_SYSTEM_SEQUENCE_CONFIG, + &SequenceConfig); + + if (Status == VL53L0X_ERROR_NONE) { + Status = sequence_step_enabled(Dev, + VL53L0X_SEQUENCESTEP_TCC, SequenceConfig, + &pSchedulerSequenceSteps->TccOn); + } + if (Status == VL53L0X_ERROR_NONE) { + Status = sequence_step_enabled(Dev, + VL53L0X_SEQUENCESTEP_DSS, SequenceConfig, + &pSchedulerSequenceSteps->DssOn); + } + if (Status == VL53L0X_ERROR_NONE) { + Status = sequence_step_enabled(Dev, + VL53L0X_SEQUENCESTEP_MSRC, SequenceConfig, + &pSchedulerSequenceSteps->MsrcOn); + } + if (Status == VL53L0X_ERROR_NONE) { + Status = sequence_step_enabled(Dev, + VL53L0X_SEQUENCESTEP_PRE_RANGE, SequenceConfig, + &pSchedulerSequenceSteps->PreRangeOn); + } + if (Status == VL53L0X_ERROR_NONE) { + Status = sequence_step_enabled(Dev, + VL53L0X_SEQUENCESTEP_FINAL_RANGE, SequenceConfig, + &pSchedulerSequenceSteps->FinalRangeOn); + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetNumberOfSequenceSteps(VL53L0X_DEV Dev, + uint8_t *pNumberOfSequenceSteps) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + *pNumberOfSequenceSteps = VL53L0X_SEQUENCESTEP_NUMBER_OF_CHECKS; + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetSequenceStepsInfo( + VL53L0X_SequenceStepId SequenceStepId, + char *pSequenceStepsString) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_get_sequence_steps_info( + SequenceStepId, + pSequenceStepsString); + + LOG_FUNCTION_END(Status); + + return Status; +} + +VL53L0X_Error VL53L0X_SetSequenceStepTimeout(VL53L0X_DEV Dev, + VL53L0X_SequenceStepId SequenceStepId, FixPoint1616_t TimeOutMilliSecs) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + VL53L0X_Error Status1 = VL53L0X_ERROR_NONE; + uint32_t TimeoutMicroSeconds = ((TimeOutMilliSecs * 1000) + 0x8000) + >> 16; + uint32_t MeasurementTimingBudgetMicroSeconds; + FixPoint1616_t OldTimeOutMicroSeconds; + + LOG_FUNCTION_START(""); + + /* Read back the current value in case we need to revert back to this. + */ + Status = get_sequence_step_timeout(Dev, SequenceStepId, + &OldTimeOutMicroSeconds); + + if (Status == VL53L0X_ERROR_NONE) { + Status = set_sequence_step_timeout(Dev, SequenceStepId, + TimeoutMicroSeconds); + } + + if (Status == VL53L0X_ERROR_NONE) { + VL53L0X_GETPARAMETERFIELD(Dev, + MeasurementTimingBudgetMicroSeconds, + MeasurementTimingBudgetMicroSeconds); + + /* At this point we don't know if the requested value is valid, + * therefore proceed to update the entire timing budget and + * if this fails, revert back to the previous value. + */ + Status = VL53L0X_SetMeasurementTimingBudgetMicroSeconds(Dev, + MeasurementTimingBudgetMicroSeconds); + + if (Status != VL53L0X_ERROR_NONE) { + Status1 = set_sequence_step_timeout(Dev, SequenceStepId, + OldTimeOutMicroSeconds); + + if (Status1 == VL53L0X_ERROR_NONE) { + Status1 = + VL53L0X_SetMeasurementTimingBudgetMicroSeconds( + Dev, + MeasurementTimingBudgetMicroSeconds); + } + + Status = Status1; + } + } + + LOG_FUNCTION_END(Status); + + return Status; +} + +VL53L0X_Error VL53L0X_GetSequenceStepTimeout(VL53L0X_DEV Dev, + VL53L0X_SequenceStepId SequenceStepId, + FixPoint1616_t *pTimeOutMilliSecs) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint32_t TimeoutMicroSeconds; + + LOG_FUNCTION_START(""); + + Status = get_sequence_step_timeout(Dev, SequenceStepId, + &TimeoutMicroSeconds); + if (Status == VL53L0X_ERROR_NONE) { + TimeoutMicroSeconds <<= 8; + *pTimeOutMilliSecs = (TimeoutMicroSeconds + 500)/1000; + *pTimeOutMilliSecs <<= 8; + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetInterMeasurementPeriodMilliSeconds(VL53L0X_DEV Dev, + uint32_t InterMeasurementPeriodMilliSeconds) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint16_t osc_calibrate_val; + uint32_t IMPeriodMilliSeconds; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_RdWord(Dev, VL53L0X_REG_OSC_CALIBRATE_VAL, + &osc_calibrate_val); + + if (Status == VL53L0X_ERROR_NONE) { + if (osc_calibrate_val != 0) { + IMPeriodMilliSeconds = + InterMeasurementPeriodMilliSeconds + * osc_calibrate_val; + } else { + IMPeriodMilliSeconds = + InterMeasurementPeriodMilliSeconds; + } + Status = VL53L0X_WrDWord(Dev, + VL53L0X_REG_SYSTEM_INTERMEASUREMENT_PERIOD, + IMPeriodMilliSeconds); + } + + if (Status == VL53L0X_ERROR_NONE) { + VL53L0X_SETPARAMETERFIELD(Dev, + InterMeasurementPeriodMilliSeconds, + InterMeasurementPeriodMilliSeconds); + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetInterMeasurementPeriodMilliSeconds(VL53L0X_DEV Dev, + uint32_t *pInterMeasurementPeriodMilliSeconds) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint16_t osc_calibrate_val; + uint32_t IMPeriodMilliSeconds; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_RdWord(Dev, VL53L0X_REG_OSC_CALIBRATE_VAL, + &osc_calibrate_val); + + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_RdDWord(Dev, + VL53L0X_REG_SYSTEM_INTERMEASUREMENT_PERIOD, + &IMPeriodMilliSeconds); + } + + if (Status == VL53L0X_ERROR_NONE) { + if (osc_calibrate_val != 0) { + *pInterMeasurementPeriodMilliSeconds = + IMPeriodMilliSeconds / osc_calibrate_val; + } + VL53L0X_SETPARAMETERFIELD(Dev, + InterMeasurementPeriodMilliSeconds, + *pInterMeasurementPeriodMilliSeconds); + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetXTalkCompensationEnable(VL53L0X_DEV Dev, + uint8_t XTalkCompensationEnable) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + FixPoint1616_t TempFix1616; + uint16_t LinearityCorrectiveGain; + + LOG_FUNCTION_START(""); + + LinearityCorrectiveGain = PALDevDataGet(Dev, LinearityCorrectiveGain); + + if ((XTalkCompensationEnable == 0) + || (LinearityCorrectiveGain != 1000)) { + TempFix1616 = 0; + } else { + VL53L0X_GETPARAMETERFIELD(Dev, XTalkCompensationRateMegaCps, + TempFix1616); + } + + /* the following register has a format 3.13 */ + Status = VL53L0X_WrWord(Dev, + VL53L0X_REG_CROSSTALK_COMPENSATION_PEAK_RATE_MCPS, + VL53L0X_FIXPOINT1616TOFIXPOINT313(TempFix1616)); + + if (Status == VL53L0X_ERROR_NONE) { + if (XTalkCompensationEnable == 0) { + VL53L0X_SETPARAMETERFIELD(Dev, XTalkCompensationEnable, + 0); + } else { + VL53L0X_SETPARAMETERFIELD(Dev, XTalkCompensationEnable, + 1); + } + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetXTalkCompensationEnable(VL53L0X_DEV Dev, + uint8_t *pXTalkCompensationEnable) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t Temp8; + + LOG_FUNCTION_START(""); + + VL53L0X_GETPARAMETERFIELD(Dev, XTalkCompensationEnable, Temp8); + *pXTalkCompensationEnable = Temp8; + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetXTalkCompensationRateMegaCps(VL53L0X_DEV Dev, + FixPoint1616_t XTalkCompensationRateMegaCps) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t Temp8; + uint16_t LinearityCorrectiveGain; + uint16_t data; + + LOG_FUNCTION_START(""); + + VL53L0X_GETPARAMETERFIELD(Dev, XTalkCompensationEnable, Temp8); + LinearityCorrectiveGain = PALDevDataGet(Dev, LinearityCorrectiveGain); + + if (Temp8 == 0) { /* disabled write only internal value */ + VL53L0X_SETPARAMETERFIELD(Dev, XTalkCompensationRateMegaCps, + XTalkCompensationRateMegaCps); + } else { + /* the following register has a format 3.13 */ + if (LinearityCorrectiveGain == 1000) { + data = VL53L0X_FIXPOINT1616TOFIXPOINT313( + XTalkCompensationRateMegaCps); + } else { + data = 0; + } + + Status = VL53L0X_WrWord(Dev, + VL53L0X_REG_CROSSTALK_COMPENSATION_PEAK_RATE_MCPS, data); + + if (Status == VL53L0X_ERROR_NONE) { + VL53L0X_SETPARAMETERFIELD(Dev, + XTalkCompensationRateMegaCps, + XTalkCompensationRateMegaCps); + } + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetXTalkCompensationRateMegaCps(VL53L0X_DEV Dev, + FixPoint1616_t *pXTalkCompensationRateMegaCps) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint16_t Value; + FixPoint1616_t TempFix1616; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_RdWord(Dev, + VL53L0X_REG_CROSSTALK_COMPENSATION_PEAK_RATE_MCPS, (uint16_t *)&Value); + if (Status == VL53L0X_ERROR_NONE) { + if (Value == 0) { + /* the Xtalk is disabled return value from memory */ + VL53L0X_GETPARAMETERFIELD(Dev, + XTalkCompensationRateMegaCps, TempFix1616); + *pXTalkCompensationRateMegaCps = TempFix1616; + VL53L0X_SETPARAMETERFIELD(Dev, XTalkCompensationEnable, + 0); + } else { + TempFix1616 = VL53L0X_FIXPOINT313TOFIXPOINT1616(Value); + *pXTalkCompensationRateMegaCps = TempFix1616; + VL53L0X_SETPARAMETERFIELD(Dev, + XTalkCompensationRateMegaCps, TempFix1616); + VL53L0X_SETPARAMETERFIELD(Dev, XTalkCompensationEnable, + 1); + } + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetRefCalibration(VL53L0X_DEV Dev, uint8_t VhvSettings, + uint8_t PhaseCal) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_set_ref_calibration(Dev, VhvSettings, PhaseCal); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetRefCalibration(VL53L0X_DEV Dev, uint8_t *pVhvSettings, + uint8_t *pPhaseCal) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_get_ref_calibration(Dev, pVhvSettings, pPhaseCal); + + LOG_FUNCTION_END(Status); + return Status; +} + +/* + * CHECK LIMIT FUNCTIONS + */ + +VL53L0X_Error VL53L0X_GetNumberOfLimitCheck(uint16_t *pNumberOfLimitCheck) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + *pNumberOfLimitCheck = VL53L0X_CHECKENABLE_NUMBER_OF_CHECKS; + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetLimitCheckInfo(VL53L0X_DEV Dev, uint16_t LimitCheckId, + char *pLimitCheckString) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_get_limit_check_info(Dev, LimitCheckId, + pLimitCheckString); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetLimitCheckStatus(VL53L0X_DEV Dev, + uint16_t LimitCheckId, + uint8_t *pLimitCheckStatus) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t Temp8; + + LOG_FUNCTION_START(""); + + if (LimitCheckId >= VL53L0X_CHECKENABLE_NUMBER_OF_CHECKS) { + Status = VL53L0X_ERROR_INVALID_PARAMS; + } else { + + VL53L0X_GETARRAYPARAMETERFIELD(Dev, LimitChecksStatus, + LimitCheckId, Temp8); + + *pLimitCheckStatus = Temp8; + + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetLimitCheckEnable(VL53L0X_DEV Dev, + uint16_t LimitCheckId, + uint8_t LimitCheckEnable) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + FixPoint1616_t TempFix1616 = 0; + uint8_t LimitCheckEnableInt = 0; + uint8_t LimitCheckDisable = 0; + uint8_t Temp8; + + LOG_FUNCTION_START(""); + + if (LimitCheckId >= VL53L0X_CHECKENABLE_NUMBER_OF_CHECKS) { + Status = VL53L0X_ERROR_INVALID_PARAMS; + } else { + if (LimitCheckEnable == 0) { + TempFix1616 = 0; + LimitCheckEnableInt = 0; + LimitCheckDisable = 1; + + } else { + VL53L0X_GETARRAYPARAMETERFIELD(Dev, LimitChecksValue, + LimitCheckId, TempFix1616); + LimitCheckDisable = 0; + /* this to be sure to have either 0 or 1 */ + LimitCheckEnableInt = 1; + } + + switch (LimitCheckId) { + + case VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE: + /* internal computation: */ + VL53L0X_SETARRAYPARAMETERFIELD(Dev, LimitChecksEnable, + VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE, + LimitCheckEnableInt); + + break; + + case VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE: + + Status = VL53L0X_WrWord(Dev, + VL53L0X_REG_FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT, + VL53L0X_FIXPOINT1616TOFIXPOINT97(TempFix1616)); + + break; + + case VL53L0X_CHECKENABLE_SIGNAL_REF_CLIP: + + /* internal computation: */ + VL53L0X_SETARRAYPARAMETERFIELD(Dev, LimitChecksEnable, + VL53L0X_CHECKENABLE_SIGNAL_REF_CLIP, + LimitCheckEnableInt); + + break; + + case VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD: + + /* internal computation: */ + VL53L0X_SETARRAYPARAMETERFIELD(Dev, LimitChecksEnable, + VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD, + LimitCheckEnableInt); + + break; + + case VL53L0X_CHECKENABLE_SIGNAL_RATE_MSRC: + + Temp8 = (uint8_t)(LimitCheckDisable << 1); + Status = VL53L0X_UpdateByte(Dev, + VL53L0X_REG_MSRC_CONFIG_CONTROL, + 0xFE, Temp8); + + break; + + case VL53L0X_CHECKENABLE_SIGNAL_RATE_PRE_RANGE: + + Temp8 = (uint8_t)(LimitCheckDisable << 4); + Status = VL53L0X_UpdateByte(Dev, + VL53L0X_REG_MSRC_CONFIG_CONTROL, + 0xEF, Temp8); + + break; + + + default: + Status = VL53L0X_ERROR_INVALID_PARAMS; + + } + + } + + if (Status == VL53L0X_ERROR_NONE) { + if (LimitCheckEnable == 0) { + VL53L0X_SETARRAYPARAMETERFIELD(Dev, LimitChecksEnable, + LimitCheckId, 0); + } else { + VL53L0X_SETARRAYPARAMETERFIELD(Dev, LimitChecksEnable, + LimitCheckId, 1); + } + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetLimitCheckEnable(VL53L0X_DEV Dev, + uint16_t LimitCheckId, + uint8_t *pLimitCheckEnable) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t Temp8; + + LOG_FUNCTION_START(""); + + if (LimitCheckId >= VL53L0X_CHECKENABLE_NUMBER_OF_CHECKS) { + Status = VL53L0X_ERROR_INVALID_PARAMS; + *pLimitCheckEnable = 0; + } else { + VL53L0X_GETARRAYPARAMETERFIELD(Dev, LimitChecksEnable, + LimitCheckId, Temp8); + *pLimitCheckEnable = Temp8; + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetLimitCheckValue(VL53L0X_DEV Dev, uint16_t LimitCheckId, + FixPoint1616_t LimitCheckValue) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t Temp8; + + LOG_FUNCTION_START(""); + + VL53L0X_GETARRAYPARAMETERFIELD(Dev, LimitChecksEnable, LimitCheckId, + Temp8); + + if (Temp8 == 0) { /* disabled write only internal value */ + VL53L0X_SETARRAYPARAMETERFIELD(Dev, LimitChecksValue, + LimitCheckId, LimitCheckValue); + } else { + + switch (LimitCheckId) { + + case VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE: + /* internal computation: */ + VL53L0X_SETARRAYPARAMETERFIELD(Dev, LimitChecksValue, + VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE, + LimitCheckValue); + break; + + case VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE: + + Status = VL53L0X_WrWord(Dev, + VL53L0X_REG_FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT, + VL53L0X_FIXPOINT1616TOFIXPOINT97( + LimitCheckValue)); + + break; + + case VL53L0X_CHECKENABLE_SIGNAL_REF_CLIP: + + /* internal computation: */ + VL53L0X_SETARRAYPARAMETERFIELD(Dev, LimitChecksValue, + VL53L0X_CHECKENABLE_SIGNAL_REF_CLIP, + LimitCheckValue); + + break; + + case VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD: + + /* internal computation: */ + VL53L0X_SETARRAYPARAMETERFIELD(Dev, LimitChecksValue, + VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD, + LimitCheckValue); + + break; + + case VL53L0X_CHECKENABLE_SIGNAL_RATE_MSRC: + case VL53L0X_CHECKENABLE_SIGNAL_RATE_PRE_RANGE: + + Status = VL53L0X_WrWord(Dev, + VL53L0X_REG_PRE_RANGE_MIN_COUNT_RATE_RTN_LIMIT, + VL53L0X_FIXPOINT1616TOFIXPOINT97( + LimitCheckValue)); + + break; + + default: + Status = VL53L0X_ERROR_INVALID_PARAMS; + + } + + if (Status == VL53L0X_ERROR_NONE) { + VL53L0X_SETARRAYPARAMETERFIELD(Dev, LimitChecksValue, + LimitCheckId, LimitCheckValue); + } + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetLimitCheckValue(VL53L0X_DEV Dev, uint16_t LimitCheckId, + FixPoint1616_t *pLimitCheckValue) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t EnableZeroValue = 0; + uint16_t Temp16; + FixPoint1616_t TempFix1616; + + LOG_FUNCTION_START(""); + + switch (LimitCheckId) { + + case VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE: + /* internal computation: */ + VL53L0X_GETARRAYPARAMETERFIELD(Dev, LimitChecksValue, + VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE, TempFix1616); + EnableZeroValue = 0; + break; + + case VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE: + Status = VL53L0X_RdWord(Dev, + VL53L0X_REG_FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT, + &Temp16); + if (Status == VL53L0X_ERROR_NONE) + TempFix1616 = VL53L0X_FIXPOINT97TOFIXPOINT1616(Temp16); + + + EnableZeroValue = 1; + break; + + case VL53L0X_CHECKENABLE_SIGNAL_REF_CLIP: + /* internal computation: */ + VL53L0X_GETARRAYPARAMETERFIELD(Dev, LimitChecksValue, + VL53L0X_CHECKENABLE_SIGNAL_REF_CLIP, TempFix1616); + EnableZeroValue = 0; + break; + + case VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD: + /* internal computation: */ + VL53L0X_GETARRAYPARAMETERFIELD(Dev, LimitChecksValue, + VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD, + TempFix1616); + EnableZeroValue = 0; + break; + + case VL53L0X_CHECKENABLE_SIGNAL_RATE_MSRC: + case VL53L0X_CHECKENABLE_SIGNAL_RATE_PRE_RANGE: + Status = VL53L0X_RdWord(Dev, + VL53L0X_REG_PRE_RANGE_MIN_COUNT_RATE_RTN_LIMIT, + &Temp16); + if (Status == VL53L0X_ERROR_NONE) + TempFix1616 = VL53L0X_FIXPOINT97TOFIXPOINT1616(Temp16); + + + EnableZeroValue = 0; + break; + + default: + Status = VL53L0X_ERROR_INVALID_PARAMS; + + } + + if (Status == VL53L0X_ERROR_NONE) { + + if (EnableZeroValue == 1) { + + if (TempFix1616 == 0) { + /* disabled: return value from memory */ + VL53L0X_GETARRAYPARAMETERFIELD(Dev, + LimitChecksValue, LimitCheckId, + TempFix1616); + *pLimitCheckValue = TempFix1616; + VL53L0X_SETARRAYPARAMETERFIELD(Dev, + LimitChecksEnable, LimitCheckId, 0); + } else { + *pLimitCheckValue = TempFix1616; + VL53L0X_SETARRAYPARAMETERFIELD(Dev, + LimitChecksValue, LimitCheckId, + TempFix1616); + VL53L0X_SETARRAYPARAMETERFIELD(Dev, + LimitChecksEnable, LimitCheckId, 1); + } + } else { + *pLimitCheckValue = TempFix1616; + } + } + + LOG_FUNCTION_END(Status); + return Status; + +} + +VL53L0X_Error VL53L0X_GetLimitCheckCurrent(VL53L0X_DEV Dev, + uint16_t LimitCheckId, + FixPoint1616_t *pLimitCheckCurrent) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + VL53L0X_RangingMeasurementData_t LastRangeDataBuffer; + + LOG_FUNCTION_START(""); + + if (LimitCheckId >= VL53L0X_CHECKENABLE_NUMBER_OF_CHECKS) { + Status = VL53L0X_ERROR_INVALID_PARAMS; + } else { + switch (LimitCheckId) { + case VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE: + /* Need to run a ranging to have the latest values */ + *pLimitCheckCurrent = PALDevDataGet(Dev, SigmaEstimate); + + break; + + case VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE: + /* Need to run a ranging to have the latest values */ + LastRangeDataBuffer = PALDevDataGet(Dev, + LastRangeMeasure); + *pLimitCheckCurrent = + LastRangeDataBuffer.SignalRateRtnMegaCps; + + break; + + case VL53L0X_CHECKENABLE_SIGNAL_REF_CLIP: + /* Need to run a ranging to have the latest values */ + *pLimitCheckCurrent = PALDevDataGet(Dev, + LastSignalRefMcps); + + break; + + case VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD: + /* Need to run a ranging to have the latest values */ + LastRangeDataBuffer = PALDevDataGet(Dev, + LastRangeMeasure); + *pLimitCheckCurrent = + LastRangeDataBuffer.SignalRateRtnMegaCps; + + break; + + case VL53L0X_CHECKENABLE_SIGNAL_RATE_MSRC: + /* Need to run a ranging to have the latest values */ + LastRangeDataBuffer = PALDevDataGet(Dev, + LastRangeMeasure); + *pLimitCheckCurrent = + LastRangeDataBuffer.SignalRateRtnMegaCps; + + break; + + case VL53L0X_CHECKENABLE_SIGNAL_RATE_PRE_RANGE: + /* Need to run a ranging to have the latest values */ + LastRangeDataBuffer = PALDevDataGet(Dev, + LastRangeMeasure); + *pLimitCheckCurrent = + LastRangeDataBuffer.SignalRateRtnMegaCps; + + break; + + default: + Status = VL53L0X_ERROR_INVALID_PARAMS; + } + } + + LOG_FUNCTION_END(Status); + return Status; + +} + +/* + * WRAPAROUND Check + */ +VL53L0X_Error VL53L0X_SetWrapAroundCheckEnable(VL53L0X_DEV Dev, + uint8_t WrapAroundCheckEnable) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t Byte; + uint8_t WrapAroundCheckEnableInt; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_RdByte(Dev, VL53L0X_REG_SYSTEM_SEQUENCE_CONFIG, &Byte); + if (WrapAroundCheckEnable == 0) { + /* Disable wraparound */ + Byte = Byte & 0x7F; + WrapAroundCheckEnableInt = 0; + } else { + /*Enable wraparound */ + Byte = Byte | 0x80; + WrapAroundCheckEnableInt = 1; + } + + Status = VL53L0X_WrByte(Dev, VL53L0X_REG_SYSTEM_SEQUENCE_CONFIG, Byte); + + if (Status == VL53L0X_ERROR_NONE) { + PALDevDataSet(Dev, SequenceConfig, Byte); + VL53L0X_SETPARAMETERFIELD(Dev, WrapAroundCheckEnable, + WrapAroundCheckEnableInt); + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetWrapAroundCheckEnable(VL53L0X_DEV Dev, + uint8_t *pWrapAroundCheckEnable) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t data; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_RdByte(Dev, VL53L0X_REG_SYSTEM_SEQUENCE_CONFIG, &data); + if (Status == VL53L0X_ERROR_NONE) { + PALDevDataSet(Dev, SequenceConfig, data); + if (data & (0x01 << 7)) + *pWrapAroundCheckEnable = 0x01; + else + *pWrapAroundCheckEnable = 0x00; + } + if (Status == VL53L0X_ERROR_NONE) { + VL53L0X_SETPARAMETERFIELD(Dev, WrapAroundCheckEnable, + *pWrapAroundCheckEnable); + } + + LOG_FUNCTION_END(Status); + return Status; +} + +/* End Group PAL Parameters Functions */ + +/* Group PAL Measurement Functions */ +VL53L0X_Error VL53L0X_PerformSingleMeasurement(VL53L0X_DEV Dev) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + VL53L0X_DeviceModes DeviceMode; + + LOG_FUNCTION_START(""); + + /* Get Current DeviceMode */ + Status = VL53L0X_GetDeviceMode(Dev, &DeviceMode); + + /* Start immediately to run a single ranging measurement in case of + * single ranging or single histogram + */ + if (Status == VL53L0X_ERROR_NONE + && DeviceMode == VL53L0X_DEVICEMODE_SINGLE_RANGING) + Status = VL53L0X_StartMeasurement(Dev); + + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_measurement_poll_for_completion(Dev); + + + /* Change PAL State in case of single ranging or single histogram */ + if (Status == VL53L0X_ERROR_NONE + && DeviceMode == VL53L0X_DEVICEMODE_SINGLE_RANGING) + PALDevDataSet(Dev, PalState, VL53L0X_STATE_IDLE); + + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_PerformSingleHistogramMeasurement(VL53L0X_DEV Dev, + VL53L0X_HistogramMeasurementData_t *pHistogramMeasurementData) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NOT_IMPLEMENTED; + + LOG_FUNCTION_START(""); + + /* not implemented on VL53L0X */ + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_PerformRefCalibration(VL53L0X_DEV Dev, + uint8_t *pVhvSettings, + uint8_t *pPhaseCal) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_perform_ref_calibration(Dev, pVhvSettings, + pPhaseCal, 1); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_PerformXTalkMeasurement(VL53L0X_DEV Dev, + uint32_t TimeoutMs, FixPoint1616_t *pXtalkPerSpad, + uint8_t *pAmbientTooHigh) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NOT_IMPLEMENTED; + + LOG_FUNCTION_START(""); + + /* not implemented on VL53L0X */ + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_PerformXTalkCalibration(VL53L0X_DEV Dev, + FixPoint1616_t XTalkCalDistance, + FixPoint1616_t *pXTalkCompensationRateMegaCps) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_perform_xtalk_calibration(Dev, XTalkCalDistance, + pXTalkCompensationRateMegaCps); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_PerformOffsetCalibration(VL53L0X_DEV Dev, + FixPoint1616_t CalDistanceMilliMeter, int32_t *pOffsetMicroMeter) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_perform_offset_calibration(Dev, CalDistanceMilliMeter, + pOffsetMicroMeter); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_CheckAndLoadInterruptSettings(VL53L0X_DEV Dev, + uint8_t StartNotStopFlag) +{ + uint8_t InterruptConfig; + FixPoint1616_t ThresholdLow; + FixPoint1616_t ThresholdHigh; + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + InterruptConfig = VL53L0X_GETDEVICESPECIFICPARAMETER(Dev, + Pin0GpioFunctionality); + + switch (InterruptConfig) { + case VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_LOW: + Status = VL53L0X_GetInterruptThresholds(Dev, + VL53L0X_DEVICEMODE_CONTINUOUS_RANGING, + &ThresholdLow, &ThresholdHigh); + + if ((ThresholdLow > 255*65536) && + (Status == VL53L0X_ERROR_NONE)) { + + if (StartNotStopFlag != 0) { + Status = VL53L0X_load_tuning_settings(Dev, + InterruptThresholdSettings); + } else { + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x04); + Status |= VL53L0X_WrByte(Dev, 0x70, 0x00); + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x00); + Status |= VL53L0X_WrByte(Dev, 0x80, 0x00); + } + } + break; + case VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_HIGH: + Status = VL53L0X_GetInterruptThresholds(Dev, + VL53L0X_DEVICEMODE_CONTINUOUS_RANGING, + &ThresholdLow, &ThresholdHigh); + + if ((ThresholdHigh > 0) && + (Status == VL53L0X_ERROR_NONE)) { + + if (StartNotStopFlag != 0) { + Status = VL53L0X_load_tuning_settings(Dev, + InterruptThresholdSettings); + } else { + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x04); + Status |= VL53L0X_WrByte(Dev, 0x70, 0x00); + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x00); + Status |= VL53L0X_WrByte(Dev, 0x80, 0x00); + } + } + break; + case VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_OUT: + Status = VL53L0X_GetInterruptThresholds(Dev, + VL53L0X_DEVICEMODE_CONTINUOUS_RANGING, + &ThresholdLow, &ThresholdHigh); + + if (Status == VL53L0X_ERROR_NONE) { + if (StartNotStopFlag != 0) { + Status = VL53L0X_load_tuning_settings(Dev, + InterruptThresholdSettings); + } else { + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x04); + Status |= VL53L0X_WrByte(Dev, 0x70, 0x00); + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x00); + Status |= VL53L0X_WrByte(Dev, 0x80, 0x00); + } + } + break; + } + + LOG_FUNCTION_END(Status); + return Status; +} + + +VL53L0X_Error VL53L0X_StartMeasurement(VL53L0X_DEV Dev) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + VL53L0X_DeviceModes DeviceMode; + uint8_t Byte; + uint8_t StartStopByte = VL53L0X_REG_SYSRANGE_MODE_START_STOP; + uint32_t LoopNb; + + LOG_FUNCTION_START(""); + + /* Get Current DeviceMode */ + VL53L0X_GetDeviceMode(Dev, &DeviceMode); + + Status = VL53L0X_WrByte(Dev, 0x80, 0x01); + Status = VL53L0X_WrByte(Dev, 0xFF, 0x01); + Status = VL53L0X_WrByte(Dev, 0x00, 0x00); + Status = VL53L0X_WrByte(Dev, 0x91, PALDevDataGet(Dev, StopVariable)); + Status = VL53L0X_WrByte(Dev, 0x00, 0x01); + Status = VL53L0X_WrByte(Dev, 0xFF, 0x00); + Status = VL53L0X_WrByte(Dev, 0x80, 0x00); + + switch (DeviceMode) { + case VL53L0X_DEVICEMODE_SINGLE_RANGING: + Status = VL53L0X_WrByte(Dev, VL53L0X_REG_SYSRANGE_START, 0x01); + + Byte = StartStopByte; + if (Status == VL53L0X_ERROR_NONE) { + /* Wait until start bit has been cleared */ + LoopNb = 0; + do { + if (LoopNb > 0) + Status = VL53L0X_RdByte(Dev, + VL53L0X_REG_SYSRANGE_START, &Byte); + LoopNb = LoopNb + 1; + } while (((Byte & StartStopByte) == StartStopByte) + && (Status == VL53L0X_ERROR_NONE) + && (LoopNb < VL53L0X_DEFAULT_MAX_LOOP)); + + if (LoopNb >= VL53L0X_DEFAULT_MAX_LOOP) + Status = VL53L0X_ERROR_TIME_OUT; + + } + + break; + case VL53L0X_DEVICEMODE_CONTINUOUS_RANGING: + /* Back-to-back mode */ + + /* Check if need to apply interrupt settings */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_CheckAndLoadInterruptSettings(Dev, 1); + + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_SYSRANGE_START, + VL53L0X_REG_SYSRANGE_MODE_BACKTOBACK); + if (Status == VL53L0X_ERROR_NONE) { + /* Set PAL State to Running */ + PALDevDataSet(Dev, PalState, VL53L0X_STATE_RUNNING); + } + break; + case VL53L0X_DEVICEMODE_CONTINUOUS_TIMED_RANGING: + /* Continuous mode */ + /* Check if need to apply interrupt settings */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_CheckAndLoadInterruptSettings(Dev, 1); + + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_SYSRANGE_START, + VL53L0X_REG_SYSRANGE_MODE_TIMED); + + if (Status == VL53L0X_ERROR_NONE) { + /* Set PAL State to Running */ + PALDevDataSet(Dev, PalState, VL53L0X_STATE_RUNNING); + } + break; + default: + /* Selected mode not supported */ + Status = VL53L0X_ERROR_MODE_NOT_SUPPORTED; + } + + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_StopMeasurement(VL53L0X_DEV Dev) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_WrByte(Dev, VL53L0X_REG_SYSRANGE_START, + VL53L0X_REG_SYSRANGE_MODE_SINGLESHOT); + + Status = VL53L0X_WrByte(Dev, 0xFF, 0x01); + Status = VL53L0X_WrByte(Dev, 0x00, 0x00); + Status = VL53L0X_WrByte(Dev, 0x91, 0x00); + Status = VL53L0X_WrByte(Dev, 0x00, 0x01); + Status = VL53L0X_WrByte(Dev, 0xFF, 0x00); + + if (Status == VL53L0X_ERROR_NONE) { + /* Set PAL State to Idle */ + PALDevDataSet(Dev, PalState, VL53L0X_STATE_IDLE); + } + + /* Check if need to apply interrupt settings */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_CheckAndLoadInterruptSettings(Dev, 0); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetMeasurementDataReady(VL53L0X_DEV Dev, + uint8_t *pMeasurementDataReady) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t SysRangeStatusRegister; + uint8_t InterruptConfig; + uint32_t InterruptMask; + + LOG_FUNCTION_START(""); + + InterruptConfig = VL53L0X_GETDEVICESPECIFICPARAMETER(Dev, + Pin0GpioFunctionality); + + if (InterruptConfig == + VL53L0X_REG_SYSTEM_INTERRUPT_GPIO_NEW_SAMPLE_READY) { + Status = VL53L0X_GetInterruptMaskStatus(Dev, &InterruptMask); + if (InterruptMask == + VL53L0X_REG_SYSTEM_INTERRUPT_GPIO_NEW_SAMPLE_READY) + *pMeasurementDataReady = 1; + else + *pMeasurementDataReady = 0; + } else { + Status = VL53L0X_RdByte(Dev, VL53L0X_REG_RESULT_RANGE_STATUS, + &SysRangeStatusRegister); + if (Status == VL53L0X_ERROR_NONE) { + if (SysRangeStatusRegister & 0x01) + *pMeasurementDataReady = 1; + else + *pMeasurementDataReady = 0; + } + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_WaitDeviceReadyForNewMeasurement(VL53L0X_DEV Dev, + uint32_t MaxLoop) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NOT_IMPLEMENTED; + + LOG_FUNCTION_START(""); + + /* not implemented for VL53L0X */ + + LOG_FUNCTION_END(Status); + return Status; +} + + +VL53L0X_Error VL53L0X_GetRangingMeasurementData(VL53L0X_DEV Dev, + VL53L0X_RangingMeasurementData_t *pRangingMeasurementData) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t DeviceRangeStatus; + uint8_t RangeFractionalEnable; + uint8_t PalRangeStatus; + uint8_t XTalkCompensationEnable; + uint16_t AmbientRate; + FixPoint1616_t SignalRate; + uint16_t XTalkCompensationRateMegaCps; + uint16_t EffectiveSpadRtnCount; + uint16_t tmpuint16; + uint16_t XtalkRangeMilliMeter; + uint16_t LinearityCorrectiveGain; + uint8_t localBuffer[12]; + VL53L0X_RangingMeasurementData_t LastRangeDataBuffer; + + LOG_FUNCTION_START(""); + + /* + * use multi read even if some registers are not useful, result will + * be more efficient + * start reading at 0x14 dec20 + * end reading at 0x21 dec33 total 14 bytes to read + */ + Status = VL53L0X_ReadMulti(Dev, 0x14, localBuffer, 12); + + if (Status == VL53L0X_ERROR_NONE) { + + pRangingMeasurementData->ZoneId = 0; /* Only one zone */ + pRangingMeasurementData->TimeStamp = 0; /* Not Implemented */ + + tmpuint16 = VL53L0X_MAKEUINT16(localBuffer[11], + localBuffer[10]); + /* cut1.1 if SYSTEM__RANGE_CONFIG if 1 range is 2bits fractional + *(format 11.2) else no fractional + */ + + pRangingMeasurementData->MeasurementTimeUsec = 0; + + + SignalRate = VL53L0X_FIXPOINT97TOFIXPOINT1616( + VL53L0X_MAKEUINT16(localBuffer[7], localBuffer[6])); + /* peak_signal_count_rate_rtn_mcps */ + pRangingMeasurementData->SignalRateRtnMegaCps = SignalRate; + + AmbientRate = VL53L0X_MAKEUINT16(localBuffer[9], + localBuffer[8]); + pRangingMeasurementData->AmbientRateRtnMegaCps = + VL53L0X_FIXPOINT97TOFIXPOINT1616(AmbientRate); + + EffectiveSpadRtnCount = VL53L0X_MAKEUINT16(localBuffer[3], + localBuffer[2]); + /* EffectiveSpadRtnCount is 8.8 format */ + pRangingMeasurementData->EffectiveSpadRtnCount = + EffectiveSpadRtnCount; + + DeviceRangeStatus = localBuffer[0]; + + /* Get Linearity Corrective Gain */ + LinearityCorrectiveGain = PALDevDataGet(Dev, + LinearityCorrectiveGain); + + /* Get ranging configuration */ + RangeFractionalEnable = PALDevDataGet(Dev, + RangeFractionalEnable); + + if (LinearityCorrectiveGain != 1000) { + + tmpuint16 = (uint16_t)((LinearityCorrectiveGain + * tmpuint16 + 500) / 1000); + + /* Implement Xtalk */ + VL53L0X_GETPARAMETERFIELD(Dev, + XTalkCompensationRateMegaCps, + XTalkCompensationRateMegaCps); + VL53L0X_GETPARAMETERFIELD(Dev, XTalkCompensationEnable, + XTalkCompensationEnable); + + if (XTalkCompensationEnable) { + + if ((SignalRate + - ((XTalkCompensationRateMegaCps + * EffectiveSpadRtnCount) >> 8)) + <= 0) { + if (RangeFractionalEnable) + XtalkRangeMilliMeter = 8888; + else + XtalkRangeMilliMeter = 8888 + << 2; + } else { + XtalkRangeMilliMeter = + (tmpuint16 * SignalRate) + / (SignalRate + - ((XTalkCompensationRateMegaCps + * EffectiveSpadRtnCount) + >> 8)); + } + + tmpuint16 = XtalkRangeMilliMeter; + } + + } + + if (RangeFractionalEnable) { + pRangingMeasurementData->RangeMilliMeter = + (uint16_t)((tmpuint16) >> 2); + pRangingMeasurementData->RangeFractionalPart = + (uint8_t)((tmpuint16 & 0x03) << 6); + } else { + pRangingMeasurementData->RangeMilliMeter = tmpuint16; + pRangingMeasurementData->RangeFractionalPart = 0; + } + + /* + * For a standard definition of RangeStatus, this should + * return 0 in case of good result after a ranging + * The range status depends on the device so call a device + * specific function to obtain the right Status. + */ + Status |= VL53L0X_get_pal_range_status(Dev, DeviceRangeStatus, + SignalRate, EffectiveSpadRtnCount, + pRangingMeasurementData, &PalRangeStatus); + + if (Status == VL53L0X_ERROR_NONE) + pRangingMeasurementData->RangeStatus = PalRangeStatus; + + } + + if (Status == VL53L0X_ERROR_NONE) { + /* Copy last read data into Dev buffer */ + LastRangeDataBuffer = PALDevDataGet(Dev, LastRangeMeasure); + + LastRangeDataBuffer.RangeMilliMeter = + pRangingMeasurementData->RangeMilliMeter; + LastRangeDataBuffer.RangeFractionalPart = + pRangingMeasurementData->RangeFractionalPart; + LastRangeDataBuffer.RangeDMaxMilliMeter = + pRangingMeasurementData->RangeDMaxMilliMeter; + LastRangeDataBuffer.MeasurementTimeUsec = + pRangingMeasurementData->MeasurementTimeUsec; + LastRangeDataBuffer.SignalRateRtnMegaCps = + pRangingMeasurementData->SignalRateRtnMegaCps; + LastRangeDataBuffer.AmbientRateRtnMegaCps = + pRangingMeasurementData->AmbientRateRtnMegaCps; + LastRangeDataBuffer.EffectiveSpadRtnCount = + pRangingMeasurementData->EffectiveSpadRtnCount; + LastRangeDataBuffer.RangeStatus = + pRangingMeasurementData->RangeStatus; + + PALDevDataSet(Dev, LastRangeMeasure, LastRangeDataBuffer); + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetMeasurementRefSignal(VL53L0X_DEV Dev, + FixPoint1616_t *pMeasurementRefSignal) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t SignalRefClipLimitCheckEnable = 0; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_GetLimitCheckEnable(Dev, + VL53L0X_CHECKENABLE_SIGNAL_REF_CLIP, + &SignalRefClipLimitCheckEnable); + if (SignalRefClipLimitCheckEnable != 0) + *pMeasurementRefSignal = PALDevDataGet(Dev, LastSignalRefMcps); + else + Status = VL53L0X_ERROR_INVALID_COMMAND; + + LOG_FUNCTION_END(Status); + + return Status; +} + +VL53L0X_Error VL53L0X_GetHistogramMeasurementData(VL53L0X_DEV Dev, + VL53L0X_HistogramMeasurementData_t *pHistogramMeasurementData) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NOT_IMPLEMENTED; + + LOG_FUNCTION_START(""); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_PerformSingleRangingMeasurement(VL53L0X_DEV Dev, + VL53L0X_RangingMeasurementData_t *pRangingMeasurementData) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + /* This function will do a complete single ranging + * Here we fix the mode! + */ + Status = VL53L0X_SetDeviceMode(Dev, VL53L0X_DEVICEMODE_SINGLE_RANGING); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_PerformSingleMeasurement(Dev); + + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_GetRangingMeasurementData(Dev, + pRangingMeasurementData); + + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_ClearInterruptMask(Dev, 0); + + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetNumberOfROIZones(VL53L0X_DEV Dev, + uint8_t NumberOfROIZones) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + if (NumberOfROIZones != 1) + Status = VL53L0X_ERROR_INVALID_PARAMS; + + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetNumberOfROIZones(VL53L0X_DEV Dev, + uint8_t *pNumberOfROIZones) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + *pNumberOfROIZones = 1; + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetMaxNumberOfROIZones(VL53L0X_DEV Dev, + uint8_t *pMaxNumberOfROIZones) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + *pMaxNumberOfROIZones = 1; + + LOG_FUNCTION_END(Status); + return Status; +} + +/* End Group PAL Measurement Functions */ + +VL53L0X_Error VL53L0X_SetGpioConfig(VL53L0X_DEV Dev, uint8_t Pin, + VL53L0X_DeviceModes DeviceMode, VL53L0X_GpioFunctionality Functionality, + VL53L0X_InterruptPolarity Polarity) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t data; + + LOG_FUNCTION_START(""); + + if (Pin != 0) { + Status = VL53L0X_ERROR_GPIO_NOT_EXISTING; + } else if (DeviceMode == VL53L0X_DEVICEMODE_GPIO_DRIVE) { + if (Polarity == VL53L0X_INTERRUPTPOLARITY_LOW) + data = 0x10; + else + data = 1; + + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_GPIO_HV_MUX_ACTIVE_HIGH, data); + + } else if (DeviceMode == VL53L0X_DEVICEMODE_GPIO_OSC) { + + Status |= VL53L0X_WrByte(Dev, 0xff, 0x01); + Status |= VL53L0X_WrByte(Dev, 0x00, 0x00); + + Status |= VL53L0X_WrByte(Dev, 0xff, 0x00); + Status |= VL53L0X_WrByte(Dev, 0x80, 0x01); + Status |= VL53L0X_WrByte(Dev, 0x85, 0x02); + + Status |= VL53L0X_WrByte(Dev, 0xff, 0x04); + Status |= VL53L0X_WrByte(Dev, 0xcd, 0x00); + Status |= VL53L0X_WrByte(Dev, 0xcc, 0x11); + + Status |= VL53L0X_WrByte(Dev, 0xff, 0x07); + Status |= VL53L0X_WrByte(Dev, 0xbe, 0x00); + + Status |= VL53L0X_WrByte(Dev, 0xff, 0x06); + Status |= VL53L0X_WrByte(Dev, 0xcc, 0x09); + + Status |= VL53L0X_WrByte(Dev, 0xff, 0x00); + Status |= VL53L0X_WrByte(Dev, 0xff, 0x01); + Status |= VL53L0X_WrByte(Dev, 0x00, 0x00); + + } else { + + if (Status == VL53L0X_ERROR_NONE) { + switch (Functionality) { + case VL53L0X_GPIOFUNCTIONALITY_OFF: + data = 0x00; + break; + case VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_LOW: + data = 0x01; + break; + case VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_HIGH: + data = 0x02; + break; + case VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_OUT: + data = 0x03; + break; + case VL53L0X_GPIOFUNCTIONALITY_NEW_MEASURE_READY: + data = 0x04; + break; + default: + Status = + VL53L0X_ERROR_GPIO_FUNCTIONALITY_NOT_SUPPORTED; + } + } + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_SYSTEM_INTERRUPT_CONFIG_GPIO, data); + + if (Status == VL53L0X_ERROR_NONE) { + if (Polarity == VL53L0X_INTERRUPTPOLARITY_LOW) + data = 0; + else + data = (uint8_t)(1 << 4); + + Status = VL53L0X_UpdateByte(Dev, + VL53L0X_REG_GPIO_HV_MUX_ACTIVE_HIGH, 0xEF, data); + } + + if (Status == VL53L0X_ERROR_NONE) + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, + Pin0GpioFunctionality, Functionality); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_ClearInterruptMask(Dev, 0); + + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetGpioConfig(VL53L0X_DEV Dev, uint8_t Pin, + VL53L0X_DeviceModes *pDeviceMode, + VL53L0X_GpioFunctionality *pFunctionality, + VL53L0X_InterruptPolarity *pPolarity) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + VL53L0X_GpioFunctionality GpioFunctionality; + uint8_t data; + + LOG_FUNCTION_START(""); + + /* pDeviceMode not managed by Ewok it return the current mode */ + + Status = VL53L0X_GetDeviceMode(Dev, pDeviceMode); + + if (Status == VL53L0X_ERROR_NONE) { + if (Pin != 0) { + Status = VL53L0X_ERROR_GPIO_NOT_EXISTING; + } else { + Status = VL53L0X_RdByte(Dev, + VL53L0X_REG_SYSTEM_INTERRUPT_CONFIG_GPIO, &data); + } + } + + if (Status == VL53L0X_ERROR_NONE) { + switch (data & 0x07) { + case 0x00: + GpioFunctionality = VL53L0X_GPIOFUNCTIONALITY_OFF; + break; + case 0x01: + GpioFunctionality = + VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_LOW; + break; + case 0x02: + GpioFunctionality = + VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_HIGH; + break; + case 0x03: + GpioFunctionality = + VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_OUT; + break; + case 0x04: + GpioFunctionality = + VL53L0X_GPIOFUNCTIONALITY_NEW_MEASURE_READY; + break; + default: + Status = VL53L0X_ERROR_GPIO_FUNCTIONALITY_NOT_SUPPORTED; + } + } + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_RdByte(Dev, + VL53L0X_REG_GPIO_HV_MUX_ACTIVE_HIGH, + &data); + + if (Status == VL53L0X_ERROR_NONE) { + if ((data & (uint8_t)(1 << 4)) == 0) + *pPolarity = VL53L0X_INTERRUPTPOLARITY_LOW; + else + *pPolarity = VL53L0X_INTERRUPTPOLARITY_HIGH; + } + + if (Status == VL53L0X_ERROR_NONE) { + *pFunctionality = GpioFunctionality; + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, Pin0GpioFunctionality, + GpioFunctionality); + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetInterruptThresholds(VL53L0X_DEV Dev, + VL53L0X_DeviceModes DeviceMode, FixPoint1616_t ThresholdLow, + FixPoint1616_t ThresholdHigh) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint16_t Threshold16; + + LOG_FUNCTION_START(""); + + /* no dependency on DeviceMode for Ewok */ + /* Need to divide by 2 because the FW will apply a x2 */ + Threshold16 = (uint16_t)((ThresholdLow >> 17) & 0x00fff); + Status = VL53L0X_WrWord(Dev, VL53L0X_REG_SYSTEM_THRESH_LOW, + Threshold16); + + if (Status == VL53L0X_ERROR_NONE) { + /* Need to divide by 2 because the FW will apply a x2 */ + Threshold16 = (uint16_t)((ThresholdHigh >> 17) & 0x00fff); + Status = VL53L0X_WrWord(Dev, VL53L0X_REG_SYSTEM_THRESH_HIGH, + Threshold16); + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetInterruptThresholds(VL53L0X_DEV Dev, + VL53L0X_DeviceModes DeviceMode, FixPoint1616_t *pThresholdLow, + FixPoint1616_t *pThresholdHigh) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint16_t Threshold16; + + LOG_FUNCTION_START(""); + + /* no dependency on DeviceMode for Ewok */ + + Status = VL53L0X_RdWord(Dev, VL53L0X_REG_SYSTEM_THRESH_LOW, + &Threshold16); + /* Need to multiply by 2 because the FW will apply a x2 */ + *pThresholdLow = (FixPoint1616_t)((0x00fff & Threshold16) << 17); + + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_RdWord(Dev, VL53L0X_REG_SYSTEM_THRESH_HIGH, + &Threshold16); + /* Need to multiply by 2 because the FW will apply a x2 */ + *pThresholdHigh = + (FixPoint1616_t)((0x00fff & Threshold16) << 17); + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetStopCompletedStatus(VL53L0X_DEV Dev, + uint32_t *pStopStatus) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t Byte = 0; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_WrByte(Dev, 0xFF, 0x01); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_RdByte(Dev, 0x04, &Byte); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_WrByte(Dev, 0xFF, 0x0); + + *pStopStatus = Byte; + + if (Byte == 0) { + Status = VL53L0X_WrByte(Dev, 0x80, 0x01); + Status = VL53L0X_WrByte(Dev, 0xFF, 0x01); + Status = VL53L0X_WrByte(Dev, 0x00, 0x00); + Status = VL53L0X_WrByte(Dev, 0x91, + PALDevDataGet(Dev, StopVariable)); + Status = VL53L0X_WrByte(Dev, 0x00, 0x01); + Status = VL53L0X_WrByte(Dev, 0xFF, 0x00); + Status = VL53L0X_WrByte(Dev, 0x80, 0x00); + } + + LOG_FUNCTION_END(Status); + return Status; +} + +/* Group PAL Interrupt Functions */ +VL53L0X_Error VL53L0X_ClearInterruptMask(VL53L0X_DEV Dev, + uint32_t InterruptMask) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t LoopCount; + uint8_t Byte; + + LOG_FUNCTION_START(""); + + /* clear bit 0 range interrupt, bit 1 error interrupt */ + LoopCount = 0; + do { + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_SYSTEM_INTERRUPT_CLEAR, 0x01); + Status |= VL53L0X_WrByte(Dev, + VL53L0X_REG_SYSTEM_INTERRUPT_CLEAR, 0x00); + Status |= VL53L0X_RdByte(Dev, + VL53L0X_REG_RESULT_INTERRUPT_STATUS, &Byte); + LoopCount++; + } while (((Byte & 0x07) != 0x00) + && (LoopCount < 3) + && (Status == VL53L0X_ERROR_NONE)); + + + if (LoopCount >= 3) + Status = VL53L0X_ERROR_INTERRUPT_NOT_CLEARED; + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetInterruptMaskStatus(VL53L0X_DEV Dev, + uint32_t *pInterruptMaskStatus) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t Byte; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_RdByte(Dev, VL53L0X_REG_RESULT_INTERRUPT_STATUS, + &Byte); + *pInterruptMaskStatus = Byte & 0x07; + + if (Byte & 0x18) + Status = VL53L0X_ERROR_RANGE_ERROR; + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_EnableInterruptMask(VL53L0X_DEV Dev, + uint32_t InterruptMask) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NOT_IMPLEMENTED; + + LOG_FUNCTION_START(""); + + /* not implemented for VL53L0X */ + + LOG_FUNCTION_END(Status); + return Status; +} + +/* End Group PAL Interrupt Functions */ + +/* Group SPAD functions */ + +VL53L0X_Error VL53L0X_SetSpadAmbientDamperThreshold(VL53L0X_DEV Dev, + uint16_t SpadAmbientDamperThreshold) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_WrByte(Dev, 0xFF, 0x01); + Status |= VL53L0X_WrWord(Dev, 0x40, SpadAmbientDamperThreshold); + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x00); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetSpadAmbientDamperThreshold(VL53L0X_DEV Dev, + uint16_t *pSpadAmbientDamperThreshold) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_WrByte(Dev, 0xFF, 0x01); + Status |= VL53L0X_RdWord(Dev, 0x40, pSpadAmbientDamperThreshold); + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x00); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_SetSpadAmbientDamperFactor(VL53L0X_DEV Dev, + uint16_t SpadAmbientDamperFactor) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t Byte; + + LOG_FUNCTION_START(""); + + Byte = (uint8_t)(SpadAmbientDamperFactor & 0x00FF); + + Status = VL53L0X_WrByte(Dev, 0xFF, 0x01); + Status |= VL53L0X_WrByte(Dev, 0x42, Byte); + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x00); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_GetSpadAmbientDamperFactor(VL53L0X_DEV Dev, + uint16_t *pSpadAmbientDamperFactor) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t Byte; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_WrByte(Dev, 0xFF, 0x01); + Status |= VL53L0X_RdByte(Dev, 0x42, &Byte); + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x00); + *pSpadAmbientDamperFactor = (uint16_t)Byte; + + LOG_FUNCTION_END(Status); + return Status; +} + +/* END Group SPAD functions */ + +/***************************************************************************** + * Internal functions + *****************************************************************************/ + +VL53L0X_Error VL53L0X_SetReferenceSpads(VL53L0X_DEV Dev, uint32_t count, + uint8_t isApertureSpads) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_set_reference_spads(Dev, count, isApertureSpads); + + LOG_FUNCTION_END(Status); + + return Status; +} + +VL53L0X_Error VL53L0X_GetReferenceSpads(VL53L0X_DEV Dev, uint32_t *pSpadCount, + uint8_t *pIsApertureSpads) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_get_reference_spads(Dev, pSpadCount, pIsApertureSpads); + + LOG_FUNCTION_END(Status); + + return Status; +} + +VL53L0X_Error VL53L0X_PerformRefSpadManagement(VL53L0X_DEV Dev, + uint32_t *refSpadCount, uint8_t *isApertureSpads) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_perform_ref_spad_management(Dev, refSpadCount, + isApertureSpads); + + LOG_FUNCTION_END(Status); + + return Status; +} diff --git a/lib/vl53l0x/vl53l0x_api.h b/lib/vl53l0x/vl53l0x_api.h new file mode 100644 index 0000000..0521690 --- /dev/null +++ b/lib/vl53l0x/vl53l0x_api.h @@ -0,0 +1,1926 @@ +/******************************************************************************* + * Copyright 2016, STMicroelectronics International N.V. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of STMicroelectronics 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, FITNESS FOR A PARTICULAR PURPOSE, AND + NON-INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS ARE DISCLAIMED. + IN NO EVENT SHALL STMICROELECTRONICS INTERNATIONAL N.V. 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. + *****************************************************************************/ + +#ifndef _VL53L0X_API_H_ +#define _VL53L0X_API_H_ + +#include "vl53l0x_api_strings.h" +#include "vl53l0x_def.h" +#include "vl53l0x_platform.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef _MSC_VER +# ifdef VL53L0X_API_EXPORTS +# define VL53L0X_API __declspec(dllexport) +# else +# define VL53L0X_API +# endif +#else +# define VL53L0X_API +#endif + +/** @defgroup VL53L0X_cut11_group VL53L0X cut1.1 Function Definition + * @brief VL53L0X cut1.1 Function Definition + * @{ + */ + +/** @defgroup VL53L0X_general_group VL53L0X General Functions + * @brief General functions and definitions + * @{ + */ + +/** + * @brief Return the VL53L0X PAL Implementation Version + * + * @note This function doesn't access to the device + * + * @param pVersion Pointer to current PAL Implementation Version + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetVersion(VL53L0X_Version_t *pVersion); + +/** + * @brief Return the PAL Specification Version used for the current + * implementation. + * + * @note This function doesn't access to the device + * + * @param pPalSpecVersion Pointer to current PAL Specification Version + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetPalSpecVersion( + VL53L0X_Version_t *pPalSpecVersion); + +/** + * @brief Reads the Product Revision for a for given Device + * This function can be used to distinguish cut1.0 from cut1.1. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pProductRevisionMajor Pointer to Product Revision Major + * for a given Device + * @param pProductRevisionMinor Pointer to Product Revision Minor + * for a given Device + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetProductRevision(VL53L0X_DEV Dev, + uint8_t *pProductRevisionMajor, uint8_t *pProductRevisionMinor); + +/** + * @brief Reads the Device information for given Device + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pVL53L0X_DeviceInfo Pointer to current device info for a given + * Device + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetDeviceInfo(VL53L0X_DEV Dev, + VL53L0X_DeviceInfo_t *pVL53L0X_DeviceInfo); + +/** + * @brief Read current status of the error register for the selected device + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pDeviceErrorStatus Pointer to current error code of the device + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetDeviceErrorStatus(VL53L0X_DEV Dev, + VL53L0X_DeviceError * pDeviceErrorStatus); + +/** + * @brief Human readable Range Status string for a given RangeStatus + * + * @note This function doesn't access to the device + * + * @param RangeStatus The RangeStatus code as stored on + * @a VL53L0X_RangingMeasurementData_t + * @param pRangeStatusString The returned RangeStatus string. + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetRangeStatusString(uint8_t RangeStatus, + char *pRangeStatusString); + +/** + * @brief Human readable error string for a given Error Code + * + * @note This function doesn't access to the device + * + * @param ErrorCode The error code as stored on ::VL53L0X_DeviceError + * @param pDeviceErrorString The error string corresponding to the ErrorCode + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetDeviceErrorString( + VL53L0X_DeviceError ErrorCode, char *pDeviceErrorString); + +/** + * @brief Human readable error string for current PAL error status + * + * @note This function doesn't access to the device + * + * @param PalErrorCode The error code as stored on @a VL53L0X_Error + * @param pPalErrorString The error string corresponding to the + * PalErrorCode + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetPalErrorString(VL53L0X_Error PalErrorCode, + char *pPalErrorString); + +/** + * @brief Human readable PAL State string + * + * @note This function doesn't access to the device + * + * @param PalStateCode The State code as stored on @a VL53L0X_State + * @param pPalStateString The State string corresponding to the + * PalStateCode + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetPalStateString(VL53L0X_State PalStateCode, + char *pPalStateString); + +/** + * @brief Reads the internal state of the PAL for a given Device + * + * @note This function doesn't access to the device + * + * @param Dev Device Handle + * @param pPalState Pointer to current state of the PAL for a + * given Device + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetPalState(VL53L0X_DEV Dev, + VL53L0X_State * pPalState); + +/** + * @brief Set the power mode for a given Device + * The power mode can be Standby or Idle. Different level of both Standby and + * Idle can exists. + * This function should not be used when device is in Ranging state. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param PowerMode The value of the power mode to set. + * see ::VL53L0X_PowerModes + * Valid values are: + * VL53L0X_POWERMODE_STANDBY_LEVEL1, + * VL53L0X_POWERMODE_IDLE_LEVEL1 + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_MODE_NOT_SUPPORTED This error occurs when PowerMode + * is not in the supported list + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetPowerMode(VL53L0X_DEV Dev, + VL53L0X_PowerModes PowerMode); + +/** + * @brief Get the power mode for a given Device + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pPowerMode Pointer to the current value of the power + * mode. see ::VL53L0X_PowerModes + * Valid values are: + * VL53L0X_POWERMODE_STANDBY_LEVEL1, + * VL53L0X_POWERMODE_IDLE_LEVEL1 + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetPowerMode(VL53L0X_DEV Dev, + VL53L0X_PowerModes * pPowerMode); + +/** + * Set or over-hide part to part calibration offset + * \sa VL53L0X_DataInit() VL53L0X_GetOffsetCalibrationDataMicroMeter() + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param OffsetCalibrationDataMicroMeter Offset (microns) + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetOffsetCalibrationDataMicroMeter( + VL53L0X_DEV Dev, int32_t OffsetCalibrationDataMicroMeter); + +/** + * @brief Get part to part calibration offset + * + * @par Function Description + * Should only be used after a successful call to @a VL53L0X_DataInit to backup + * device NVM value + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pOffsetCalibrationDataMicroMeter Return part to part + * calibration offset from device (microns) + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetOffsetCalibrationDataMicroMeter( + VL53L0X_DEV Dev, int32_t *pOffsetCalibrationDataMicroMeter); + +/** + * Set the linearity corrective gain + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param LinearityCorrectiveGain Linearity corrective + * gain in x1000 + * if value is 1000 then no modification is applied. + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetLinearityCorrectiveGain(VL53L0X_DEV Dev, + int16_t LinearityCorrectiveGain); + +/** + * @brief Get the linearity corrective gain + * + * @par Function Description + * Should only be used after a successful call to @a VL53L0X_DataInit to backup + * device NVM value + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pLinearityCorrectiveGain Pointer to the linearity + * corrective gain in x1000 + * if value is 1000 then no modification is applied. + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetLinearityCorrectiveGain(VL53L0X_DEV Dev, + uint16_t *pLinearityCorrectiveGain); + +/** + * Set Group parameter Hold state + * + * @par Function Description + * Set or remove device internal group parameter hold + * + * @note This function is not Implemented + * + * @param Dev Device Handle + * @param GroupParamHold Group parameter Hold state to be set (on/off) + * @return VL53L0X_ERROR_NOT_IMPLEMENTED Not implemented + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetGroupParamHold(VL53L0X_DEV Dev, + uint8_t GroupParamHold); + +/** + * @brief Get the maximal distance for actual setup + * @par Function Description + * Device must be initialized through @a VL53L0X_SetParameters() prior calling + * this function. + * + * Any range value more than the value returned is to be considered as + * "no target detected" or + * "no target in detectable range"\n + * @warning The maximal distance depends on the setup + * + * @note This function is not Implemented + * + * @param Dev Device Handle + * @param pUpperLimitMilliMeter The maximal range limit for actual setup + * (in millimeter) + * @return VL53L0X_ERROR_NOT_IMPLEMENTED Not implemented + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetUpperLimitMilliMeter(VL53L0X_DEV Dev, + uint16_t *pUpperLimitMilliMeter); + + +/** + * @brief Get the Total Signal Rate + * @par Function Description + * This function will return the Total Signal Rate after a good ranging is done. + * + * @note This function access to Device + * + * @param Dev Device Handle + * @param pTotalSignalRate Total Signal Rate value in Mega count per second + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_Error VL53L0X_GetTotalSignalRate(VL53L0X_DEV Dev, + FixPoint1616_t *pTotalSignalRate); + +/** @} VL53L0X_general_group */ + +/** @defgroup VL53L0X_init_group VL53L0X Init Functions + * @brief VL53L0X Init Functions + * @{ + */ + +/** + * @brief Set new device address + * + * After completion the device will answer to the new address programmed. + * This function should be called when several devices are used in parallel + * before start programming the sensor. + * When a single device us used, there is no need to call this function. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param DeviceAddress The new Device address + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetDeviceAddress(VL53L0X_DEV Dev, + uint8_t DeviceAddress); + +/** + * + * @brief One time device initialization + * + * To be called once and only once after device is brought out of reset + * (Chip enable) and booted see @a VL53L0X_WaitDeviceBooted() + * + * @par Function Description + * When not used after a fresh device "power up" or reset, it may return + * @a #VL53L0X_ERROR_CALIBRATION_WARNING meaning wrong calibration data + * may have been fetched from device that can result in ranging offset error\n + * If application cannot execute device reset or need to run VL53L0X_DataInit + * multiple time then it must ensure proper offset calibration saving and + * restore on its own by using @a VL53L0X_GetOffsetCalibrationData() on first + * power up and then @a VL53L0X_SetOffsetCalibrationData() in all subsequent + * init. + * This function will change the VL53L0X_State from VL53L0X_STATE_POWERDOWN to + * VL53L0X_STATE_WAIT_STATICINIT. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_DataInit(VL53L0X_DEV Dev); + +/** + * @brief Set the tuning settings pointer + * + * This function is used to specify the Tuning settings buffer to be used + * for a given device. The buffer contains all the necessary data to permit + * the API to write tuning settings. + * This function permit to force the usage of either external or internal + * tuning settings. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pTuningSettingBuffer Pointer to tuning settings buffer. + * @param UseInternalTuningSettings Use internal tuning settings value. + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetTuningSettingBuffer(VL53L0X_DEV Dev, + uint8_t *pTuningSettingBuffer, uint8_t UseInternalTuningSettings); + +/** + * @brief Get the tuning settings pointer and the internal external switch + * value. + * + * This function is used to get the Tuning settings buffer pointer and the + * value. + * of the switch to select either external or internal tuning settings. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param ppTuningSettingBuffer Pointer to tuning settings buffer. + * @param pUseInternalTuningSettings Pointer to store Use internal tuning + * settings value. + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetTuningSettingBuffer(VL53L0X_DEV Dev, + uint8_t **ppTuningSettingBuffer, uint8_t *pUseInternalTuningSettings); + +/** + * @brief Do basic device init (and eventually patch loading) + * This function will change the VL53L0X_State from + * VL53L0X_STATE_WAIT_STATICINIT to VL53L0X_STATE_IDLE. + * In this stage all default setting will be applied. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_StaticInit(VL53L0X_DEV Dev); + +/** + * @brief Wait for device booted after chip enable (hardware standby) + * This function can be run only when VL53L0X_State is VL53L0X_STATE_POWERDOWN. + * + * @note This function is not Implemented + * + * @param Dev Device Handle + * @return VL53L0X_ERROR_NOT_IMPLEMENTED Not implemented + * + */ +VL53L0X_API VL53L0X_Error VL53L0X_WaitDeviceBooted(VL53L0X_DEV Dev); + +/** + * @brief Do an hard reset or soft reset (depending on implementation) of the + * device \nAfter call of this function, device must be in same state as right + * after a power-up sequence.This function will change the VL53L0X_State to + * VL53L0X_STATE_POWERDOWN. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_ResetDevice(VL53L0X_DEV Dev); + +/** @} VL53L0X_init_group */ + +/** @defgroup VL53L0X_parameters_group VL53L0X Parameters Functions + * @brief Functions used to prepare and setup the device + * @{ + */ + +/** + * @brief Prepare device for operation + * @par Function Description + * Update device with provided parameters + * @li Then start ranging operation. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pDeviceParameters Pointer to store current device parameters. + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetDeviceParameters(VL53L0X_DEV Dev, + const VL53L0X_DeviceParameters_t *pDeviceParameters); + +/** + * @brief Retrieve current device parameters + * @par Function Description + * Get actual parameters of the device + * @li Then start ranging operation. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pDeviceParameters Pointer to store current device parameters. + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetDeviceParameters(VL53L0X_DEV Dev, + VL53L0X_DeviceParameters_t *pDeviceParameters); + +/** + * @brief Set a new device mode + * @par Function Description + * Set device to a new mode (ranging, histogram ...) + * + * @note This function doesn't Access to the device + * + * @param Dev Device Handle + * @param DeviceMode New device mode to apply + * Valid values are: + * VL53L0X_DEVICEMODE_SINGLE_RANGING + * VL53L0X_DEVICEMODE_CONTINUOUS_RANGING + * VL53L0X_DEVICEMODE_CONTINUOUS_TIMED_RANGING + * VL53L0X_DEVICEMODE_SINGLE_HISTOGRAM + * VL53L0X_HISTOGRAMMODE_REFERENCE_ONLY + * VL53L0X_HISTOGRAMMODE_RETURN_ONLY + * VL53L0X_HISTOGRAMMODE_BOTH + * + * + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_MODE_NOT_SUPPORTED This error occurs when DeviceMode + * is not in the supported list + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetDeviceMode(VL53L0X_DEV Dev, + VL53L0X_DeviceModes DeviceMode); + +/** + * @brief Get current new device mode + * @par Function Description + * Get actual mode of the device(ranging, histogram ...) + * + * @note This function doesn't Access to the device + * + * @param Dev Device Handle + * @param pDeviceMode Pointer to current apply mode value + * Valid values are: + * VL53L0X_DEVICEMODE_SINGLE_RANGING + * VL53L0X_DEVICEMODE_CONTINUOUS_RANGING + * VL53L0X_DEVICEMODE_CONTINUOUS_TIMED_RANGING + * VL53L0X_DEVICEMODE_SINGLE_HISTOGRAM + * VL53L0X_HISTOGRAMMODE_REFERENCE_ONLY + * VL53L0X_HISTOGRAMMODE_RETURN_ONLY + * VL53L0X_HISTOGRAMMODE_BOTH + * + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_MODE_NOT_SUPPORTED This error occurs when + * DeviceMode is not in the supported list + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetDeviceMode(VL53L0X_DEV Dev, + VL53L0X_DeviceModes * pDeviceMode); + +/** + * @brief Sets the resolution of range measurements. + * @par Function Description + * Set resolution of range measurements to either 0.25mm if + * fraction enabled or 1mm if not enabled. + * + * @note This function Accesses the device + * + * @param Dev Device Handle + * @param Enable Enable high resolution + * + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetRangeFractionEnable(VL53L0X_DEV Dev, + uint8_t Enable); + +/** + * @brief Gets the fraction enable parameter indicating the resolution of + * range measurements. + * + * @par Function Description + * Gets the fraction enable state, which translates to the resolution of + * range measurements as follows :Enabled:=0.25mm resolution, + * Not Enabled:=1mm resolution. + * + * @note This function Accesses the device + * + * @param Dev Device Handle + * @param pEnable Output Parameter reporting the fraction enable state. + * + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetFractionEnable(VL53L0X_DEV Dev, + uint8_t *pEnable); + +/** + * @brief Set a new Histogram mode + * @par Function Description + * Set device to a new Histogram mode + * + * @note This function doesn't Access to the device + * + * @param Dev Device Handle + * @param HistogramMode New device mode to apply + * Valid values are: + * VL53L0X_HISTOGRAMMODE_DISABLED + * VL53L0X_DEVICEMODE_SINGLE_HISTOGRAM + * VL53L0X_HISTOGRAMMODE_REFERENCE_ONLY + * VL53L0X_HISTOGRAMMODE_RETURN_ONLY + * VL53L0X_HISTOGRAMMODE_BOTH + * + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_MODE_NOT_SUPPORTED This error occurs when + * HistogramMode is not in the supported list + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetHistogramMode(VL53L0X_DEV Dev, + VL53L0X_HistogramModes HistogramMode); + +/** + * @brief Get current new device mode + * @par Function Description + * Get current Histogram mode of a Device + * + * @note This function doesn't Access to the device + * + * @param Dev Device Handle + * @param pHistogramMode Pointer to current Histogram Mode value + * Valid values are: + * VL53L0X_HISTOGRAMMODE_DISABLED + * VL53L0X_DEVICEMODE_SINGLE_HISTOGRAM + * VL53L0X_HISTOGRAMMODE_REFERENCE_ONLY + * VL53L0X_HISTOGRAMMODE_RETURN_ONLY + * VL53L0X_HISTOGRAMMODE_BOTH + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetHistogramMode(VL53L0X_DEV Dev, + VL53L0X_HistogramModes * pHistogramMode); + +/** + * @brief Set Ranging Timing Budget in microseconds + * + * @par Function Description + * Defines the maximum time allowed by the user to the device to run a + * full ranging sequence for the current mode (ranging, histogram, ASL ...) + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param MeasurementTimingBudgetMicroSeconds Max measurement time in + * microseconds. + * Valid values are: + * >= 17000 microsecs when wraparound enabled + * >= 12000 microsecs when wraparound disabled + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_INVALID_PARAMS This error is returned if + MeasurementTimingBudgetMicroSeconds out of range + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetMeasurementTimingBudgetMicroSeconds( + VL53L0X_DEV Dev, uint32_t MeasurementTimingBudgetMicroSeconds); + +/** + * @brief Get Ranging Timing Budget in microseconds + * + * @par Function Description + * Returns the programmed the maximum time allowed by the user to the + * device to run a full ranging sequence for the current mode + * (ranging, histogram, ASL ...) + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pMeasurementTimingBudgetMicroSeconds Max measurement time in + * microseconds. + * Valid values are: + * >= 17000 microsecs when wraparound enabled + * >= 12000 microsecs when wraparound disabled + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetMeasurementTimingBudgetMicroSeconds( + VL53L0X_DEV Dev, uint32_t *pMeasurementTimingBudgetMicroSeconds); + +/** + * @brief Gets the VCSEL pulse period. + * + * @par Function Description + * This function retrieves the VCSEL pulse period for the given period type. + * + * @note This function Accesses the device + * + * @param Dev Device Handle + * @param VcselPeriodType VCSEL period identifier (pre-range|final). + * @param pVCSELPulsePeriod Pointer to VCSEL period value. + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_INVALID_PARAMS Error VcselPeriodType parameter not + * supported. + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetVcselPulsePeriod(VL53L0X_DEV Dev, + VL53L0X_VcselPeriod VcselPeriodType, uint8_t *pVCSELPulsePeriod); + +/** + * @brief Sets the VCSEL pulse period. + * + * @par Function Description + * This function retrieves the VCSEL pulse period for the given period type. + * + * @note This function Accesses the device + * + * @param Dev Device Handle + * @param VcselPeriodType VCSEL period identifier (pre-range|final). + * @param VCSELPulsePeriod VCSEL period value + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_INVALID_PARAMS Error VcselPeriodType parameter not + * supported. + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetVcselPulsePeriod(VL53L0X_DEV Dev, + VL53L0X_VcselPeriod VcselPeriodType, uint8_t VCSELPulsePeriod); + +/** + * @brief Sets the (on/off) state of a requested sequence step. + * + * @par Function Description + * This function enables/disables a requested sequence step. + * + * @note This function Accesses the device + * + * @param Dev Device Handle + * @param SequenceStepId Sequence step identifier. + * @param SequenceStepEnabled Demanded state {0=Off,1=On} + * is enabled. + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_INVALID_PARAMS Error SequenceStepId parameter not + * supported. + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetSequenceStepEnable(VL53L0X_DEV Dev, + VL53L0X_SequenceStepId SequenceStepId, uint8_t SequenceStepEnabled); + +/** + * @brief Gets the (on/off) state of a requested sequence step. + * + * @par Function Description + * This function retrieves the state of a requested sequence step, i.e. on/off. + * + * @note This function Accesses the device + * + * @param Dev Device Handle + * @param SequenceStepId Sequence step identifier. + * @param pSequenceStepEnabled Out parameter reporting if the sequence step + * is enabled {0=Off,1=On}. + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_INVALID_PARAMS Error SequenceStepId parameter not + * supported. + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetSequenceStepEnable(VL53L0X_DEV Dev, + VL53L0X_SequenceStepId SequenceStepId, uint8_t *pSequenceStepEnabled); + +/** + * @brief Gets the (on/off) state of all sequence steps. + * + * @par Function Description + * This function retrieves the state of all sequence step in the scheduler. + * + * @note This function Accesses the device + * + * @param Dev Device Handle + * @param pSchedulerSequenceSteps Pointer to struct containing result. + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetSequenceStepEnables(VL53L0X_DEV Dev, + VL53L0X_SchedulerSequenceSteps_t *pSchedulerSequenceSteps); + +/** + * @brief Sets the timeout of a requested sequence step. + * + * @par Function Description + * This function sets the timeout of a requested sequence step. + * + * @note This function Accesses the device + * + * @param Dev Device Handle + * @param SequenceStepId Sequence step identifier. + * @param TimeOutMilliSecs Demanded timeout + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_INVALID_PARAMS Error SequenceStepId parameter not + * supported. + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetSequenceStepTimeout(VL53L0X_DEV Dev, + VL53L0X_SequenceStepId SequenceStepId, FixPoint1616_t TimeOutMilliSecs); + +/** + * @brief Gets the timeout of a requested sequence step. + * + * @par Function Description + * This function retrieves the timeout of a requested sequence step. + * + * @note This function Accesses the device + * + * @param Dev Device Handle + * @param SequenceStepId Sequence step identifier. + * @param pTimeOutMilliSecs Timeout value. + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_INVALID_PARAMS Error SequenceStepId parameter not + * supported. + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetSequenceStepTimeout(VL53L0X_DEV Dev, + VL53L0X_SequenceStepId SequenceStepId, + FixPoint1616_t *pTimeOutMilliSecs); + +/** + * @brief Gets number of sequence steps managed by the API. + * + * @par Function Description + * This function retrieves the number of sequence steps currently managed + * by the API + * + * @note This function Accesses the device + * + * @param Dev Device Handle + * @param pNumberOfSequenceSteps Out parameter reporting the number of + * sequence steps. + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetNumberOfSequenceSteps(VL53L0X_DEV Dev, + uint8_t *pNumberOfSequenceSteps); + +/** + * @brief Gets the name of a given sequence step. + * + * @par Function Description + * This function retrieves the name of sequence steps corresponding to + * SequenceStepId. + * + * @note This function doesn't Accesses the device + * + * @param SequenceStepId Sequence step identifier. + * @param pSequenceStepsString Pointer to Info string + * + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetSequenceStepsInfo( + VL53L0X_SequenceStepId SequenceStepId, char *pSequenceStepsString); + +/** + * Program continuous mode Inter-Measurement period in milliseconds + * + * @par Function Description + * When trying to set too short time return INVALID_PARAMS minimal value + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param InterMeasurementPeriodMilliSeconds Inter-Measurement Period in ms. + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetInterMeasurementPeriodMilliSeconds( + VL53L0X_DEV Dev, uint32_t InterMeasurementPeriodMilliSeconds); + +/** + * Get continuous mode Inter-Measurement period in milliseconds + * + * @par Function Description + * When trying to set too short time return INVALID_PARAMS minimal value + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pInterMeasurementPeriodMilliSeconds Pointer to programmed + * Inter-Measurement Period in milliseconds. + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetInterMeasurementPeriodMilliSeconds( + VL53L0X_DEV Dev, uint32_t *pInterMeasurementPeriodMilliSeconds); + +/** + * @brief Enable/Disable Cross talk compensation feature + * + * @note This function is not Implemented. + * Enable/Disable Cross Talk by set to zero the Cross Talk value + * by using @a VL53L0X_SetXTalkCompensationRateMegaCps(). + * + * @param Dev Device Handle + * @param XTalkCompensationEnable Cross talk compensation + * to be set 0=disabled else = enabled + * @return VL53L0X_ERROR_NOT_IMPLEMENTED Not implemented + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetXTalkCompensationEnable(VL53L0X_DEV Dev, + uint8_t XTalkCompensationEnable); + +/** + * @brief Get Cross talk compensation rate + * + * @note This function is not Implemented. + * Enable/Disable Cross Talk by set to zero the Cross Talk value by + * using @a VL53L0X_SetXTalkCompensationRateMegaCps(). + * + * @param Dev Device Handle + * @param pXTalkCompensationEnable Pointer to the Cross talk compensation + * state 0=disabled or 1 = enabled + * @return VL53L0X_ERROR_NOT_IMPLEMENTED Not implemented + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetXTalkCompensationEnable(VL53L0X_DEV Dev, + uint8_t *pXTalkCompensationEnable); + +/** + * @brief Set Cross talk compensation rate + * + * @par Function Description + * Set Cross talk compensation rate. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param XTalkCompensationRateMegaCps Compensation rate in + * Mega counts per second (16.16 fix point) see datasheet for details + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetXTalkCompensationRateMegaCps( + VL53L0X_DEV Dev, + FixPoint1616_t XTalkCompensationRateMegaCps); + +/** + * @brief Get Cross talk compensation rate + * + * @par Function Description + * Get Cross talk compensation rate. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pXTalkCompensationRateMegaCps Pointer to Compensation rate + in Mega counts per second (16.16 fix point) see datasheet for details + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetXTalkCompensationRateMegaCps( + VL53L0X_DEV Dev, + FixPoint1616_t *pXTalkCompensationRateMegaCps); + +/** + * @brief Set Reference Calibration Parameters + * + * @par Function Description + * Set Reference Calibration Parameters. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param VhvSettings Parameter for VHV + * @param PhaseCal Parameter for PhaseCal + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetRefCalibration(VL53L0X_DEV Dev, + uint8_t VhvSettings, uint8_t PhaseCal); + +/** + * @brief Get Reference Calibration Parameters + * + * @par Function Description + * Get Reference Calibration Parameters. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pVhvSettings Pointer to VHV parameter + * @param pPhaseCal Pointer to PhaseCal Parameter + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetRefCalibration(VL53L0X_DEV Dev, + uint8_t *pVhvSettings, uint8_t *pPhaseCal); + +/** + * @brief Get the number of the check limit managed by a given Device + * + * @par Function Description + * This function give the number of the check limit managed by the Device + * + * @note This function doesn't Access to the device + * + * @param pNumberOfLimitCheck Pointer to the number of check limit. + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetNumberOfLimitCheck( + uint16_t *pNumberOfLimitCheck); + +/** + * @brief Return a description string for a given limit check number + * + * @par Function Description + * This function returns a description string for a given limit check number. + * The limit check is identified with the LimitCheckId. + * + * @note This function doesn't Access to the device + * + * @param Dev Device Handle + * @param LimitCheckId Limit Check ID + (0<= LimitCheckId < VL53L0X_GetNumberOfLimitCheck() ). + * @param pLimitCheckString Pointer to the + description string of the given check limit. + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_INVALID_PARAMS This error is + returned when LimitCheckId value is out of range. + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetLimitCheckInfo(VL53L0X_DEV Dev, + uint16_t LimitCheckId, char *pLimitCheckString); + +/** + * @brief Return a the Status of the specified check limit + * + * @par Function Description + * This function returns the Status of the specified check limit. + * The value indicate if the check is fail or not. + * The limit check is identified with the LimitCheckId. + * + * @note This function doesn't Access to the device + * + * @param Dev Device Handle + * @param LimitCheckId Limit Check ID + (0<= LimitCheckId < VL53L0X_GetNumberOfLimitCheck() ). + * @param pLimitCheckStatus Pointer to the + Limit Check Status of the given check limit. + * LimitCheckStatus : + * 0 the check is not fail + * 1 the check if fail or not enabled + * + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_INVALID_PARAMS This error is + returned when LimitCheckId value is out of range. + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetLimitCheckStatus(VL53L0X_DEV Dev, + uint16_t LimitCheckId, uint8_t *pLimitCheckStatus); + +/** + * @brief Enable/Disable a specific limit check + * + * @par Function Description + * This function Enable/Disable a specific limit check. + * The limit check is identified with the LimitCheckId. + * + * @note This function doesn't Access to the device + * + * @param Dev Device Handle + * @param LimitCheckId Limit Check ID + * (0<= LimitCheckId < VL53L0X_GetNumberOfLimitCheck() ). + * @param LimitCheckEnable if 1 the check limit + * corresponding to LimitCheckId is Enabled + * if 0 the check limit + * corresponding to LimitCheckId is disabled + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_INVALID_PARAMS This error is returned + * when LimitCheckId value is out of range. + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetLimitCheckEnable(VL53L0X_DEV Dev, + uint16_t LimitCheckId, uint8_t LimitCheckEnable); + +/** + * @brief Get specific limit check enable state + * + * @par Function Description + * This function get the enable state of a specific limit check. + * The limit check is identified with the LimitCheckId. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param LimitCheckId Limit Check ID + * (0<= LimitCheckId < VL53L0X_GetNumberOfLimitCheck() ). + * @param pLimitCheckEnable Pointer to the check limit enable + * value. + * if 1 the check limit + * corresponding to LimitCheckId is Enabled + * if 0 the check limit + * corresponding to LimitCheckId is disabled + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_INVALID_PARAMS This error is returned + * when LimitCheckId value is out of range. + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetLimitCheckEnable(VL53L0X_DEV Dev, + uint16_t LimitCheckId, uint8_t *pLimitCheckEnable); + +/** + * @brief Set a specific limit check value + * + * @par Function Description + * This function set a specific limit check value. + * The limit check is identified with the LimitCheckId. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param LimitCheckId Limit Check ID + * (0<= LimitCheckId < VL53L0X_GetNumberOfLimitCheck() ). + * @param LimitCheckValue Limit check Value for a given + * LimitCheckId + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_INVALID_PARAMS This error is returned when either + * LimitCheckId or LimitCheckValue value is out of range. + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetLimitCheckValue(VL53L0X_DEV Dev, + uint16_t LimitCheckId, FixPoint1616_t LimitCheckValue); + +/** + * @brief Get a specific limit check value + * + * @par Function Description + * This function get a specific limit check value from device then it updates + * internal values and check enables. + * The limit check is identified with the LimitCheckId. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param LimitCheckId Limit Check ID + * (0<= LimitCheckId < VL53L0X_GetNumberOfLimitCheck() ). + * @param pLimitCheckValue Pointer to Limit + * check Value for a given LimitCheckId. + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_INVALID_PARAMS This error is returned + * when LimitCheckId value is out of range. + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetLimitCheckValue(VL53L0X_DEV Dev, + uint16_t LimitCheckId, FixPoint1616_t *pLimitCheckValue); + +/** + * @brief Get the current value of the signal used for the limit check + * + * @par Function Description + * This function get a the current value of the signal used for the limit check. + * To obtain the latest value you should run a ranging before. + * The value reported is linked to the limit check identified with the + * LimitCheckId. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param LimitCheckId Limit Check ID + * (0<= LimitCheckId < VL53L0X_GetNumberOfLimitCheck() ). + * @param pLimitCheckCurrent Pointer to current Value for a + * given LimitCheckId. + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_INVALID_PARAMS This error is returned when + * LimitCheckId value is out of range. + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetLimitCheckCurrent(VL53L0X_DEV Dev, + uint16_t LimitCheckId, FixPoint1616_t *pLimitCheckCurrent); + +/** + * @brief Enable (or disable) Wrap around Check + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param WrapAroundCheckEnable Wrap around Check to be set + * 0=disabled, other = enabled + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetWrapAroundCheckEnable(VL53L0X_DEV Dev, + uint8_t WrapAroundCheckEnable); + +/** + * @brief Get setup of Wrap around Check + * + * @par Function Description + * This function get the wrapAround check enable parameters + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pWrapAroundCheckEnable Pointer to the Wrap around Check state + * 0=disabled or 1 = enabled + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetWrapAroundCheckEnable(VL53L0X_DEV Dev, + uint8_t *pWrapAroundCheckEnable); + +/** @} VL53L0X_parameters_group */ + +/** @defgroup VL53L0X_measurement_group VL53L0X Measurement Functions + * @brief Functions used for the measurements + * @{ + */ + +/** + * @brief Single shot measurement. + * + * @par Function Description + * Perform simple measurement sequence (Start measure, Wait measure to end, + * and returns when measurement is done). + * Once function returns, user can get valid data by calling + * VL53L0X_GetRangingMeasurement or VL53L0X_GetHistogramMeasurement + * depending on defined measurement mode + * User should Clear the interrupt in case this are enabled by using the + * function VL53L0X_ClearInterruptMask(). + * + * @warning This function is a blocking function + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_PerformSingleMeasurement(VL53L0X_DEV Dev); + +/** + * @brief Perform Reference Calibration + * + * @details Perform a reference calibration of the Device. + * This function should be run from time to time before doing + * a ranging measurement. + * This function will launch a special ranging measurement, so + * if interrupt are enable an interrupt will be done. + * This function will clear the interrupt generated automatically. + * + * @warning This function is a blocking function + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pVhvSettings Pointer to vhv settings parameter. + * @param pPhaseCal Pointer to PhaseCal parameter. + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_PerformRefCalibration(VL53L0X_DEV Dev, + uint8_t *pVhvSettings, uint8_t *pPhaseCal); + +/** + * @brief Perform XTalk Measurement + * + * @details Measures the current cross talk from glass in front + * of the sensor. + * This functions performs a histogram measurement and uses the results + * to measure the crosstalk. For the function to be successful, there + * must be no target in front of the sensor. + * + * @warning This function is a blocking function + * + * @warning This function is not supported when the final range + * vcsel clock period is set below 10 PCLKS. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param TimeoutMs Histogram measurement duration. + * @param pXtalkPerSpad Output parameter containing the crosstalk + * measurement result, in MCPS/Spad. Format fixpoint 16:16. + * @param pAmbientTooHigh Output parameter which indicate that + * pXtalkPerSpad is not good if the Ambient is too high. + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_INVALID_PARAMS vcsel clock period not supported + * for this operation. Must not be less than 10PCLKS. + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_PerformXTalkMeasurement(VL53L0X_DEV Dev, + uint32_t TimeoutMs, FixPoint1616_t *pXtalkPerSpad, + uint8_t *pAmbientTooHigh); + +/** + * @brief Perform XTalk Calibration + * + * @details Perform a XTalk calibration of the Device. + * This function will launch a ranging measurement, if interrupts + * are enabled an interrupt will be done. + * This function will clear the interrupt generated automatically. + * This function will program a new value for the XTalk compensation + * and it will enable the cross talk before exit. + * This function will disable the VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD. + * + * @warning This function is a blocking function + * + * @note This function Access to the device + * + * @note This function change the device mode to + * VL53L0X_DEVICEMODE_SINGLE_RANGING + * + * @param Dev Device Handle + * @param XTalkCalDistance XTalkCalDistance value used for the XTalk + * computation. + * @param pXTalkCompensationRateMegaCps Pointer to new + * XTalkCompensation value. + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_PerformXTalkCalibration(VL53L0X_DEV Dev, + FixPoint1616_t XTalkCalDistance, + FixPoint1616_t *pXTalkCompensationRateMegaCps); + +/** + * @brief Perform Offset Calibration + * + * @details Perform a Offset calibration of the Device. + * This function will launch a ranging measurement, if interrupts are + * enabled an interrupt will be done. + * This function will clear the interrupt generated automatically. + * This function will program a new value for the Offset calibration value + * This function will disable the VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD. + * + * @warning This function is a blocking function + * + * @note This function Access to the device + * + * @note This function does not change the device mode. + * + * @param Dev Device Handle + * @param CalDistanceMilliMeter Calibration distance value used for the + * offset compensation. + * @param pOffsetMicroMeter Pointer to new Offset value computed by the + * function. + * + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_PerformOffsetCalibration(VL53L0X_DEV Dev, + FixPoint1616_t CalDistanceMilliMeter, int32_t *pOffsetMicroMeter); + +/** + * @brief Start device measurement + * + * @details Started measurement will depend on device parameters set through + * @a VL53L0X_SetParameters() + * This is a non-blocking function. + * This function will change the VL53L0X_State from VL53L0X_STATE_IDLE to + * VL53L0X_STATE_RUNNING. + * + * @note This function Access to the device + * + + * @param Dev Device Handle + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_MODE_NOT_SUPPORTED This error occurs when + * DeviceMode programmed with @a VL53L0X_SetDeviceMode is not in the supported + * list: + * Supported mode are: + * VL53L0X_DEVICEMODE_SINGLE_RANGING, + * VL53L0X_DEVICEMODE_CONTINUOUS_RANGING, + * VL53L0X_DEVICEMODE_CONTINUOUS_TIMED_RANGING + * @return VL53L0X_ERROR_TIME_OUT Time out on start measurement + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_StartMeasurement(VL53L0X_DEV Dev); + +/** + * @brief Stop device measurement + * + * @details Will set the device in standby mode at end of current measurement\n + * Not necessary in single mode as device shall return automatically + * in standby mode at end of measurement. + * This function will change the VL53L0X_State from + * VL53L0X_STATE_RUNNING to VL53L0X_STATE_IDLE. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_StopMeasurement(VL53L0X_DEV Dev); + +/** + * @brief Return Measurement Data Ready + * + * @par Function Description + * This function indicate that a measurement data is ready. + * This function check if interrupt mode is used then check is done accordingly. + * If perform function clear the interrupt, this function will not work, + * like in case of @a VL53L0X_PerformSingleRangingMeasurement(). + * The previous function is blocking function, VL53L0X_GetMeasurementDataReady + * is used for non-blocking capture. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pMeasurementDataReady Pointer to Measurement Data Ready. + * 0=data not ready, 1 = data ready + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetMeasurementDataReady(VL53L0X_DEV Dev, + uint8_t *pMeasurementDataReady); + +/** + * @brief Wait for device ready for a new measurement command. + * Blocking function. + * + * @note This function is not Implemented + * + * @param Dev Device Handle + * @param MaxLoop Max Number of polling loop (timeout). + * @return VL53L0X_ERROR_NOT_IMPLEMENTED Not implemented + */ +VL53L0X_API VL53L0X_Error VL53L0X_WaitDeviceReadyForNewMeasurement( + VL53L0X_DEV Dev, + uint32_t MaxLoop); + +/** + * @brief Retrieve the Reference Signal after a measurements + * + * @par Function Description + * Get Reference Signal from last successful Ranging measurement + * This function return a valid value after that you call the + * @a VL53L0X_GetRangingMeasurementData(). + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pMeasurementRefSignal Pointer to the Ref Signal to fill up. + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetMeasurementRefSignal(VL53L0X_DEV Dev, + FixPoint1616_t *pMeasurementRefSignal); + +/** + * @brief Retrieve the measurements from device for a given setup + * + * @par Function Description + * Get data from last successful Ranging measurement + * @warning USER should take care about @a VL53L0X_GetNumberOfROIZones() + * before get data. + * PAL will fill a NumberOfROIZones times the corresponding data + * structure used in the measurement function. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pRangingMeasurementData Pointer to the data structure to fill up. + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetRangingMeasurementData(VL53L0X_DEV Dev, + VL53L0X_RangingMeasurementData_t *pRangingMeasurementData); + +/** + * @brief Retrieve the measurements from device for a given setup + * + * @par Function Description + * Get data from last successful Histogram measurement + * @warning USER should take care about @a VL53L0X_GetNumberOfROIZones() + * before get data. + * PAL will fill a NumberOfROIZones times the corresponding data structure + * used in the measurement function. + * + * @note This function is not Implemented + * + * @param Dev Device Handle + * @param pHistogramMeasurementData Pointer to the histogram data structure. + * @return VL53L0X_ERROR_NOT_IMPLEMENTED Not implemented + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetHistogramMeasurementData(VL53L0X_DEV Dev, + VL53L0X_HistogramMeasurementData_t *pHistogramMeasurementData); + +/** + * @brief Performs a single ranging measurement and retrieve the ranging + * measurement data + * + * @par Function Description + * This function will change the device mode to + * VL53L0X_DEVICEMODE_SINGLE_RANGING with @a VL53L0X_SetDeviceMode(), + * It performs measurement with @a VL53L0X_PerformSingleMeasurement() + * It get data from last successful Ranging measurement with + * @a VL53L0X_GetRangingMeasurementData. + * Finally it clear the interrupt with @a VL53L0X_ClearInterruptMask(). + * + * @note This function Access to the device + * + * @note This function change the device mode to + * VL53L0X_DEVICEMODE_SINGLE_RANGING + * + * @param Dev Device Handle + * @param pRangingMeasurementData Pointer to the data structure to fill up. + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_PerformSingleRangingMeasurement( + VL53L0X_DEV Dev, + VL53L0X_RangingMeasurementData_t *pRangingMeasurementData); + +/** + * @brief Performs a single histogram measurement and retrieve the histogram + * measurement data + * Is equivalent to VL53L0X_PerformSingleMeasurement + + * VL53L0X_GetHistogramMeasurementData + * + * @par Function Description + * Get data from last successful Ranging measurement. + * This function will clear the interrupt in case of these are enabled. + * + * @note This function is not Implemented + * + * @param Dev Device Handle + * @param pHistogramMeasurementData Pointer to the data structure to fill up. + * @return VL53L0X_ERROR_NOT_IMPLEMENTED Not implemented + */ +VL53L0X_API VL53L0X_Error VL53L0X_PerformSingleHistogramMeasurement( + VL53L0X_DEV Dev, + VL53L0X_HistogramMeasurementData_t *pHistogramMeasurementData); + +/** + * @brief Set the number of ROI Zones to be used for a specific Device + * + * @par Function Description + * Set the number of ROI Zones to be used for a specific Device. + * The programmed value should be less than the max number of ROI Zones given + * with @a VL53L0X_GetMaxNumberOfROIZones(). + * This version of API manage only one zone. + * + * @param Dev Device Handle + * @param NumberOfROIZones Number of ROI Zones to be used for a + * specific Device. + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_INVALID_PARAMS This error is returned if + * NumberOfROIZones != 1 + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetNumberOfROIZones(VL53L0X_DEV Dev, + uint8_t NumberOfROIZones); + +/** + * @brief Get the number of ROI Zones managed by the Device + * + * @par Function Description + * Get number of ROI Zones managed by the Device + * USER should take care about @a VL53L0X_GetNumberOfROIZones() + * before get data after a perform measurement. + * PAL will fill a NumberOfROIZones times the corresponding data + * structure used in the measurement function. + * + * @note This function doesn't Access to the device + * + * @param Dev Device Handle + * @param pNumberOfROIZones Pointer to the Number of ROI Zones value. + * @return VL53L0X_ERROR_NONE Success + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetNumberOfROIZones(VL53L0X_DEV Dev, + uint8_t *pNumberOfROIZones); + +/** + * @brief Get the Maximum number of ROI Zones managed by the Device + * + * @par Function Description + * Get Maximum number of ROI Zones managed by the Device. + * + * @note This function doesn't Access to the device + * + * @param Dev Device Handle + * @param pMaxNumberOfROIZones Pointer to the Maximum Number + * of ROI Zones value. + * @return VL53L0X_ERROR_NONE Success + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetMaxNumberOfROIZones(VL53L0X_DEV Dev, + uint8_t *pMaxNumberOfROIZones); + +/** @} VL53L0X_measurement_group */ + +/** @defgroup VL53L0X_interrupt_group VL53L0X Interrupt Functions + * @brief Functions used for interrupt managements + * @{ + */ + +/** + * @brief Set the configuration of GPIO pin for a given device + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param Pin ID of the GPIO Pin + * @param Functionality Select Pin functionality. + * Refer to ::VL53L0X_GpioFunctionality + * @param DeviceMode Device Mode associated to the Gpio. + * @param Polarity Set interrupt polarity. Active high + * or active low see ::VL53L0X_InterruptPolarity + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_GPIO_NOT_EXISTING Only Pin=0 is accepted + * @return VL53L0X_ERROR_GPIO_FUNCTIONALITY_NOT_SUPPORTED This error occurs + * when Functionality programmed is not in the supported list: + * Supported value are: + * VL53L0X_GPIOFUNCTIONALITY_OFF, + * VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_LOW, + * VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_HIGH, + VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_OUT, + * VL53L0X_GPIOFUNCTIONALITY_NEW_MEASURE_READY + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetGpioConfig(VL53L0X_DEV Dev, uint8_t Pin, + VL53L0X_DeviceModes DeviceMode, VL53L0X_GpioFunctionality Functionality, + VL53L0X_InterruptPolarity Polarity); + +/** + * @brief Get current configuration for GPIO pin for a given device + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param Pin ID of the GPIO Pin + * @param pDeviceMode Pointer to Device Mode associated to the Gpio. + * @param pFunctionality Pointer to Pin functionality. + * Refer to ::VL53L0X_GpioFunctionality + * @param pPolarity Pointer to interrupt polarity. + * Active high or active low see ::VL53L0X_InterruptPolarity + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_GPIO_NOT_EXISTING Only Pin=0 is accepted + * @return VL53L0X_ERROR_GPIO_FUNCTIONALITY_NOT_SUPPORTED This error occurs + * when Functionality programmed is not in the supported list: + * Supported value are: + * VL53L0X_GPIOFUNCTIONALITY_OFF, + * VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_LOW, + * VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_HIGH, + * VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_OUT, + * VL53L0X_GPIOFUNCTIONALITY_NEW_MEASURE_READY + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetGpioConfig(VL53L0X_DEV Dev, uint8_t Pin, + VL53L0X_DeviceModes * pDeviceMode, + VL53L0X_GpioFunctionality * pFunctionality, + VL53L0X_InterruptPolarity * pPolarity); + +/** + * @brief Set low and high Interrupt thresholds for a given mode + * (ranging, ALS, ...) for a given device + * + * @par Function Description + * Set low and high Interrupt thresholds for a given mode (ranging, ALS, ...) + * for a given device + * + * @note This function Access to the device + * + * @note DeviceMode is ignored for the current device + * + * @param Dev Device Handle + * @param DeviceMode Device Mode for which change thresholds + * @param ThresholdLow Low threshold (mm, lux ..., depending on the mode) + * @param ThresholdHigh High threshold (mm, lux ..., depending on the mode) + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetInterruptThresholds(VL53L0X_DEV Dev, + VL53L0X_DeviceModes DeviceMode, FixPoint1616_t ThresholdLow, + FixPoint1616_t ThresholdHigh); + +/** + * @brief Get high and low Interrupt thresholds for a given mode + * (ranging, ALS, ...) for a given device + * + * @par Function Description + * Get high and low Interrupt thresholds for a given mode (ranging, ALS, ...) + * for a given device + * + * @note This function Access to the device + * + * @note DeviceMode is ignored for the current device + * + * @param Dev Device Handle + * @param DeviceMode Device Mode from which read thresholds + * @param pThresholdLow Low threshold (mm, lux ..., depending on the mode) + * @param pThresholdHigh High threshold (mm, lux ..., depending on the mode) + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetInterruptThresholds(VL53L0X_DEV Dev, + VL53L0X_DeviceModes DeviceMode, FixPoint1616_t *pThresholdLow, + FixPoint1616_t *pThresholdHigh); + +/** + * @brief Return device stop completion status + * + * @par Function Description + * Returns stop completiob status. + * User shall call this function after a stop command + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pStopStatus Pointer to status variable to update + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetStopCompletedStatus(VL53L0X_DEV Dev, + uint32_t *pStopStatus); + + +/** + * @brief Clear given system interrupt condition + * + * @par Function Description + * Clear given interrupt(s). + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param InterruptMask Mask of interrupts to clear + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_INTERRUPT_NOT_CLEARED Cannot clear interrupts + * + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_ClearInterruptMask(VL53L0X_DEV Dev, + uint32_t InterruptMask); + +/** + * @brief Return device interrupt status + * + * @par Function Description + * Returns currently raised interrupts by the device. + * User shall be able to activate/deactivate interrupts through + * @a VL53L0X_SetGpioConfig() + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pInterruptMaskStatus Pointer to status variable to update + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetInterruptMaskStatus(VL53L0X_DEV Dev, + uint32_t *pInterruptMaskStatus); + +/** + * @brief Configure ranging interrupt reported to system + * + * @note This function is not Implemented + * + * @param Dev Device Handle + * @param InterruptMask Mask of interrupt to Enable/disable + * (0:interrupt disabled or 1: interrupt enabled) + * @return VL53L0X_ERROR_NOT_IMPLEMENTED Not implemented + */ +VL53L0X_API VL53L0X_Error VL53L0X_EnableInterruptMask(VL53L0X_DEV Dev, + uint32_t InterruptMask); + +/** @} VL53L0X_interrupt_group */ + +/** @defgroup VL53L0X_SPADfunctions_group VL53L0X SPAD Functions + * @brief Functions used for SPAD managements + * @{ + */ + +/** + * @brief Set the SPAD Ambient Damper Threshold value + * + * @par Function Description + * This function set the SPAD Ambient Damper Threshold value + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param SpadAmbientDamperThreshold SPAD Ambient Damper Threshold value + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetSpadAmbientDamperThreshold(VL53L0X_DEV Dev, + uint16_t SpadAmbientDamperThreshold); + +/** + * @brief Get the current SPAD Ambient Damper Threshold value + * + * @par Function Description + * This function get the SPAD Ambient Damper Threshold value + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pSpadAmbientDamperThreshold Pointer to programmed + * SPAD Ambient Damper Threshold value + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetSpadAmbientDamperThreshold(VL53L0X_DEV Dev, + uint16_t *pSpadAmbientDamperThreshold); + +/** + * @brief Set the SPAD Ambient Damper Factor value + * + * @par Function Description + * This function set the SPAD Ambient Damper Factor value + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param SpadAmbientDamperFactor SPAD Ambient Damper Factor value + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetSpadAmbientDamperFactor(VL53L0X_DEV Dev, + uint16_t SpadAmbientDamperFactor); + +/** + * @brief Get the current SPAD Ambient Damper Factor value + * + * @par Function Description + * This function get the SPAD Ambient Damper Factor value + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param pSpadAmbientDamperFactor Pointer to programmed SPAD Ambient + * Damper Factor value + * @return VL53L0X_ERROR_NONE Success + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetSpadAmbientDamperFactor(VL53L0X_DEV Dev, + uint16_t *pSpadAmbientDamperFactor); + +/** + * @brief Performs Reference Spad Management + * + * @par Function Description + * The reference SPAD initialization procedure determines the minimum amount + * of reference spads to be enables to achieve a target reference signal rate + * and should be performed once during initialization. + * + * @note This function Access to the device + * + * @note This function change the device mode to + * VL53L0X_DEVICEMODE_SINGLE_RANGING + * + * @param Dev Device Handle + * @param refSpadCount Reports ref Spad Count + * @param isApertureSpads Reports if spads are of type + * aperture or non-aperture. + * 1:=aperture, 0:=Non-Aperture + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_REF_SPAD_INIT Error in the Ref Spad procedure. + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_PerformRefSpadManagement(VL53L0X_DEV Dev, + uint32_t *refSpadCount, uint8_t *isApertureSpads); + +/** + * @brief Applies Reference SPAD configuration + * + * @par Function Description + * This function applies a given number of reference spads, identified as + * either Aperture or Non-Aperture. + * The requested spad count and type are stored within the device specific + * parameters data for access by the host. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param refSpadCount Number of ref spads. + * @param isApertureSpads Defines if spads are of type + * aperture or non-aperture. + * 1:=aperture, 0:=Non-Aperture + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_REF_SPAD_INIT Error in the in the reference + * spad configuration. + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_SetReferenceSpads(VL53L0X_DEV Dev, + uint32_t refSpadCount, uint8_t isApertureSpads); + +/** + * @brief Retrieves SPAD configuration + * + * @par Function Description + * This function retrieves the current number of applied reference spads + * and also their type : Aperture or Non-Aperture. + * + * @note This function Access to the device + * + * @param Dev Device Handle + * @param refSpadCount Number ref Spad Count + * @param isApertureSpads Reports if spads are of type + * aperture or non-aperture. + * 1:=aperture, 0:=Non-Aperture + * @return VL53L0X_ERROR_NONE Success + * @return VL53L0X_ERROR_REF_SPAD_INIT Error in the in the reference + * spad configuration. + * @return "Other error code" See ::VL53L0X_Error + */ +VL53L0X_API VL53L0X_Error VL53L0X_GetReferenceSpads(VL53L0X_DEV Dev, + uint32_t *refSpadCount, uint8_t *isApertureSpads); + +/** @} VL53L0X_SPADfunctions_group */ + +/** @} VL53L0X_cut11_group */ + +#ifdef __cplusplus +} +#endif + +#endif /* _VL53L0X_API_H_ */ diff --git a/lib/vl53l0x/vl53l0x_api_calibration.c b/lib/vl53l0x/vl53l0x_api_calibration.c new file mode 100644 index 0000000..7638553 --- /dev/null +++ b/lib/vl53l0x/vl53l0x_api_calibration.c @@ -0,0 +1,1288 @@ +/******************************************************************************* + * Copyright 2016, STMicroelectronics International N.V. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of STMicroelectronics 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, FITNESS FOR A PARTICULAR PURPOSE, AND + NON-INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS ARE DISCLAIMED. + IN NO EVENT SHALL STMICROELECTRONICS INTERNATIONAL N.V. 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 "vl53l0x_api.h" +#include "vl53l0x_api_core.h" +#include "vl53l0x_api_calibration.h" + +#ifndef __KERNEL__ +#include +#endif + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(TRACE_MODULE_API, fmt, ##__VA_ARGS__) +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(TRACE_MODULE_API, status, ##__VA_ARGS__) +#define LOG_FUNCTION_END_FMT(status, fmt, ...) \ + _LOG_FUNCTION_END_FMT(TRACE_MODULE_API, status, fmt, ##__VA_ARGS__) + +#define REF_ARRAY_SPAD_0 0 +#define REF_ARRAY_SPAD_5 5 +#define REF_ARRAY_SPAD_10 10 + +uint32_t refArrayQuadrants[4] = {REF_ARRAY_SPAD_10, REF_ARRAY_SPAD_5, + REF_ARRAY_SPAD_0, REF_ARRAY_SPAD_5 }; + +VL53L0X_Error VL53L0X_perform_xtalk_calibration(VL53L0X_DEV Dev, + FixPoint1616_t XTalkCalDistance, + FixPoint1616_t *pXTalkCompensationRateMegaCps) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint16_t sum_ranging = 0; + uint16_t sum_spads = 0; + FixPoint1616_t sum_signalRate = 0; + FixPoint1616_t total_count = 0; + uint8_t xtalk_meas = 0; + VL53L0X_RangingMeasurementData_t RangingMeasurementData; + FixPoint1616_t xTalkStoredMeanSignalRate; + FixPoint1616_t xTalkStoredMeanRange; + FixPoint1616_t xTalkStoredMeanRtnSpads; + uint32_t signalXTalkTotalPerSpad; + uint32_t xTalkStoredMeanRtnSpadsAsInt; + uint32_t xTalkCalDistanceAsInt; + FixPoint1616_t XTalkCompensationRateMegaCps; + + if (XTalkCalDistance <= 0) + Status = VL53L0X_ERROR_INVALID_PARAMS; + + /* Disable the XTalk compensation */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_SetXTalkCompensationEnable(Dev, 0); + + /* Disable the RIT */ + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_SetLimitCheckEnable(Dev, + VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD, 0); + } + + /* Perform 50 measurements and compute the averages */ + if (Status == VL53L0X_ERROR_NONE) { + sum_ranging = 0; + sum_spads = 0; + sum_signalRate = 0; + total_count = 0; + for (xtalk_meas = 0; xtalk_meas < 50; xtalk_meas++) { + Status = VL53L0X_PerformSingleRangingMeasurement(Dev, + &RangingMeasurementData); + + if (Status != VL53L0X_ERROR_NONE) + break; + + /* The range is valid when RangeStatus = 0 */ + if (RangingMeasurementData.RangeStatus == 0) { + sum_ranging = sum_ranging + + RangingMeasurementData.RangeMilliMeter; + sum_signalRate = sum_signalRate + + RangingMeasurementData.SignalRateRtnMegaCps; + sum_spads = sum_spads + + RangingMeasurementData.EffectiveSpadRtnCount + / 256; + total_count = total_count + 1; + } + } + + /* no valid values found */ + if (total_count == 0) + Status = VL53L0X_ERROR_RANGE_ERROR; + + } + + + if (Status == VL53L0X_ERROR_NONE) { + /* FixPoint1616_t / uint16_t = FixPoint1616_t */ + xTalkStoredMeanSignalRate = sum_signalRate / total_count; + xTalkStoredMeanRange = (FixPoint1616_t)((uint32_t)( + sum_ranging << 16) / total_count); + xTalkStoredMeanRtnSpads = (FixPoint1616_t)((uint32_t)( + sum_spads << 16) / total_count); + + /* Round Mean Spads to Whole Number. + * Typically the calculated mean SPAD count is a whole number + * or very close to a whole + * number, therefore any truncation will not result in a + * significant loss in accuracy. + * Also, for a grey target at a typical distance of around + * 400mm, around 220 SPADs will + * be enabled, therefore, any truncation will result in a loss + * of accuracy of less than + * 0.5%. + */ + xTalkStoredMeanRtnSpadsAsInt = (xTalkStoredMeanRtnSpads + + 0x8000) >> 16; + + /* Round Cal Distance to Whole Number. + * Note that the cal distance is in mm, therefore no resolution + * is lost. + */ + xTalkCalDistanceAsInt = (XTalkCalDistance + 0x8000) >> 16; + + if (xTalkStoredMeanRtnSpadsAsInt == 0 || + xTalkCalDistanceAsInt == 0 || + xTalkStoredMeanRange >= XTalkCalDistance) { + XTalkCompensationRateMegaCps = 0; + } else { + /* Round Cal Distance to Whole Number. + * Note that the cal distance is in mm, therefore no + * resolution is lost. + */ + xTalkCalDistanceAsInt = (XTalkCalDistance + + 0x8000) >> 16; + + /* Apply division by mean spad count early in the + * calculation to keep the numbers small. + * This ensures we can maintain a 32bit calculation. + * Fixed1616 / int := Fixed1616 + */ + signalXTalkTotalPerSpad = (xTalkStoredMeanSignalRate) / + xTalkStoredMeanRtnSpadsAsInt; + + /* Complete the calculation for total Signal XTalk per + * SPAD + * Fixed1616 * (Fixed1616 - Fixed1616/int) := + * (2^16 * Fixed1616) + */ + signalXTalkTotalPerSpad *= ((1 << 16) - + (xTalkStoredMeanRange / xTalkCalDistanceAsInt)); + + /* Round from 2^16 * Fixed1616, to Fixed1616. */ + XTalkCompensationRateMegaCps = (signalXTalkTotalPerSpad + + 0x8000) >> 16; + } + + *pXTalkCompensationRateMegaCps = XTalkCompensationRateMegaCps; + + /* Enable the XTalk compensation */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_SetXTalkCompensationEnable(Dev, 1); + + /* Enable the XTalk compensation */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_SetXTalkCompensationRateMegaCps(Dev, + XTalkCompensationRateMegaCps); + + } + + return Status; +} + +VL53L0X_Error VL53L0X_perform_offset_calibration(VL53L0X_DEV Dev, + FixPoint1616_t CalDistanceMilliMeter, + int32_t *pOffsetMicroMeter) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint16_t sum_ranging = 0; + FixPoint1616_t total_count = 0; + VL53L0X_RangingMeasurementData_t RangingMeasurementData; + FixPoint1616_t StoredMeanRange; + uint32_t StoredMeanRangeAsInt; + uint32_t CalDistanceAsInt_mm; + uint8_t SequenceStepEnabled; + int meas = 0; + + if (CalDistanceMilliMeter <= 0) + Status = VL53L0X_ERROR_INVALID_PARAMS; + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_SetOffsetCalibrationDataMicroMeter(Dev, 0); + + + /* Get the value of the TCC */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_GetSequenceStepEnable(Dev, + VL53L0X_SEQUENCESTEP_TCC, &SequenceStepEnabled); + + + /* Disable the TCC */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_SetSequenceStepEnable(Dev, + VL53L0X_SEQUENCESTEP_TCC, 0); + + + /* Disable the RIT */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_SetLimitCheckEnable(Dev, + VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD, 0); + + /* Perform 50 measurements and compute the averages */ + if (Status == VL53L0X_ERROR_NONE) { + sum_ranging = 0; + total_count = 0; + for (meas = 0; meas < 50; meas++) { + Status = VL53L0X_PerformSingleRangingMeasurement(Dev, + &RangingMeasurementData); + + if (Status != VL53L0X_ERROR_NONE) + break; + + /* The range is valid when RangeStatus = 0 */ + if (RangingMeasurementData.RangeStatus == 0) { + sum_ranging = sum_ranging + + RangingMeasurementData.RangeMilliMeter; + total_count = total_count + 1; + } + } + + /* no valid values found */ + if (total_count == 0) + Status = VL53L0X_ERROR_RANGE_ERROR; + } + + + if (Status == VL53L0X_ERROR_NONE) { + /* FixPoint1616_t / uint16_t = FixPoint1616_t */ + StoredMeanRange = (FixPoint1616_t)((uint32_t)(sum_ranging << 16) + / total_count); + + StoredMeanRangeAsInt = (StoredMeanRange + 0x8000) >> 16; + + /* Round Cal Distance to Whole Number. + * Note that the cal distance is in mm, therefore no resolution + * is lost. + */ + CalDistanceAsInt_mm = (CalDistanceMilliMeter + 0x8000) >> 16; + + *pOffsetMicroMeter = (CalDistanceAsInt_mm - + StoredMeanRangeAsInt) * 1000; + + /* Apply the calculated offset */ + if (Status == VL53L0X_ERROR_NONE) { + VL53L0X_SETPARAMETERFIELD(Dev, RangeOffsetMicroMeters, + *pOffsetMicroMeter); + Status = VL53L0X_SetOffsetCalibrationDataMicroMeter(Dev, + *pOffsetMicroMeter); + } + + } + + /* Restore the TCC */ + if (Status == VL53L0X_ERROR_NONE) { + if (SequenceStepEnabled != 0) + Status = VL53L0X_SetSequenceStepEnable(Dev, + VL53L0X_SEQUENCESTEP_TCC, 1); + } + + return Status; +} + + +VL53L0X_Error VL53L0X_set_offset_calibration_data_micro_meter(VL53L0X_DEV Dev, + int32_t OffsetCalibrationDataMicroMeter) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + int32_t cMaxOffsetMicroMeter = 511000; + int32_t cMinOffsetMicroMeter = -512000; + int16_t cOffsetRange = 4096; + uint32_t encodedOffsetVal; + + LOG_FUNCTION_START(""); + + if (OffsetCalibrationDataMicroMeter > cMaxOffsetMicroMeter) + OffsetCalibrationDataMicroMeter = cMaxOffsetMicroMeter; + else if (OffsetCalibrationDataMicroMeter < cMinOffsetMicroMeter) + OffsetCalibrationDataMicroMeter = cMinOffsetMicroMeter; + + /* The offset register is 10.2 format and units are mm + * therefore conversion is applied by a division of + * 250. + */ + if (OffsetCalibrationDataMicroMeter >= 0) { + encodedOffsetVal = + OffsetCalibrationDataMicroMeter/250; + } else { + encodedOffsetVal = + cOffsetRange + + OffsetCalibrationDataMicroMeter/250; + } + + Status = VL53L0X_WrWord(Dev, + VL53L0X_REG_ALGO_PART_TO_PART_RANGE_OFFSET_MM, + encodedOffsetVal); + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_get_offset_calibration_data_micro_meter(VL53L0X_DEV Dev, + int32_t *pOffsetCalibrationDataMicroMeter) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint16_t RangeOffsetRegister; + int16_t cMaxOffset = 2047; + int16_t cOffsetRange = 4096; + + /* Note that offset has 10.2 format */ + + Status = VL53L0X_RdWord(Dev, + VL53L0X_REG_ALGO_PART_TO_PART_RANGE_OFFSET_MM, + &RangeOffsetRegister); + + if (Status == VL53L0X_ERROR_NONE) { + RangeOffsetRegister = (RangeOffsetRegister & 0x0fff); + + /* Apply 12 bit 2's compliment conversion */ + if (RangeOffsetRegister > cMaxOffset) + *pOffsetCalibrationDataMicroMeter = + (int16_t)(RangeOffsetRegister - cOffsetRange) + * 250; + else + *pOffsetCalibrationDataMicroMeter = + (int16_t)RangeOffsetRegister * 250; + + } + + return Status; +} + + +VL53L0X_Error VL53L0X_apply_offset_adjustment(VL53L0X_DEV Dev) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + int32_t CorrectedOffsetMicroMeters; + int32_t CurrentOffsetMicroMeters; + + /* if we run on this function we can read all the NVM info + * used by the API + */ + Status = VL53L0X_get_info_from_device(Dev, 7); + + /* Read back current device offset */ + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_GetOffsetCalibrationDataMicroMeter(Dev, + &CurrentOffsetMicroMeters); + } + + /* Apply Offset Adjustment derived from 400mm measurements */ + if (Status == VL53L0X_ERROR_NONE) { + + /* Store initial device offset */ + PALDevDataSet(Dev, Part2PartOffsetNVMMicroMeter, + CurrentOffsetMicroMeters); + + CorrectedOffsetMicroMeters = CurrentOffsetMicroMeters + + (int32_t)PALDevDataGet(Dev, + Part2PartOffsetAdjustmentNVMMicroMeter); + + Status = VL53L0X_SetOffsetCalibrationDataMicroMeter(Dev, + CorrectedOffsetMicroMeters); + + /* store current, adjusted offset */ + if (Status == VL53L0X_ERROR_NONE) { + VL53L0X_SETPARAMETERFIELD(Dev, RangeOffsetMicroMeters, + CorrectedOffsetMicroMeters); + } + } + + return Status; +} + +void get_next_good_spad(uint8_t goodSpadArray[], uint32_t size, + uint32_t curr, int32_t *next) +{ + uint32_t startIndex; + uint32_t fineOffset; + uint32_t cSpadsPerByte = 8; + uint32_t coarseIndex; + uint32_t fineIndex; + uint8_t dataByte; + uint8_t success = 0; + + /* + * Starting with the current good spad, loop through the array to find + * the next. i.e. the next bit set in the sequence. + * + * The coarse index is the byte index of the array and the fine index is + * the index of the bit within each byte. + */ + + *next = -1; + + startIndex = curr / cSpadsPerByte; + fineOffset = curr % cSpadsPerByte; + + for (coarseIndex = startIndex; ((coarseIndex < size) && !success); + coarseIndex++) { + fineIndex = 0; + dataByte = goodSpadArray[coarseIndex]; + + if (coarseIndex == startIndex) { + /* locate the bit position of the provided current + * spad bit before iterating + */ + dataByte >>= fineOffset; + fineIndex = fineOffset; + } + + while (fineIndex < cSpadsPerByte) { + if ((dataByte & 0x1) == 1) { + success = 1; + *next = coarseIndex * cSpadsPerByte + fineIndex; + break; + } + dataByte >>= 1; + fineIndex++; + } + } +} + + +uint8_t is_aperture(uint32_t spadIndex) +{ + /* + * This function reports if a given spad index is an aperture SPAD by + * deriving the quadrant. + */ + uint32_t quadrant; + uint8_t isAperture = 1; + + quadrant = spadIndex >> 6; + if (refArrayQuadrants[quadrant] == REF_ARRAY_SPAD_0) + isAperture = 0; + + return isAperture; +} + + +VL53L0X_Error enable_spad_bit(uint8_t spadArray[], uint32_t size, + uint32_t spadIndex) +{ + VL53L0X_Error status = VL53L0X_ERROR_NONE; + uint32_t cSpadsPerByte = 8; + uint32_t coarseIndex; + uint32_t fineIndex; + + coarseIndex = spadIndex / cSpadsPerByte; + fineIndex = spadIndex % cSpadsPerByte; + if (coarseIndex >= size) + status = VL53L0X_ERROR_REF_SPAD_INIT; + else + spadArray[coarseIndex] |= (1 << fineIndex); + + return status; +} + +VL53L0X_Error count_enabled_spads(uint8_t spadArray[], + uint32_t byteCount, uint32_t maxSpads, + uint32_t *pTotalSpadsEnabled, uint8_t *pIsAperture) +{ + VL53L0X_Error status = VL53L0X_ERROR_NONE; + uint32_t cSpadsPerByte = 8; + uint32_t lastByte; + uint32_t lastBit; + uint32_t byteIndex = 0; + uint32_t bitIndex = 0; + uint8_t tempByte; + uint8_t spadTypeIdentified = 0; + + /* The entire array will not be used for spads, therefore the last + * byte and last bit is determined from the max spads value. + */ + + lastByte = maxSpads / cSpadsPerByte; + lastBit = maxSpads % cSpadsPerByte; + + /* Check that the max spads value does not exceed the array bounds. */ + if (lastByte >= byteCount) + status = VL53L0X_ERROR_REF_SPAD_INIT; + + *pTotalSpadsEnabled = 0; + + /* Count the bits enabled in the whole bytes */ + for (byteIndex = 0; byteIndex <= (lastByte - 1); byteIndex++) { + tempByte = spadArray[byteIndex]; + + for (bitIndex = 0; bitIndex <= cSpadsPerByte; bitIndex++) { + if ((tempByte & 0x01) == 1) { + (*pTotalSpadsEnabled)++; + + if (!spadTypeIdentified) { + *pIsAperture = 1; + if ((byteIndex < 2) && (bitIndex < 4)) + *pIsAperture = 0; + spadTypeIdentified = 1; + } + } + tempByte >>= 1; + } + } + + /* Count the number of bits enabled in the last byte accounting + * for the fact that not all bits in the byte may be used. + */ + tempByte = spadArray[lastByte]; + + for (bitIndex = 0; bitIndex <= lastBit; bitIndex++) { + if ((tempByte & 0x01) == 1) + (*pTotalSpadsEnabled)++; + } + + return status; +} + +VL53L0X_Error set_ref_spad_map(VL53L0X_DEV Dev, uint8_t *refSpadArray) +{ + VL53L0X_Error status = VL53L0X_WriteMulti(Dev, + VL53L0X_REG_GLOBAL_CONFIG_SPAD_ENABLES_REF_0, + refSpadArray, 6); + return status; +} + +VL53L0X_Error get_ref_spad_map(VL53L0X_DEV Dev, uint8_t *refSpadArray) +{ + VL53L0X_Error status = VL53L0X_ReadMulti(Dev, + VL53L0X_REG_GLOBAL_CONFIG_SPAD_ENABLES_REF_0, + refSpadArray, + 6); + return status; +} + +VL53L0X_Error enable_ref_spads(VL53L0X_DEV Dev, + uint8_t apertureSpads, + uint8_t goodSpadArray[], + uint8_t spadArray[], + uint32_t size, + uint32_t start, + uint32_t offset, + uint32_t spadCount, + uint32_t *lastSpad) +{ + VL53L0X_Error status = VL53L0X_ERROR_NONE; + uint32_t index; + uint32_t i; + int32_t nextGoodSpad = offset; + uint32_t currentSpad; + uint8_t checkSpadArray[6]; + + /* + * This function takes in a spad array which may or may not have SPADS + * already enabled and appends from a given offset a requested number + * of new SPAD enables. The 'good spad map' is applied to + * determine the next SPADs to enable. + * + * This function applies to only aperture or only non-aperture spads. + * Checks are performed to ensure this. + */ + + currentSpad = offset; + for (index = 0; index < spadCount; index++) { + get_next_good_spad(goodSpadArray, size, currentSpad, + &nextGoodSpad); + + if (nextGoodSpad == -1) { + status = VL53L0X_ERROR_REF_SPAD_INIT; + break; + } + + /* Confirm that the next good SPAD is non-aperture */ + if (is_aperture(start + nextGoodSpad) != apertureSpads) { + /* if we can't get the required number of good aperture + * spads from the current quadrant then this is an error + */ + status = VL53L0X_ERROR_REF_SPAD_INIT; + break; + } + currentSpad = (uint32_t)nextGoodSpad; + enable_spad_bit(spadArray, size, currentSpad); + currentSpad++; + } + *lastSpad = currentSpad; + + if (status == VL53L0X_ERROR_NONE) + status = set_ref_spad_map(Dev, spadArray); + + + if (status == VL53L0X_ERROR_NONE) { + status = get_ref_spad_map(Dev, checkSpadArray); + + i = 0; + + /* Compare spad maps. If not equal report error. */ + while (i < size) { + if (spadArray[i] != checkSpadArray[i]) { + status = VL53L0X_ERROR_REF_SPAD_INIT; + break; + } + i++; + } + } + return status; +} + + +VL53L0X_Error perform_ref_signal_measurement(VL53L0X_DEV Dev, + uint16_t *refSignalRate) +{ + VL53L0X_Error status = VL53L0X_ERROR_NONE; + VL53L0X_RangingMeasurementData_t rangingMeasurementData; + + uint8_t SequenceConfig = 0; + + /* store the value of the sequence config, + * this will be reset before the end of the function + */ + + SequenceConfig = PALDevDataGet(Dev, SequenceConfig); + + /* + * This function performs a reference signal rate measurement. + */ + if (status == VL53L0X_ERROR_NONE) + status = VL53L0X_WrByte(Dev, + VL53L0X_REG_SYSTEM_SEQUENCE_CONFIG, 0xC0); + + if (status == VL53L0X_ERROR_NONE) + status = VL53L0X_PerformSingleRangingMeasurement(Dev, + &rangingMeasurementData); + + if (status == VL53L0X_ERROR_NONE) + status = VL53L0X_WrByte(Dev, 0xFF, 0x01); + + if (status == VL53L0X_ERROR_NONE) + status = VL53L0X_RdWord(Dev, + VL53L0X_REG_RESULT_PEAK_SIGNAL_RATE_REF, + refSignalRate); + + if (status == VL53L0X_ERROR_NONE) + status = VL53L0X_WrByte(Dev, 0xFF, 0x00); + + if (status == VL53L0X_ERROR_NONE) { + /* restore the previous Sequence Config */ + status = VL53L0X_WrByte(Dev, VL53L0X_REG_SYSTEM_SEQUENCE_CONFIG, + SequenceConfig); + if (status == VL53L0X_ERROR_NONE) + PALDevDataSet(Dev, SequenceConfig, SequenceConfig); + } + + return status; +} + +VL53L0X_Error VL53L0X_perform_ref_spad_management(VL53L0X_DEV Dev, + uint32_t *refSpadCount, + uint8_t *isApertureSpads) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t lastSpadArray[6]; + uint8_t startSelect = 0xB4; + uint32_t minimumSpadCount = 3; + uint32_t maxSpadCount = 44; + uint32_t currentSpadIndex = 0; + uint32_t lastSpadIndex = 0; + int32_t nextGoodSpad = 0; + uint16_t targetRefRate = 0x0A00; /* 20 MCPS in 9:7 format */ + uint16_t peakSignalRateRef; + uint32_t needAptSpads = 0; + uint32_t index = 0; + uint32_t spadArraySize = 6; + uint32_t signalRateDiff = 0; + uint32_t lastSignalRateDiff = 0; + uint8_t complete = 0; + uint8_t VhvSettings = 0; + uint8_t PhaseCal = 0; + uint32_t refSpadCount_int = 0; + uint8_t isApertureSpads_int = 0; + + /* + * The reference SPAD initialization procedure determines the minimum + * amount of reference spads to be enables to achieve a target reference + * signal rate and should be performed once during initialization. + * + * Either aperture or non-aperture spads are applied but never both. + * Firstly non-aperture spads are set, begining with 5 spads, and + * increased one spad at a time until the closest measurement to the + * target rate is achieved. + * + * If the target rate is exceeded when 5 non-aperture spads are enabled, + * initialization is performed instead with aperture spads. + * + * When setting spads, a 'Good Spad Map' is applied. + * + * This procedure operates within a SPAD window of interest of a maximum + * 44 spads. + * The start point is currently fixed to 180, which lies towards the end + * of the non-aperture quadrant and runs in to the adjacent aperture + * quadrant. + */ + + + targetRefRate = PALDevDataGet(Dev, targetRefRate); + + /* + * Initialize Spad arrays. + * Currently the good spad map is initialised to 'All good'. + * This is a short term implementation. The good spad map will be + * provided as an input. + * Note that there are 6 bytes. Only the first 44 bits will be used to + * represent spads. + */ + for (index = 0; index < spadArraySize; index++) + Dev->Data.SpadData.RefSpadEnables[index] = 0; + + + Status = VL53L0X_WrByte(Dev, 0xFF, 0x01); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_DYNAMIC_SPAD_REF_EN_START_OFFSET, 0x00); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_DYNAMIC_SPAD_NUM_REQUESTED_REF_SPAD, 0x2C); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_WrByte(Dev, 0xFF, 0x00); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_GLOBAL_CONFIG_REF_EN_START_SELECT, + startSelect); + + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_POWER_MANAGEMENT_GO1_POWER_FORCE, 0); + + /* Perform ref calibration */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_perform_ref_calibration(Dev, &VhvSettings, + &PhaseCal, 0); + + if (Status == VL53L0X_ERROR_NONE) { + /* Enable Minimum NON-APERTURE Spads */ + currentSpadIndex = 0; + lastSpadIndex = currentSpadIndex; + needAptSpads = 0; + Status = enable_ref_spads(Dev, + needAptSpads, + Dev->Data.SpadData.RefGoodSpadMap, + Dev->Data.SpadData.RefSpadEnables, + spadArraySize, + startSelect, + currentSpadIndex, + minimumSpadCount, + &lastSpadIndex); + } + + if (Status == VL53L0X_ERROR_NONE) { + currentSpadIndex = lastSpadIndex; + + Status = perform_ref_signal_measurement(Dev, + &peakSignalRateRef); + if ((Status == VL53L0X_ERROR_NONE) && + (peakSignalRateRef > targetRefRate)) { + /* Signal rate measurement too high, + * switch to APERTURE SPADs + */ + + for (index = 0; index < spadArraySize; index++) + Dev->Data.SpadData.RefSpadEnables[index] = 0; + + + /* Increment to the first APERTURE spad */ + while ((is_aperture(startSelect + currentSpadIndex) + == 0) && (currentSpadIndex < maxSpadCount)) { + currentSpadIndex++; + } + + needAptSpads = 1; + + Status = enable_ref_spads(Dev, + needAptSpads, + Dev->Data.SpadData.RefGoodSpadMap, + Dev->Data.SpadData.RefSpadEnables, + spadArraySize, + startSelect, + currentSpadIndex, + minimumSpadCount, + &lastSpadIndex); + + if (Status == VL53L0X_ERROR_NONE) { + currentSpadIndex = lastSpadIndex; + Status = perform_ref_signal_measurement(Dev, + &peakSignalRateRef); + + if ((Status == VL53L0X_ERROR_NONE) && + (peakSignalRateRef > targetRefRate)) { + /* Signal rate still too high after + * setting the minimum number of + * APERTURE spads. Can do no more + * therefore set the min number of + * aperture spads as the result. + */ + isApertureSpads_int = 1; + refSpadCount_int = minimumSpadCount; + } + } + } else { + needAptSpads = 0; + } + } + + if ((Status == VL53L0X_ERROR_NONE) && + (peakSignalRateRef < targetRefRate)) { + /* At this point, the minimum number of either aperture + * or non-aperture spads have been set. Proceed to add + * spads and perform measurements until the target + * reference is reached. + */ + isApertureSpads_int = needAptSpads; + refSpadCount_int = minimumSpadCount; + + memcpy(lastSpadArray, Dev->Data.SpadData.RefSpadEnables, + spadArraySize); + lastSignalRateDiff = abs(peakSignalRateRef - + targetRefRate); + complete = 0; + + while (!complete) { + get_next_good_spad( + Dev->Data.SpadData.RefGoodSpadMap, + spadArraySize, currentSpadIndex, + &nextGoodSpad); + + if (nextGoodSpad == -1) { + Status = VL53L0X_ERROR_REF_SPAD_INIT; + break; + } + + /* Cannot combine Aperture and Non-Aperture spads, so + * ensure the current spad is of the correct type. + */ + if (is_aperture((uint32_t)startSelect + nextGoodSpad) != + needAptSpads) { + /* At this point we have enabled the maximum + * number of Aperture spads. + */ + complete = 1; + break; + } + + (refSpadCount_int)++; + + currentSpadIndex = nextGoodSpad; + Status = enable_spad_bit( + Dev->Data.SpadData.RefSpadEnables, + spadArraySize, currentSpadIndex); + + if (Status == VL53L0X_ERROR_NONE) { + currentSpadIndex++; + /* Proceed to apply the additional spad and + * perform measurement. + */ + Status = set_ref_spad_map(Dev, + Dev->Data.SpadData.RefSpadEnables); + } + + if (Status != VL53L0X_ERROR_NONE) + break; + + Status = perform_ref_signal_measurement(Dev, + &peakSignalRateRef); + + if (Status != VL53L0X_ERROR_NONE) + break; + + signalRateDiff = abs(peakSignalRateRef - targetRefRate); + + if (peakSignalRateRef > targetRefRate) { + /* Select the spad map that provides the + * measurement closest to the target rate, + * either above or below it. + */ + if (signalRateDiff > lastSignalRateDiff) { + /* Previous spad map produced a closer + * measurement, so choose this. + */ + Status = set_ref_spad_map(Dev, + lastSpadArray); + memcpy( + Dev->Data.SpadData.RefSpadEnables, + lastSpadArray, spadArraySize); + + (refSpadCount_int)--; + } + complete = 1; + } else { + /* Continue to add spads */ + lastSignalRateDiff = signalRateDiff; + memcpy(lastSpadArray, + Dev->Data.SpadData.RefSpadEnables, + spadArraySize); + } + + } /* while */ + } + + if (Status == VL53L0X_ERROR_NONE) { + *refSpadCount = refSpadCount_int; + *isApertureSpads = isApertureSpads_int; + + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, RefSpadsInitialised, 1); + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, + ReferenceSpadCount, (uint8_t)(*refSpadCount)); + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, + ReferenceSpadType, *isApertureSpads); + } + + return Status; +} + +VL53L0X_Error VL53L0X_set_reference_spads(VL53L0X_DEV Dev, + uint32_t count, uint8_t isApertureSpads) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint32_t currentSpadIndex = 0; + uint8_t startSelect = 0xB4; + uint32_t spadArraySize = 6; + uint32_t maxSpadCount = 44; + uint32_t lastSpadIndex; + uint32_t index; + + /* + * This function applies a requested number of reference spads, either + * aperture or + * non-aperture, as requested. + * The good spad map will be applied. + */ + + Status = VL53L0X_WrByte(Dev, 0xFF, 0x01); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_DYNAMIC_SPAD_REF_EN_START_OFFSET, 0x00); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_DYNAMIC_SPAD_NUM_REQUESTED_REF_SPAD, 0x2C); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_WrByte(Dev, 0xFF, 0x00); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_GLOBAL_CONFIG_REF_EN_START_SELECT, + startSelect); + + for (index = 0; index < spadArraySize; index++) + Dev->Data.SpadData.RefSpadEnables[index] = 0; + + if (isApertureSpads) { + /* Increment to the first APERTURE spad */ + while ((is_aperture(startSelect + currentSpadIndex) == 0) && + (currentSpadIndex < maxSpadCount)) { + currentSpadIndex++; + } + } + Status = enable_ref_spads(Dev, + isApertureSpads, + Dev->Data.SpadData.RefGoodSpadMap, + Dev->Data.SpadData.RefSpadEnables, + spadArraySize, + startSelect, + currentSpadIndex, + count, + &lastSpadIndex); + + if (Status == VL53L0X_ERROR_NONE) { + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, RefSpadsInitialised, 1); + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, + ReferenceSpadCount, (uint8_t)(count)); + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, + ReferenceSpadType, isApertureSpads); + } + + return Status; +} + +VL53L0X_Error VL53L0X_get_reference_spads(VL53L0X_DEV Dev, + uint32_t *pSpadCount, uint8_t *pIsApertureSpads) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t refSpadsInitialised; + uint8_t refSpadArray[6]; + uint32_t cMaxSpadCount = 44; + uint32_t cSpadArraySize = 6; + uint32_t spadsEnabled; + uint8_t isApertureSpads = 0; + + refSpadsInitialised = VL53L0X_GETDEVICESPECIFICPARAMETER(Dev, + RefSpadsInitialised); + + if (refSpadsInitialised == 1) { + + *pSpadCount = (uint32_t)VL53L0X_GETDEVICESPECIFICPARAMETER(Dev, + ReferenceSpadCount); + *pIsApertureSpads = VL53L0X_GETDEVICESPECIFICPARAMETER(Dev, + ReferenceSpadType); + } else { + + /* obtain spad info from device.*/ + Status = get_ref_spad_map(Dev, refSpadArray); + + if (Status == VL53L0X_ERROR_NONE) { + /* count enabled spads within spad map array and + * determine if Aperture or Non-Aperture. + */ + Status = count_enabled_spads(refSpadArray, + cSpadArraySize, + cMaxSpadCount, + &spadsEnabled, + &isApertureSpads); + + if (Status == VL53L0X_ERROR_NONE) { + + *pSpadCount = spadsEnabled; + *pIsApertureSpads = isApertureSpads; + + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, + RefSpadsInitialised, 1); + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, + ReferenceSpadCount, + (uint8_t)spadsEnabled); + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, + ReferenceSpadType, isApertureSpads); + } + } + } + + return Status; +} + + +VL53L0X_Error VL53L0X_perform_single_ref_calibration(VL53L0X_DEV Dev, + uint8_t vhv_init_byte) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_WrByte(Dev, VL53L0X_REG_SYSRANGE_START, + VL53L0X_REG_SYSRANGE_MODE_START_STOP | + vhv_init_byte); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_measurement_poll_for_completion(Dev); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_ClearInterruptMask(Dev, 0); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_WrByte(Dev, VL53L0X_REG_SYSRANGE_START, 0x00); + + return Status; +} + + +VL53L0X_Error VL53L0X_ref_calibration_io(VL53L0X_DEV Dev, + uint8_t read_not_write, + uint8_t VhvSettings, uint8_t PhaseCal, + uint8_t *pVhvSettings, uint8_t *pPhaseCal, + const uint8_t vhv_enable, const uint8_t phase_enable) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t PhaseCalint = 0; + + /* Read VHV from device */ + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x01); + Status |= VL53L0X_WrByte(Dev, 0x00, 0x00); + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x00); + + if (read_not_write) { + if (vhv_enable) + Status |= VL53L0X_RdByte(Dev, 0xCB, pVhvSettings); + if (phase_enable) + Status |= VL53L0X_RdByte(Dev, 0xEE, &PhaseCalint); + } else { + if (vhv_enable) + Status |= VL53L0X_WrByte(Dev, 0xCB, VhvSettings); + if (phase_enable) + Status |= VL53L0X_UpdateByte(Dev, 0xEE, 0x80, PhaseCal); + } + + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x01); + Status |= VL53L0X_WrByte(Dev, 0x00, 0x01); + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x00); + + *pPhaseCal = (uint8_t)(PhaseCalint&0xEF); + + return Status; +} + + +VL53L0X_Error VL53L0X_perform_vhv_calibration(VL53L0X_DEV Dev, + uint8_t *pVhvSettings, const uint8_t get_data_enable, + const uint8_t restore_config) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t SequenceConfig = 0; + uint8_t VhvSettings = 0; + uint8_t PhaseCal = 0; + uint8_t PhaseCalInt = 0; + + /* store the value of the sequence config, + * this will be reset before the end of the function + */ + + if (restore_config) + SequenceConfig = PALDevDataGet(Dev, SequenceConfig); + + /* Run VHV */ + Status = VL53L0X_WrByte(Dev, VL53L0X_REG_SYSTEM_SEQUENCE_CONFIG, 0x01); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_perform_single_ref_calibration(Dev, 0x40); + + /* Read VHV from device */ + if ((Status == VL53L0X_ERROR_NONE) && (get_data_enable == 1)) { + Status = VL53L0X_ref_calibration_io(Dev, 1, + VhvSettings, PhaseCal, /* Not used here */ + pVhvSettings, &PhaseCalInt, + 1, 0); + } else + *pVhvSettings = 0; + + + if ((Status == VL53L0X_ERROR_NONE) && restore_config) { + /* restore the previous Sequence Config */ + Status = VL53L0X_WrByte(Dev, VL53L0X_REG_SYSTEM_SEQUENCE_CONFIG, + SequenceConfig); + if (Status == VL53L0X_ERROR_NONE) + PALDevDataSet(Dev, SequenceConfig, SequenceConfig); + + } + + return Status; +} + +VL53L0X_Error VL53L0X_perform_phase_calibration(VL53L0X_DEV Dev, + uint8_t *pPhaseCal, const uint8_t get_data_enable, + const uint8_t restore_config) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t SequenceConfig = 0; + uint8_t VhvSettings = 0; + uint8_t PhaseCal = 0; + uint8_t VhvSettingsint; + + /* store the value of the sequence config, + * this will be reset before the end of the function + */ + + if (restore_config) + SequenceConfig = PALDevDataGet(Dev, SequenceConfig); + + /* Run PhaseCal */ + Status = VL53L0X_WrByte(Dev, VL53L0X_REG_SYSTEM_SEQUENCE_CONFIG, 0x02); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_perform_single_ref_calibration(Dev, 0x0); + + /* Read PhaseCal from device */ + if ((Status == VL53L0X_ERROR_NONE) && (get_data_enable == 1)) { + Status = VL53L0X_ref_calibration_io(Dev, 1, + VhvSettings, PhaseCal, /* Not used here */ + &VhvSettingsint, pPhaseCal, + 0, 1); + } else + *pPhaseCal = 0; + + + if ((Status == VL53L0X_ERROR_NONE) && restore_config) { + /* restore the previous Sequence Config */ + Status = VL53L0X_WrByte(Dev, VL53L0X_REG_SYSTEM_SEQUENCE_CONFIG, + SequenceConfig); + if (Status == VL53L0X_ERROR_NONE) + PALDevDataSet(Dev, SequenceConfig, SequenceConfig); + + } + + return Status; +} + +VL53L0X_Error VL53L0X_perform_ref_calibration(VL53L0X_DEV Dev, + uint8_t *pVhvSettings, uint8_t *pPhaseCal, uint8_t get_data_enable) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t SequenceConfig = 0; + + /* store the value of the sequence config, + * this will be reset before the end of the function + */ + + SequenceConfig = PALDevDataGet(Dev, SequenceConfig); + + /* In the following function we don't save the config to optimize + * writes on device. Config is saved and restored only once. + */ + Status = VL53L0X_perform_vhv_calibration( + Dev, pVhvSettings, get_data_enable, 0); + + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_perform_phase_calibration( + Dev, pPhaseCal, get_data_enable, 0); + + + if (Status == VL53L0X_ERROR_NONE) { + /* restore the previous Sequence Config */ + Status = VL53L0X_WrByte(Dev, VL53L0X_REG_SYSTEM_SEQUENCE_CONFIG, + SequenceConfig); + if (Status == VL53L0X_ERROR_NONE) + PALDevDataSet(Dev, SequenceConfig, SequenceConfig); + + } + + return Status; +} + +VL53L0X_Error VL53L0X_set_ref_calibration(VL53L0X_DEV Dev, + uint8_t VhvSettings, uint8_t PhaseCal) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t pVhvSettings; + uint8_t pPhaseCal; + + Status = VL53L0X_ref_calibration_io(Dev, 0, + VhvSettings, PhaseCal, + &pVhvSettings, &pPhaseCal, + 1, 1); + + return Status; +} + +VL53L0X_Error VL53L0X_get_ref_calibration(VL53L0X_DEV Dev, + uint8_t *pVhvSettings, uint8_t *pPhaseCal) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t VhvSettings = 0; + uint8_t PhaseCal = 0; + + Status = VL53L0X_ref_calibration_io(Dev, 1, + VhvSettings, PhaseCal, + pVhvSettings, pPhaseCal, + 1, 1); + + return Status; +} diff --git a/lib/vl53l0x/vl53l0x_api_calibration.h b/lib/vl53l0x/vl53l0x_api_calibration.h new file mode 100644 index 0000000..8fe3cf8 --- /dev/null +++ b/lib/vl53l0x/vl53l0x_api_calibration.h @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright 2016, STMicroelectronics International N.V. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of STMicroelectronics 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, FITNESS FOR A PARTICULAR PURPOSE, AND +NON-INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS ARE DISCLAIMED. +IN NO EVENT SHALL STMICROELECTRONICS INTERNATIONAL N.V. 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. +*******************************************************************************/ + +#ifndef _VL53L0X_API_CALIBRATION_H_ +#define _VL53L0X_API_CALIBRATION_H_ + +#include "vl53l0x_def.h" +#include "vl53l0x_platform.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +VL53L0X_Error VL53L0X_perform_xtalk_calibration(VL53L0X_DEV Dev, + FixPoint1616_t XTalkCalDistance, + FixPoint1616_t *pXTalkCompensationRateMegaCps); + +VL53L0X_Error VL53L0X_perform_offset_calibration(VL53L0X_DEV Dev, + FixPoint1616_t CalDistanceMilliMeter, + int32_t *pOffsetMicroMeter); + +VL53L0X_Error VL53L0X_set_offset_calibration_data_micro_meter(VL53L0X_DEV Dev, + int32_t OffsetCalibrationDataMicroMeter); + +VL53L0X_Error VL53L0X_get_offset_calibration_data_micro_meter(VL53L0X_DEV Dev, + int32_t *pOffsetCalibrationDataMicroMeter); + +VL53L0X_Error VL53L0X_apply_offset_adjustment(VL53L0X_DEV Dev); + +VL53L0X_Error VL53L0X_perform_ref_spad_management(VL53L0X_DEV Dev, + uint32_t *refSpadCount, uint8_t *isApertureSpads); + +VL53L0X_Error VL53L0X_set_reference_spads(VL53L0X_DEV Dev, + uint32_t count, uint8_t isApertureSpads); + +VL53L0X_Error VL53L0X_get_reference_spads(VL53L0X_DEV Dev, + uint32_t *pSpadCount, uint8_t *pIsApertureSpads); + +VL53L0X_Error VL53L0X_perform_phase_calibration(VL53L0X_DEV Dev, + uint8_t *pPhaseCal, const uint8_t get_data_enable, + const uint8_t restore_config); + +VL53L0X_Error VL53L0X_perform_ref_calibration(VL53L0X_DEV Dev, + uint8_t *pVhvSettings, uint8_t *pPhaseCal, uint8_t get_data_enable); + +VL53L0X_Error VL53L0X_set_ref_calibration(VL53L0X_DEV Dev, + uint8_t VhvSettings, uint8_t PhaseCal); + +VL53L0X_Error VL53L0X_get_ref_calibration(VL53L0X_DEV Dev, + uint8_t *pVhvSettings, uint8_t *pPhaseCal); + + + + +#ifdef __cplusplus +} +#endif + +#endif /* _VL53L0X_API_CALIBRATION_H_ */ diff --git a/lib/vl53l0x/vl53l0x_api_core.c b/lib/vl53l0x/vl53l0x_api_core.c new file mode 100644 index 0000000..3933a00 --- /dev/null +++ b/lib/vl53l0x/vl53l0x_api_core.c @@ -0,0 +1,2128 @@ +/******************************************************************************* + * Copyright 2016, STMicroelectronics International N.V. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of STMicroelectronics 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, FITNESS FOR A PARTICULAR PURPOSE, AND + NON-INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS ARE DISCLAIMED. + IN NO EVENT SHALL STMICROELECTRONICS INTERNATIONAL N.V. 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 "vl53l0x_api.h" +#include "vl53l0x_api_core.h" +#include "vl53l0x_api_calibration.h" + + +#ifndef __KERNEL__ +#include +#endif +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(TRACE_MODULE_API, fmt, ##__VA_ARGS__) +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(TRACE_MODULE_API, status, ##__VA_ARGS__) +#define LOG_FUNCTION_END_FMT(status, fmt, ...) \ + _LOG_FUNCTION_END_FMT(TRACE_MODULE_API, status, fmt, ##__VA_ARGS__) + +VL53L0X_Error VL53L0X_reverse_bytes(uint8_t *data, uint32_t size) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t tempData; + uint32_t mirrorIndex; + uint32_t middle = size/2; + uint32_t index; + + for (index = 0; index < middle; index++) { + mirrorIndex = size - index - 1; + tempData = data[index]; + data[index] = data[mirrorIndex]; + data[mirrorIndex] = tempData; + } + return Status; +} + +VL53L0X_Error VL53L0X_measurement_poll_for_completion(VL53L0X_DEV Dev) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t NewDataReady = 0; + uint32_t LoopNb; + + LOG_FUNCTION_START(""); + + LoopNb = 0; + + do { + Status = VL53L0X_GetMeasurementDataReady(Dev, &NewDataReady); + if (Status != 0) + break; /* the error is set */ + + if (NewDataReady == 1) + break; /* done note that status == 0 */ + + LoopNb++; + if (LoopNb >= VL53L0X_DEFAULT_MAX_LOOP) { + Status = VL53L0X_ERROR_TIME_OUT; + break; + } + + VL53L0X_PollingDelay(Dev); + } while (1); + + LOG_FUNCTION_END(Status); + + return Status; +} + + +uint8_t VL53L0X_decode_vcsel_period(uint8_t vcsel_period_reg) +{ + /*! + * Converts the encoded VCSEL period register value into the real + * period in PLL clocks + */ + + uint8_t vcsel_period_pclks = 0; + + vcsel_period_pclks = (vcsel_period_reg + 1) << 1; + + return vcsel_period_pclks; +} + +uint8_t VL53L0X_encode_vcsel_period(uint8_t vcsel_period_pclks) +{ + /*! + * Converts the encoded VCSEL period register value into the real period + * in PLL clocks + */ + + uint8_t vcsel_period_reg = 0; + + vcsel_period_reg = (vcsel_period_pclks >> 1) - 1; + + return vcsel_period_reg; +} + + +uint32_t VL53L0X_isqrt(uint32_t num) +{ + /* + * Implements an integer square root + * + * From: http://en.wikipedia.org/wiki/Methods_of_computing_square_roots + */ + + uint32_t res = 0; + uint32_t bit = 1 << 30; + /* The second-to-top bit is set: + * 1 << 14 for 16-bits, 1 << 30 for 32 bits + */ + + /* "bit" starts at the highest power of four <= the argument. */ + while (bit > num) + bit >>= 2; + + + while (bit != 0) { + if (num >= res + bit) { + num -= res + bit; + res = (res >> 1) + bit; + } else + res >>= 1; + + bit >>= 2; + } + + return res; +} + + +uint32_t VL53L0X_quadrature_sum(uint32_t a, uint32_t b) +{ + /* + * Implements a quadrature sum + * + * rea = sqrt(a^2 + b^2) + * + * Trap overflow case max input value is 65535 (16-bit value) + * as internal calc are 32-bit wide + * + * If overflow then seta output to maximum + */ + uint32_t res = 0; + + if (a > 65535 || b > 65535) + res = 65535; + else + res = VL53L0X_isqrt(a * a + b * b); + + return res; +} + + +VL53L0X_Error VL53L0X_device_read_strobe(VL53L0X_DEV Dev) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t strobe; + uint32_t LoopNb; + + LOG_FUNCTION_START(""); + + Status |= VL53L0X_WrByte(Dev, 0x83, 0x00); + + /* polling + * use timeout to avoid deadlock + */ + if (Status == VL53L0X_ERROR_NONE) { + LoopNb = 0; + do { + Status = VL53L0X_RdByte(Dev, 0x83, &strobe); + if ((strobe != 0x00) || Status != VL53L0X_ERROR_NONE) + break; + + LoopNb = LoopNb + 1; + } while (LoopNb < VL53L0X_DEFAULT_MAX_LOOP); + + if (LoopNb >= VL53L0X_DEFAULT_MAX_LOOP) + Status = VL53L0X_ERROR_TIME_OUT; + + } + + Status |= VL53L0X_WrByte(Dev, 0x83, 0x01); + + LOG_FUNCTION_END(Status); + return Status; + +} + +VL53L0X_Error VL53L0X_get_info_from_device(VL53L0X_DEV Dev, uint8_t option) +{ + + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t byte; + uint32_t TmpDWord; + uint8_t ModuleId; + uint8_t Revision; + uint8_t ReferenceSpadCount = 0; + uint8_t ReferenceSpadType = 0; + uint32_t PartUIDUpper = 0; + uint32_t PartUIDLower = 0; + uint32_t OffsetFixed1104_mm = 0; + int16_t OffsetMicroMeters = 0; + uint32_t DistMeasTgtFixed1104_mm = 400 << 4; + uint32_t DistMeasFixed1104_400_mm = 0; + uint32_t SignalRateMeasFixed1104_400_mm = 0; + char ProductId[19]; + char *ProductId_tmp; + uint8_t ReadDataFromDeviceDone; + FixPoint1616_t SignalRateMeasFixed400mmFix = 0; + uint8_t NvmRefGoodSpadMap[VL53L0X_REF_SPAD_BUFFER_SIZE]; + int i; + + + LOG_FUNCTION_START(""); + + ReadDataFromDeviceDone = VL53L0X_GETDEVICESPECIFICPARAMETER(Dev, + ReadDataFromDeviceDone); + + /* This access is done only once after that a GetDeviceInfo or + * datainit is done + */ + if (ReadDataFromDeviceDone != 7) { + + Status |= VL53L0X_WrByte(Dev, 0x80, 0x01); + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x01); + Status |= VL53L0X_WrByte(Dev, 0x00, 0x00); + + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x06); + Status |= VL53L0X_RdByte(Dev, 0x83, &byte); + Status |= VL53L0X_WrByte(Dev, 0x83, byte|4); + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x07); + Status |= VL53L0X_WrByte(Dev, 0x81, 0x01); + + Status |= VL53L0X_PollingDelay(Dev); + + Status |= VL53L0X_WrByte(Dev, 0x80, 0x01); + + if (((option & 1) == 1) && + ((ReadDataFromDeviceDone & 1) == 0)) { + Status |= VL53L0X_WrByte(Dev, 0x94, 0x6b); + Status |= VL53L0X_device_read_strobe(Dev); + Status |= VL53L0X_RdDWord(Dev, 0x90, &TmpDWord); + + ReferenceSpadCount = (uint8_t)((TmpDWord >> 8) & 0x07f); + ReferenceSpadType = (uint8_t)((TmpDWord >> 15) & 0x01); + + Status |= VL53L0X_WrByte(Dev, 0x94, 0x24); + Status |= VL53L0X_device_read_strobe(Dev); + Status |= VL53L0X_RdDWord(Dev, 0x90, &TmpDWord); + + + NvmRefGoodSpadMap[0] = (uint8_t)((TmpDWord >> 24) + & 0xff); + NvmRefGoodSpadMap[1] = (uint8_t)((TmpDWord >> 16) + & 0xff); + NvmRefGoodSpadMap[2] = (uint8_t)((TmpDWord >> 8) + & 0xff); + NvmRefGoodSpadMap[3] = (uint8_t)(TmpDWord & 0xff); + + Status |= VL53L0X_WrByte(Dev, 0x94, 0x25); + Status |= VL53L0X_device_read_strobe(Dev); + Status |= VL53L0X_RdDWord(Dev, 0x90, &TmpDWord); + + NvmRefGoodSpadMap[4] = (uint8_t)((TmpDWord >> 24) + & 0xff); + NvmRefGoodSpadMap[5] = (uint8_t)((TmpDWord >> 16) + & 0xff); + } + + if (((option & 2) == 2) && + ((ReadDataFromDeviceDone & 2) == 0)) { + + Status |= VL53L0X_WrByte(Dev, 0x94, 0x02); + Status |= VL53L0X_device_read_strobe(Dev); + Status |= VL53L0X_RdByte(Dev, 0x90, &ModuleId); + + Status |= VL53L0X_WrByte(Dev, 0x94, 0x7B); + Status |= VL53L0X_device_read_strobe(Dev); + Status |= VL53L0X_RdByte(Dev, 0x90, &Revision); + + Status |= VL53L0X_WrByte(Dev, 0x94, 0x77); + Status |= VL53L0X_device_read_strobe(Dev); + Status |= VL53L0X_RdDWord(Dev, 0x90, &TmpDWord); + + ProductId[0] = (char)((TmpDWord >> 25) & 0x07f); + ProductId[1] = (char)((TmpDWord >> 18) & 0x07f); + ProductId[2] = (char)((TmpDWord >> 11) & 0x07f); + ProductId[3] = (char)((TmpDWord >> 4) & 0x07f); + + byte = (uint8_t)((TmpDWord & 0x00f) << 3); + + Status |= VL53L0X_WrByte(Dev, 0x94, 0x78); + Status |= VL53L0X_device_read_strobe(Dev); + Status |= VL53L0X_RdDWord(Dev, 0x90, &TmpDWord); + + ProductId[4] = (char)(byte + + ((TmpDWord >> 29) & 0x07f)); + ProductId[5] = (char)((TmpDWord >> 22) & 0x07f); + ProductId[6] = (char)((TmpDWord >> 15) & 0x07f); + ProductId[7] = (char)((TmpDWord >> 8) & 0x07f); + ProductId[8] = (char)((TmpDWord >> 1) & 0x07f); + + byte = (uint8_t)((TmpDWord & 0x001) << 6); + + Status |= VL53L0X_WrByte(Dev, 0x94, 0x79); + + Status |= VL53L0X_device_read_strobe(Dev); + + Status |= VL53L0X_RdDWord(Dev, 0x90, &TmpDWord); + + ProductId[9] = (char)(byte + + ((TmpDWord >> 26) & 0x07f)); + ProductId[10] = (char)((TmpDWord >> 19) & 0x07f); + ProductId[11] = (char)((TmpDWord >> 12) & 0x07f); + ProductId[12] = (char)((TmpDWord >> 5) & 0x07f); + + byte = (uint8_t)((TmpDWord & 0x01f) << 2); + + Status |= VL53L0X_WrByte(Dev, 0x94, 0x7A); + + Status |= VL53L0X_device_read_strobe(Dev); + + Status |= VL53L0X_RdDWord(Dev, 0x90, &TmpDWord); + + ProductId[13] = (char)(byte + + ((TmpDWord >> 30) & 0x07f)); + ProductId[14] = (char)((TmpDWord >> 23) & 0x07f); + ProductId[15] = (char)((TmpDWord >> 16) & 0x07f); + ProductId[16] = (char)((TmpDWord >> 9) & 0x07f); + ProductId[17] = (char)((TmpDWord >> 2) & 0x07f); + ProductId[18] = '\0'; + + } + + if (((option & 4) == 4) && + ((ReadDataFromDeviceDone & 4) == 0)) { + + Status |= VL53L0X_WrByte(Dev, 0x94, 0x7B); + Status |= VL53L0X_device_read_strobe(Dev); + Status |= VL53L0X_RdDWord(Dev, 0x90, &PartUIDUpper); + + Status |= VL53L0X_WrByte(Dev, 0x94, 0x7C); + Status |= VL53L0X_device_read_strobe(Dev); + Status |= VL53L0X_RdDWord(Dev, 0x90, &PartUIDLower); + + Status |= VL53L0X_WrByte(Dev, 0x94, 0x73); + Status |= VL53L0X_device_read_strobe(Dev); + Status |= VL53L0X_RdDWord(Dev, 0x90, &TmpDWord); + + SignalRateMeasFixed1104_400_mm = (TmpDWord & + 0x0000000ff) << 8; + + Status |= VL53L0X_WrByte(Dev, 0x94, 0x74); + Status |= VL53L0X_device_read_strobe(Dev); + Status |= VL53L0X_RdDWord(Dev, 0x90, &TmpDWord); + + SignalRateMeasFixed1104_400_mm |= ((TmpDWord & + 0xff000000) >> 24); + + Status |= VL53L0X_WrByte(Dev, 0x94, 0x75); + Status |= VL53L0X_device_read_strobe(Dev); + Status |= VL53L0X_RdDWord(Dev, 0x90, &TmpDWord); + + DistMeasFixed1104_400_mm = (TmpDWord & 0x0000000ff) + << 8; + + Status |= VL53L0X_WrByte(Dev, 0x94, 0x76); + Status |= VL53L0X_device_read_strobe(Dev); + Status |= VL53L0X_RdDWord(Dev, 0x90, &TmpDWord); + + DistMeasFixed1104_400_mm |= ((TmpDWord & 0xff000000) + >> 24); + } + + Status |= VL53L0X_WrByte(Dev, 0x81, 0x00); + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x06); + Status |= VL53L0X_RdByte(Dev, 0x83, &byte); + Status |= VL53L0X_WrByte(Dev, 0x83, byte&0xfb); + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x01); + Status |= VL53L0X_WrByte(Dev, 0x00, 0x01); + + Status |= VL53L0X_WrByte(Dev, 0xFF, 0x00); + Status |= VL53L0X_WrByte(Dev, 0x80, 0x00); + } + + if ((Status == VL53L0X_ERROR_NONE) && + (ReadDataFromDeviceDone != 7)) { + /* Assign to variable if status is ok */ + if (((option & 1) == 1) && + ((ReadDataFromDeviceDone & 1) == 0)) { + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, + ReferenceSpadCount, ReferenceSpadCount); + + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, + ReferenceSpadType, ReferenceSpadType); + + for (i = 0; i < VL53L0X_REF_SPAD_BUFFER_SIZE; i++) { + Dev->Data.SpadData.RefGoodSpadMap[i] = + NvmRefGoodSpadMap[i]; + } + } + + if (((option & 2) == 2) && + ((ReadDataFromDeviceDone & 2) == 0)) { + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, + ModuleId, ModuleId); + + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, + Revision, Revision); + + ProductId_tmp = VL53L0X_GETDEVICESPECIFICPARAMETER(Dev, + ProductId); + VL53L0X_COPYSTRING(ProductId_tmp, ProductId); + + } + + if (((option & 4) == 4) && + ((ReadDataFromDeviceDone & 4) == 0)) { + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, + PartUIDUpper, PartUIDUpper); + + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, + PartUIDLower, PartUIDLower); + + SignalRateMeasFixed400mmFix = + VL53L0X_FIXPOINT97TOFIXPOINT1616( + SignalRateMeasFixed1104_400_mm); + + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, + SignalRateMeasFixed400mm, + SignalRateMeasFixed400mmFix); + + OffsetMicroMeters = 0; + if (DistMeasFixed1104_400_mm != 0) { + OffsetFixed1104_mm = + DistMeasFixed1104_400_mm - + DistMeasTgtFixed1104_mm; + OffsetMicroMeters = (OffsetFixed1104_mm + * 1000) >> 4; + OffsetMicroMeters *= -1; + } + + PALDevDataSet(Dev, + Part2PartOffsetAdjustmentNVMMicroMeter, + OffsetMicroMeters); + } + byte = (uint8_t)(ReadDataFromDeviceDone|option); + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, ReadDataFromDeviceDone, + byte); + } + + LOG_FUNCTION_END(Status); + return Status; +} + + +uint32_t VL53L0X_calc_macro_period_ps(VL53L0X_DEV Dev, + uint8_t vcsel_period_pclks) +{ + uint64_t PLL_period_ps; + uint32_t macro_period_vclks; + uint32_t macro_period_ps; + + LOG_FUNCTION_START(""); + + /* The above calculation will produce rounding errors, + * therefore set fixed value + */ + PLL_period_ps = 1655; + + macro_period_vclks = 2304; + macro_period_ps = (uint32_t)(macro_period_vclks + * vcsel_period_pclks * PLL_period_ps); + + LOG_FUNCTION_END(""); + return macro_period_ps; +} + +uint16_t VL53L0X_encode_timeout(uint32_t timeout_macro_clks) +{ + /*! + * Encode timeout in macro periods in (LSByte * 2^MSByte) + 1 format + */ + + uint16_t encoded_timeout = 0; + uint32_t ls_byte = 0; + uint16_t ms_byte = 0; + + if (timeout_macro_clks > 0) { + ls_byte = timeout_macro_clks - 1; + + while ((ls_byte & 0xFFFFFF00) > 0) { + ls_byte = ls_byte >> 1; + ms_byte++; + } + + encoded_timeout = (ms_byte << 8) + + (uint16_t) (ls_byte & 0x000000FF); + } + + return encoded_timeout; + +} + +uint32_t VL53L0X_decode_timeout(uint16_t encoded_timeout) +{ + /*! + * Decode 16-bit timeout register value - format (LSByte * 2^MSByte) + 1 + */ + + uint32_t timeout_macro_clks = 0; + + timeout_macro_clks = ((uint32_t) (encoded_timeout & 0x00FF) + << (uint32_t) ((encoded_timeout & 0xFF00) >> 8)) + 1; + + return timeout_macro_clks; +} + + +/* To convert ms into register value */ +uint32_t VL53L0X_calc_timeout_mclks(VL53L0X_DEV Dev, + uint32_t timeout_period_us, + uint8_t vcsel_period_pclks) +{ + uint32_t macro_period_ps; + uint32_t macro_period_ns; + uint32_t timeout_period_mclks = 0; + + macro_period_ps = VL53L0X_calc_macro_period_ps(Dev, vcsel_period_pclks); + macro_period_ns = (macro_period_ps + 500) / 1000; + + timeout_period_mclks = + (uint32_t) (((timeout_period_us * 1000) + + (macro_period_ns / 2)) / macro_period_ns); + + return timeout_period_mclks; +} + +/* To convert register value into us */ +uint32_t VL53L0X_calc_timeout_us(VL53L0X_DEV Dev, + uint16_t timeout_period_mclks, + uint8_t vcsel_period_pclks) +{ + uint32_t macro_period_ps; + uint32_t macro_period_ns; + uint32_t actual_timeout_period_us = 0; + + macro_period_ps = VL53L0X_calc_macro_period_ps(Dev, vcsel_period_pclks); + macro_period_ns = (macro_period_ps + 500) / 1000; + + actual_timeout_period_us = + ((timeout_period_mclks * macro_period_ns) + 500) / 1000; + + return actual_timeout_period_us; +} + + +VL53L0X_Error get_sequence_step_timeout(VL53L0X_DEV Dev, + VL53L0X_SequenceStepId SequenceStepId, + uint32_t *pTimeOutMicroSecs) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t CurrentVCSELPulsePeriodPClk; + uint8_t EncodedTimeOutByte = 0; + uint32_t TimeoutMicroSeconds = 0; + uint16_t PreRangeEncodedTimeOut = 0; + uint16_t MsrcTimeOutMClks; + uint16_t PreRangeTimeOutMClks; + uint16_t FinalRangeTimeOutMClks = 0; + uint16_t FinalRangeEncodedTimeOut; + VL53L0X_SchedulerSequenceSteps_t SchedulerSequenceSteps; + + if ((SequenceStepId == VL53L0X_SEQUENCESTEP_TCC) || + (SequenceStepId == VL53L0X_SEQUENCESTEP_DSS) || + (SequenceStepId == VL53L0X_SEQUENCESTEP_MSRC)) { + + Status = VL53L0X_GetVcselPulsePeriod(Dev, + VL53L0X_VCSEL_PERIOD_PRE_RANGE, + &CurrentVCSELPulsePeriodPClk); + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_RdByte(Dev, + VL53L0X_REG_MSRC_CONFIG_TIMEOUT_MACROP, + &EncodedTimeOutByte); + } + MsrcTimeOutMClks = VL53L0X_decode_timeout(EncodedTimeOutByte); + + TimeoutMicroSeconds = VL53L0X_calc_timeout_us(Dev, + MsrcTimeOutMClks, + CurrentVCSELPulsePeriodPClk); + } else if (SequenceStepId == VL53L0X_SEQUENCESTEP_PRE_RANGE) { + /* Retrieve PRE-RANGE VCSEL Period */ + Status = VL53L0X_GetVcselPulsePeriod(Dev, + VL53L0X_VCSEL_PERIOD_PRE_RANGE, + &CurrentVCSELPulsePeriodPClk); + + /* Retrieve PRE-RANGE Timeout in Macro periods (MCLKS) */ + if (Status == VL53L0X_ERROR_NONE) { + + /* Retrieve PRE-RANGE VCSEL Period */ + Status = VL53L0X_GetVcselPulsePeriod(Dev, + VL53L0X_VCSEL_PERIOD_PRE_RANGE, + &CurrentVCSELPulsePeriodPClk); + + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_RdWord(Dev, + VL53L0X_REG_PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI, + &PreRangeEncodedTimeOut); + } + + PreRangeTimeOutMClks = VL53L0X_decode_timeout( + PreRangeEncodedTimeOut); + + TimeoutMicroSeconds = VL53L0X_calc_timeout_us(Dev, + PreRangeTimeOutMClks, + CurrentVCSELPulsePeriodPClk); + } + } else if (SequenceStepId == VL53L0X_SEQUENCESTEP_FINAL_RANGE) { + + VL53L0X_GetSequenceStepEnables(Dev, &SchedulerSequenceSteps); + PreRangeTimeOutMClks = 0; + + if (SchedulerSequenceSteps.PreRangeOn) { + /* Retrieve PRE-RANGE VCSEL Period */ + Status = VL53L0X_GetVcselPulsePeriod(Dev, + VL53L0X_VCSEL_PERIOD_PRE_RANGE, + &CurrentVCSELPulsePeriodPClk); + + /* Retrieve PRE-RANGE Timeout in Macro periods + * (MCLKS) + */ + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_RdWord(Dev, + VL53L0X_REG_PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI, + &PreRangeEncodedTimeOut); + PreRangeTimeOutMClks = VL53L0X_decode_timeout( + PreRangeEncodedTimeOut); + } + } + + if (Status == VL53L0X_ERROR_NONE) { + /* Retrieve FINAL-RANGE VCSEL Period */ + Status = VL53L0X_GetVcselPulsePeriod(Dev, + VL53L0X_VCSEL_PERIOD_FINAL_RANGE, + &CurrentVCSELPulsePeriodPClk); + } + + /* Retrieve FINAL-RANGE Timeout in Macro periods (MCLKS) */ + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_RdWord(Dev, + VL53L0X_REG_FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI, + &FinalRangeEncodedTimeOut); + FinalRangeTimeOutMClks = VL53L0X_decode_timeout( + FinalRangeEncodedTimeOut); + } + + FinalRangeTimeOutMClks -= PreRangeTimeOutMClks; + TimeoutMicroSeconds = VL53L0X_calc_timeout_us(Dev, + FinalRangeTimeOutMClks, + CurrentVCSELPulsePeriodPClk); + } + + *pTimeOutMicroSecs = TimeoutMicroSeconds; + + return Status; +} + + +VL53L0X_Error set_sequence_step_timeout(VL53L0X_DEV Dev, + VL53L0X_SequenceStepId SequenceStepId, + uint32_t TimeOutMicroSecs) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t CurrentVCSELPulsePeriodPClk; + uint8_t MsrcEncodedTimeOut; + uint16_t PreRangeEncodedTimeOut; + uint16_t PreRangeTimeOutMClks; + uint16_t MsrcRangeTimeOutMClks; + uint32_t FinalRangeTimeOutMClks; + uint16_t FinalRangeEncodedTimeOut; + VL53L0X_SchedulerSequenceSteps_t SchedulerSequenceSteps; + + if ((SequenceStepId == VL53L0X_SEQUENCESTEP_TCC) || + (SequenceStepId == VL53L0X_SEQUENCESTEP_DSS) || + (SequenceStepId == VL53L0X_SEQUENCESTEP_MSRC)) { + + Status = VL53L0X_GetVcselPulsePeriod(Dev, + VL53L0X_VCSEL_PERIOD_PRE_RANGE, + &CurrentVCSELPulsePeriodPClk); + + if (Status == VL53L0X_ERROR_NONE) { + MsrcRangeTimeOutMClks = VL53L0X_calc_timeout_mclks(Dev, + TimeOutMicroSecs, + (uint8_t)CurrentVCSELPulsePeriodPClk); + + if (MsrcRangeTimeOutMClks > 256) + MsrcEncodedTimeOut = 255; + else + MsrcEncodedTimeOut = + (uint8_t)MsrcRangeTimeOutMClks - 1; + + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, + LastEncodedTimeout, + MsrcEncodedTimeOut); + } + + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_MSRC_CONFIG_TIMEOUT_MACROP, + MsrcEncodedTimeOut); + } + } else { + + if (SequenceStepId == VL53L0X_SEQUENCESTEP_PRE_RANGE) { + + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_GetVcselPulsePeriod(Dev, + VL53L0X_VCSEL_PERIOD_PRE_RANGE, + &CurrentVCSELPulsePeriodPClk); + PreRangeTimeOutMClks = + VL53L0X_calc_timeout_mclks(Dev, + TimeOutMicroSecs, + (uint8_t)CurrentVCSELPulsePeriodPClk); + PreRangeEncodedTimeOut = VL53L0X_encode_timeout( + PreRangeTimeOutMClks); + + VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, + LastEncodedTimeout, + PreRangeEncodedTimeOut); + } + + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_WrWord(Dev, + VL53L0X_REG_PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI, + PreRangeEncodedTimeOut); + } + + if (Status == VL53L0X_ERROR_NONE) { + VL53L0X_SETDEVICESPECIFICPARAMETER( + Dev, + PreRangeTimeoutMicroSecs, + TimeOutMicroSecs); + } + } else if (SequenceStepId == VL53L0X_SEQUENCESTEP_FINAL_RANGE) { + + /* For the final range timeout, the pre-range timeout + * must be added. To do this both final and pre-range + * timeouts must be expressed in macro periods MClks + * because they have different vcsel periods. + */ + + VL53L0X_GetSequenceStepEnables(Dev, + &SchedulerSequenceSteps); + PreRangeTimeOutMClks = 0; + if (SchedulerSequenceSteps.PreRangeOn) { + + /* Retrieve PRE-RANGE VCSEL Period */ + Status = VL53L0X_GetVcselPulsePeriod(Dev, + VL53L0X_VCSEL_PERIOD_PRE_RANGE, + &CurrentVCSELPulsePeriodPClk); + + /* Retrieve PRE-RANGE Timeout in Macro periods + * (MCLKS) + */ + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_RdWord(Dev, 0x51, + &PreRangeEncodedTimeOut); + PreRangeTimeOutMClks = + VL53L0X_decode_timeout( + PreRangeEncodedTimeOut); + } + } + + /* Calculate FINAL RANGE Timeout in Macro Periods + * (MCLKS) and add PRE-RANGE value + */ + if (Status == VL53L0X_ERROR_NONE) { + + Status = VL53L0X_GetVcselPulsePeriod(Dev, + VL53L0X_VCSEL_PERIOD_FINAL_RANGE, + &CurrentVCSELPulsePeriodPClk); + } + if (Status == VL53L0X_ERROR_NONE) { + + FinalRangeTimeOutMClks = + VL53L0X_calc_timeout_mclks(Dev, + TimeOutMicroSecs, + (uint8_t) CurrentVCSELPulsePeriodPClk); + + FinalRangeTimeOutMClks += PreRangeTimeOutMClks; + + FinalRangeEncodedTimeOut = + VL53L0X_encode_timeout(FinalRangeTimeOutMClks); + + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_WrWord(Dev, 0x71, + FinalRangeEncodedTimeOut); + } + + if (Status == VL53L0X_ERROR_NONE) { + VL53L0X_SETDEVICESPECIFICPARAMETER( + Dev, + FinalRangeTimeoutMicroSecs, + TimeOutMicroSecs); + } + } + } else + Status = VL53L0X_ERROR_INVALID_PARAMS; + + } + return Status; +} + +VL53L0X_Error VL53L0X_set_vcsel_pulse_period(VL53L0X_DEV Dev, + VL53L0X_VcselPeriod VcselPeriodType, uint8_t VCSELPulsePeriodPCLK) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t vcsel_period_reg; + uint8_t MinPreVcselPeriodPCLK = 12; + uint8_t MaxPreVcselPeriodPCLK = 18; + uint8_t MinFinalVcselPeriodPCLK = 8; + uint8_t MaxFinalVcselPeriodPCLK = 14; + uint32_t MeasurementTimingBudgetMicroSeconds; + uint32_t FinalRangeTimeoutMicroSeconds; + uint32_t PreRangeTimeoutMicroSeconds; + uint32_t MsrcTimeoutMicroSeconds; + uint8_t PhaseCalInt = 0; + + /* Check if valid clock period requested */ + + if ((VCSELPulsePeriodPCLK % 2) != 0) { + /* Value must be an even number */ + Status = VL53L0X_ERROR_INVALID_PARAMS; + } else if (VcselPeriodType == VL53L0X_VCSEL_PERIOD_PRE_RANGE && + (VCSELPulsePeriodPCLK < MinPreVcselPeriodPCLK || + VCSELPulsePeriodPCLK > MaxPreVcselPeriodPCLK)) { + Status = VL53L0X_ERROR_INVALID_PARAMS; + } else if (VcselPeriodType == VL53L0X_VCSEL_PERIOD_FINAL_RANGE && + (VCSELPulsePeriodPCLK < MinFinalVcselPeriodPCLK || + VCSELPulsePeriodPCLK > MaxFinalVcselPeriodPCLK)) { + + Status = VL53L0X_ERROR_INVALID_PARAMS; + } + + /* Apply specific settings for the requested clock period */ + + if (Status != VL53L0X_ERROR_NONE) + return Status; + + + if (VcselPeriodType == VL53L0X_VCSEL_PERIOD_PRE_RANGE) { + + /* Set phase check limits */ + if (VCSELPulsePeriodPCLK == 12) { + + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_PRE_RANGE_CONFIG_VALID_PHASE_HIGH, + 0x18); + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_PRE_RANGE_CONFIG_VALID_PHASE_LOW, + 0x08); + } else if (VCSELPulsePeriodPCLK == 14) { + + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_PRE_RANGE_CONFIG_VALID_PHASE_HIGH, + 0x30); + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_PRE_RANGE_CONFIG_VALID_PHASE_LOW, + 0x08); + } else if (VCSELPulsePeriodPCLK == 16) { + + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_PRE_RANGE_CONFIG_VALID_PHASE_HIGH, + 0x40); + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_PRE_RANGE_CONFIG_VALID_PHASE_LOW, + 0x08); + } else if (VCSELPulsePeriodPCLK == 18) { + + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_PRE_RANGE_CONFIG_VALID_PHASE_HIGH, + 0x50); + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_PRE_RANGE_CONFIG_VALID_PHASE_LOW, + 0x08); + } + } else if (VcselPeriodType == VL53L0X_VCSEL_PERIOD_FINAL_RANGE) { + + if (VCSELPulsePeriodPCLK == 8) { + + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, + 0x10); + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_FINAL_RANGE_CONFIG_VALID_PHASE_LOW, + 0x08); + + Status |= VL53L0X_WrByte(Dev, + VL53L0X_REG_GLOBAL_CONFIG_VCSEL_WIDTH, 0x02); + Status |= VL53L0X_WrByte(Dev, + VL53L0X_REG_ALGO_PHASECAL_CONFIG_TIMEOUT, 0x0C); + + Status |= VL53L0X_WrByte(Dev, 0xff, 0x01); + Status |= VL53L0X_WrByte(Dev, + VL53L0X_REG_ALGO_PHASECAL_LIM, + 0x30); + Status |= VL53L0X_WrByte(Dev, 0xff, 0x00); + } else if (VCSELPulsePeriodPCLK == 10) { + + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, + 0x28); + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_FINAL_RANGE_CONFIG_VALID_PHASE_LOW, + 0x08); + + Status |= VL53L0X_WrByte(Dev, + VL53L0X_REG_GLOBAL_CONFIG_VCSEL_WIDTH, 0x03); + Status |= VL53L0X_WrByte(Dev, + VL53L0X_REG_ALGO_PHASECAL_CONFIG_TIMEOUT, 0x09); + + Status |= VL53L0X_WrByte(Dev, 0xff, 0x01); + Status |= VL53L0X_WrByte(Dev, + VL53L0X_REG_ALGO_PHASECAL_LIM, + 0x20); + Status |= VL53L0X_WrByte(Dev, 0xff, 0x00); + } else if (VCSELPulsePeriodPCLK == 12) { + + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, + 0x38); + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_FINAL_RANGE_CONFIG_VALID_PHASE_LOW, + 0x08); + + Status |= VL53L0X_WrByte(Dev, + VL53L0X_REG_GLOBAL_CONFIG_VCSEL_WIDTH, 0x03); + Status |= VL53L0X_WrByte(Dev, + VL53L0X_REG_ALGO_PHASECAL_CONFIG_TIMEOUT, 0x08); + + Status |= VL53L0X_WrByte(Dev, 0xff, 0x01); + Status |= VL53L0X_WrByte(Dev, + VL53L0X_REG_ALGO_PHASECAL_LIM, + 0x20); + Status |= VL53L0X_WrByte(Dev, 0xff, 0x00); + } else if (VCSELPulsePeriodPCLK == 14) { + + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, + 0x048); + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_FINAL_RANGE_CONFIG_VALID_PHASE_LOW, + 0x08); + + Status |= VL53L0X_WrByte(Dev, + VL53L0X_REG_GLOBAL_CONFIG_VCSEL_WIDTH, 0x03); + Status |= VL53L0X_WrByte(Dev, + VL53L0X_REG_ALGO_PHASECAL_CONFIG_TIMEOUT, 0x07); + + Status |= VL53L0X_WrByte(Dev, 0xff, 0x01); + Status |= VL53L0X_WrByte(Dev, + VL53L0X_REG_ALGO_PHASECAL_LIM, + 0x20); + Status |= VL53L0X_WrByte(Dev, 0xff, 0x00); + } + } + + + /* Re-calculate and apply timeouts, in macro periods */ + + if (Status == VL53L0X_ERROR_NONE) { + vcsel_period_reg = VL53L0X_encode_vcsel_period((uint8_t) + VCSELPulsePeriodPCLK); + + /* When the VCSEL period for the pre or final range is changed, + * the corresponding timeout must be read from the device using + * the current VCSEL period, then the new VCSEL period can be + * applied. The timeout then must be written back to the device + * using the new VCSEL period. + * + * For the MSRC timeout, the same applies - this timeout being + * dependant on the pre-range vcsel period. + */ + switch (VcselPeriodType) { + case VL53L0X_VCSEL_PERIOD_PRE_RANGE: + Status = get_sequence_step_timeout(Dev, + VL53L0X_SEQUENCESTEP_PRE_RANGE, + &PreRangeTimeoutMicroSeconds); + + if (Status == VL53L0X_ERROR_NONE) + Status = get_sequence_step_timeout(Dev, + VL53L0X_SEQUENCESTEP_MSRC, + &MsrcTimeoutMicroSeconds); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_PRE_RANGE_CONFIG_VCSEL_PERIOD, + vcsel_period_reg); + + + if (Status == VL53L0X_ERROR_NONE) + Status = set_sequence_step_timeout(Dev, + VL53L0X_SEQUENCESTEP_PRE_RANGE, + PreRangeTimeoutMicroSeconds); + + + if (Status == VL53L0X_ERROR_NONE) + Status = set_sequence_step_timeout(Dev, + VL53L0X_SEQUENCESTEP_MSRC, + MsrcTimeoutMicroSeconds); + + VL53L0X_SETDEVICESPECIFICPARAMETER( + Dev, + PreRangeVcselPulsePeriod, + VCSELPulsePeriodPCLK); + break; + case VL53L0X_VCSEL_PERIOD_FINAL_RANGE: + Status = get_sequence_step_timeout(Dev, + VL53L0X_SEQUENCESTEP_FINAL_RANGE, + &FinalRangeTimeoutMicroSeconds); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_WrByte(Dev, + VL53L0X_REG_FINAL_RANGE_CONFIG_VCSEL_PERIOD, + vcsel_period_reg); + + + if (Status == VL53L0X_ERROR_NONE) + Status = set_sequence_step_timeout(Dev, + VL53L0X_SEQUENCESTEP_FINAL_RANGE, + FinalRangeTimeoutMicroSeconds); + + VL53L0X_SETDEVICESPECIFICPARAMETER( + Dev, + FinalRangeVcselPulsePeriod, + VCSELPulsePeriodPCLK); + break; + default: + Status = VL53L0X_ERROR_INVALID_PARAMS; + } + } + + /* Finally, the timing budget must be re-applied */ + if (Status == VL53L0X_ERROR_NONE) { + VL53L0X_GETPARAMETERFIELD(Dev, + MeasurementTimingBudgetMicroSeconds, + MeasurementTimingBudgetMicroSeconds); + + Status = VL53L0X_SetMeasurementTimingBudgetMicroSeconds(Dev, + MeasurementTimingBudgetMicroSeconds); + } + + /* Perform the phase calibration. This is needed after changing on + * vcsel period. + * get_data_enable = 0, restore_config = 1 + */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_perform_phase_calibration( + Dev, &PhaseCalInt, 0, 1); + + return Status; +} + +VL53L0X_Error VL53L0X_get_vcsel_pulse_period(VL53L0X_DEV Dev, + VL53L0X_VcselPeriod VcselPeriodType, uint8_t *pVCSELPulsePeriodPCLK) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t vcsel_period_reg; + + switch (VcselPeriodType) { + case VL53L0X_VCSEL_PERIOD_PRE_RANGE: + Status = VL53L0X_RdByte(Dev, + VL53L0X_REG_PRE_RANGE_CONFIG_VCSEL_PERIOD, + &vcsel_period_reg); + break; + case VL53L0X_VCSEL_PERIOD_FINAL_RANGE: + Status = VL53L0X_RdByte(Dev, + VL53L0X_REG_FINAL_RANGE_CONFIG_VCSEL_PERIOD, + &vcsel_period_reg); + break; + default: + Status = VL53L0X_ERROR_INVALID_PARAMS; + } + + if (Status == VL53L0X_ERROR_NONE) + *pVCSELPulsePeriodPCLK = + VL53L0X_decode_vcsel_period(vcsel_period_reg); + + return Status; +} + + + +VL53L0X_Error VL53L0X_set_measurement_timing_budget_micro_seconds( + VL53L0X_DEV Dev, + uint32_t MeasurementTimingBudgetMicroSeconds) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint32_t FinalRangeTimingBudgetMicroSeconds; + VL53L0X_SchedulerSequenceSteps_t SchedulerSequenceSteps; + uint32_t MsrcDccTccTimeoutMicroSeconds = 2000; + uint32_t StartOverheadMicroSeconds = 1910; + uint32_t EndOverheadMicroSeconds = 960; + uint32_t MsrcOverheadMicroSeconds = 660; + uint32_t TccOverheadMicroSeconds = 590; + uint32_t DssOverheadMicroSeconds = 690; + uint32_t PreRangeOverheadMicroSeconds = 660; + uint32_t FinalRangeOverheadMicroSeconds = 550; + uint32_t PreRangeTimeoutMicroSeconds = 0; + uint32_t SubTimeout = 0; + + LOG_FUNCTION_START(""); + + FinalRangeTimingBudgetMicroSeconds = + MeasurementTimingBudgetMicroSeconds - + (StartOverheadMicroSeconds + EndOverheadMicroSeconds); + + Status = VL53L0X_GetSequenceStepEnables(Dev, &SchedulerSequenceSteps); + + if (Status == VL53L0X_ERROR_NONE && + (SchedulerSequenceSteps.TccOn || + SchedulerSequenceSteps.MsrcOn || + SchedulerSequenceSteps.DssOn)) { + + /* TCC, MSRC and DSS all share the same timeout */ + Status = get_sequence_step_timeout(Dev, + VL53L0X_SEQUENCESTEP_MSRC, + &MsrcDccTccTimeoutMicroSeconds); + + /* Subtract the TCC, MSRC and DSS timeouts if they are + * enabled. + */ + + if (Status != VL53L0X_ERROR_NONE) + return Status; + + /* TCC */ + if (SchedulerSequenceSteps.TccOn) { + + SubTimeout = MsrcDccTccTimeoutMicroSeconds + + TccOverheadMicroSeconds; + + if (SubTimeout < + FinalRangeTimingBudgetMicroSeconds) { + FinalRangeTimingBudgetMicroSeconds -= + SubTimeout; + } else { + /* Requested timeout too big. */ + Status = VL53L0X_ERROR_INVALID_PARAMS; + } + } + + if (Status != VL53L0X_ERROR_NONE) { + LOG_FUNCTION_END(Status); + return Status; + } + + /* DSS */ + if (SchedulerSequenceSteps.DssOn) { + + SubTimeout = 2 * (MsrcDccTccTimeoutMicroSeconds + + DssOverheadMicroSeconds); + + if (SubTimeout < FinalRangeTimingBudgetMicroSeconds) { + FinalRangeTimingBudgetMicroSeconds + -= SubTimeout; + } else { + /* Requested timeout too big. */ + Status = VL53L0X_ERROR_INVALID_PARAMS; + } + } else if (SchedulerSequenceSteps.MsrcOn) { + /* MSRC */ + SubTimeout = MsrcDccTccTimeoutMicroSeconds + + MsrcOverheadMicroSeconds; + + if (SubTimeout < FinalRangeTimingBudgetMicroSeconds) { + FinalRangeTimingBudgetMicroSeconds + -= SubTimeout; + } else { + /* Requested timeout too big. */ + Status = VL53L0X_ERROR_INVALID_PARAMS; + } + } + + } + + if (Status != VL53L0X_ERROR_NONE) { + LOG_FUNCTION_END(Status); + return Status; + } + + if (SchedulerSequenceSteps.PreRangeOn) { + + /* Subtract the Pre-range timeout if enabled. */ + + Status = get_sequence_step_timeout(Dev, + VL53L0X_SEQUENCESTEP_PRE_RANGE, + &PreRangeTimeoutMicroSeconds); + + SubTimeout = PreRangeTimeoutMicroSeconds + + PreRangeOverheadMicroSeconds; + + if (SubTimeout < FinalRangeTimingBudgetMicroSeconds) { + FinalRangeTimingBudgetMicroSeconds -= SubTimeout; + } else { + /* Requested timeout too big. */ + Status = VL53L0X_ERROR_INVALID_PARAMS; + } + } + + + if (Status == VL53L0X_ERROR_NONE && + SchedulerSequenceSteps.FinalRangeOn) { + + FinalRangeTimingBudgetMicroSeconds -= + FinalRangeOverheadMicroSeconds; + + /* Final Range Timeout + * Note that the final range timeout is determined by the timing + * budget and the sum of all other timeouts within the sequence. + * If there is no room for the final range timeout, then an + * error will be set. Otherwise the remaining time will be + * applied to the final range. + */ + Status = set_sequence_step_timeout(Dev, + VL53L0X_SEQUENCESTEP_FINAL_RANGE, + FinalRangeTimingBudgetMicroSeconds); + + VL53L0X_SETPARAMETERFIELD(Dev, + MeasurementTimingBudgetMicroSeconds, + MeasurementTimingBudgetMicroSeconds); + } + + LOG_FUNCTION_END(Status); + + return Status; +} + +VL53L0X_Error VL53L0X_get_measurement_timing_budget_micro_seconds( + VL53L0X_DEV Dev, + uint32_t *pMeasurementTimingBudgetMicroSeconds) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + VL53L0X_SchedulerSequenceSteps_t SchedulerSequenceSteps; + uint32_t FinalRangeTimeoutMicroSeconds; + uint32_t MsrcDccTccTimeoutMicroSeconds = 2000; + uint32_t StartOverheadMicroSeconds = 1910; + uint32_t EndOverheadMicroSeconds = 960; + uint32_t MsrcOverheadMicroSeconds = 660; + uint32_t TccOverheadMicroSeconds = 590; + uint32_t DssOverheadMicroSeconds = 690; + uint32_t PreRangeOverheadMicroSeconds = 660; + uint32_t FinalRangeOverheadMicroSeconds = 550; + uint32_t PreRangeTimeoutMicroSeconds = 0; + + LOG_FUNCTION_START(""); + + /* Start and end overhead times always present */ + *pMeasurementTimingBudgetMicroSeconds + = StartOverheadMicroSeconds + EndOverheadMicroSeconds; + + Status = VL53L0X_GetSequenceStepEnables(Dev, &SchedulerSequenceSteps); + + if (Status != VL53L0X_ERROR_NONE) { + LOG_FUNCTION_END(Status); + return Status; + } + + + if (SchedulerSequenceSteps.TccOn || + SchedulerSequenceSteps.MsrcOn || + SchedulerSequenceSteps.DssOn) { + + Status = get_sequence_step_timeout(Dev, + VL53L0X_SEQUENCESTEP_MSRC, + &MsrcDccTccTimeoutMicroSeconds); + + if (Status == VL53L0X_ERROR_NONE) { + if (SchedulerSequenceSteps.TccOn) { + *pMeasurementTimingBudgetMicroSeconds += + MsrcDccTccTimeoutMicroSeconds + + TccOverheadMicroSeconds; + } + + if (SchedulerSequenceSteps.DssOn) { + *pMeasurementTimingBudgetMicroSeconds += + 2 * (MsrcDccTccTimeoutMicroSeconds + + DssOverheadMicroSeconds); + } else if (SchedulerSequenceSteps.MsrcOn) { + *pMeasurementTimingBudgetMicroSeconds += + MsrcDccTccTimeoutMicroSeconds + + MsrcOverheadMicroSeconds; + } + } + } + + if (Status == VL53L0X_ERROR_NONE) { + if (SchedulerSequenceSteps.PreRangeOn) { + Status = get_sequence_step_timeout(Dev, + VL53L0X_SEQUENCESTEP_PRE_RANGE, + &PreRangeTimeoutMicroSeconds); + *pMeasurementTimingBudgetMicroSeconds += + PreRangeTimeoutMicroSeconds + + PreRangeOverheadMicroSeconds; + } + } + + if (Status == VL53L0X_ERROR_NONE) { + if (SchedulerSequenceSteps.FinalRangeOn) { + Status = get_sequence_step_timeout(Dev, + VL53L0X_SEQUENCESTEP_FINAL_RANGE, + &FinalRangeTimeoutMicroSeconds); + *pMeasurementTimingBudgetMicroSeconds += + (FinalRangeTimeoutMicroSeconds + + FinalRangeOverheadMicroSeconds); + } + } + + if (Status == VL53L0X_ERROR_NONE) { + VL53L0X_SETPARAMETERFIELD(Dev, + MeasurementTimingBudgetMicroSeconds, + *pMeasurementTimingBudgetMicroSeconds); + } + + LOG_FUNCTION_END(Status); + return Status; +} + + + +VL53L0X_Error VL53L0X_load_tuning_settings(VL53L0X_DEV Dev, + uint8_t *pTuningSettingBuffer) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + int i; + int Index; + uint8_t msb; + uint8_t lsb; + uint8_t SelectParam; + uint8_t NumberOfWrites; + uint8_t Address; + uint8_t localBuffer[4]; /* max */ + uint16_t Temp16; + + LOG_FUNCTION_START(""); + + Index = 0; + + while ((*(pTuningSettingBuffer + Index) != 0) && + (Status == VL53L0X_ERROR_NONE)) { + NumberOfWrites = *(pTuningSettingBuffer + Index); + Index++; + if (NumberOfWrites == 0xFF) { + /* internal parameters */ + SelectParam = *(pTuningSettingBuffer + Index); + Index++; + switch (SelectParam) { + case 0: /* uint16_t SigmaEstRefArray -> 2 bytes */ + msb = *(pTuningSettingBuffer + Index); + Index++; + lsb = *(pTuningSettingBuffer + Index); + Index++; + Temp16 = VL53L0X_MAKEUINT16(lsb, msb); + PALDevDataSet(Dev, SigmaEstRefArray, Temp16); + break; + case 1: /* uint16_t SigmaEstEffPulseWidth -> 2 bytes */ + msb = *(pTuningSettingBuffer + Index); + Index++; + lsb = *(pTuningSettingBuffer + Index); + Index++; + Temp16 = VL53L0X_MAKEUINT16(lsb, msb); + PALDevDataSet(Dev, SigmaEstEffPulseWidth, + Temp16); + break; + case 2: /* uint16_t SigmaEstEffAmbWidth -> 2 bytes */ + msb = *(pTuningSettingBuffer + Index); + Index++; + lsb = *(pTuningSettingBuffer + Index); + Index++; + Temp16 = VL53L0X_MAKEUINT16(lsb, msb); + PALDevDataSet(Dev, SigmaEstEffAmbWidth, Temp16); + break; + case 3: /* uint16_t targetRefRate -> 2 bytes */ + msb = *(pTuningSettingBuffer + Index); + Index++; + lsb = *(pTuningSettingBuffer + Index); + Index++; + Temp16 = VL53L0X_MAKEUINT16(lsb, msb); + PALDevDataSet(Dev, targetRefRate, Temp16); + break; + default: /* invalid parameter */ + Status = VL53L0X_ERROR_INVALID_PARAMS; + } + + } else if (NumberOfWrites <= 4) { + Address = *(pTuningSettingBuffer + Index); + Index++; + + for (i = 0; i < NumberOfWrites; i++) { + localBuffer[i] = *(pTuningSettingBuffer + + Index); + Index++; + } + + Status = VL53L0X_WriteMulti(Dev, Address, localBuffer, + NumberOfWrites); + + } else { + Status = VL53L0X_ERROR_INVALID_PARAMS; + } + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_get_total_xtalk_rate(VL53L0X_DEV Dev, + VL53L0X_RangingMeasurementData_t *pRangingMeasurementData, + FixPoint1616_t *ptotal_xtalk_rate_mcps) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + + uint8_t xtalkCompEnable; + FixPoint1616_t totalXtalkMegaCps; + FixPoint1616_t xtalkPerSpadMegaCps; + + *ptotal_xtalk_rate_mcps = 0; + + Status = VL53L0X_GetXTalkCompensationEnable(Dev, &xtalkCompEnable); + if (Status == VL53L0X_ERROR_NONE) { + + if (xtalkCompEnable) { + + VL53L0X_GETPARAMETERFIELD( + Dev, + XTalkCompensationRateMegaCps, + xtalkPerSpadMegaCps); + + /* FixPoint1616 * FixPoint 8:8 = FixPoint0824 */ + totalXtalkMegaCps = + pRangingMeasurementData->EffectiveSpadRtnCount * + xtalkPerSpadMegaCps; + + /* FixPoint0824 >> 8 = FixPoint1616 */ + *ptotal_xtalk_rate_mcps = + (totalXtalkMegaCps + 0x80) >> 8; + } + } + + return Status; +} + +VL53L0X_Error VL53L0X_get_total_signal_rate(VL53L0X_DEV Dev, + VL53L0X_RangingMeasurementData_t *pRangingMeasurementData, + FixPoint1616_t *ptotal_signal_rate_mcps) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + FixPoint1616_t totalXtalkMegaCps; + + LOG_FUNCTION_START(""); + + *ptotal_signal_rate_mcps = + pRangingMeasurementData->SignalRateRtnMegaCps; + + Status = VL53L0X_get_total_xtalk_rate( + Dev, pRangingMeasurementData, &totalXtalkMegaCps); + + if (Status == VL53L0X_ERROR_NONE) + *ptotal_signal_rate_mcps += totalXtalkMegaCps; + + return Status; +} + +VL53L0X_Error get_dmax_lut_points(VL53L0X_DMaxLUT_t data, uint32_t lut_size, + FixPoint1616_t input, int32_t *index0, int32_t *index1){ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + FixPoint1616_t index0_tmp = 0; + FixPoint1616_t index1_tmp = 0; + int index = 0; + + for (index = 0; index < lut_size; index++) { + if (input <= data.ambRate_mcps[index]) { + index1_tmp = index; + break; + } + } + + if (index == lut_size) { + /* input is higher than last x point */ + index0_tmp = index1_tmp = lut_size - 1; + } else if (index1_tmp == 0) { + /* input is lower than first x point */ + index0_tmp = 0; + } else{ + /* input is in between 2 points */ + index0_tmp = index1_tmp - 1; + } + + *index0 = index0_tmp; + *index1 = index1_tmp; + + return Status; +} + +VL53L0X_Error VL53L0X_calc_dmax( + VL53L0X_DEV Dev, FixPoint1616_t ambRateMeas, uint32_t *pdmax_mm){ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + VL53L0X_DeviceParameters_t CurrentParameters; + int32_t index0 = 0; + int32_t index1 = 0; + FixPoint1616_t amb0, amb1, dmax0, dmax1; + FixPoint1616_t dmax_mm; + FixPoint1616_t linearSlope; + + LOG_FUNCTION_START(""); + + Status = VL53L0X_GetDeviceParameters(Dev, &CurrentParameters); + + if (ambRateMeas <= CurrentParameters.dmax_lut.ambRate_mcps[0]) { + dmax_mm = CurrentParameters.dmax_lut.dmax_mm[0]; + } else if (ambRateMeas >= + CurrentParameters.dmax_lut. + ambRate_mcps[VL53L0X_DMAX_LUT_SIZE - 1]) { + dmax_mm = + CurrentParameters.dmax_lut.dmax_mm[VL53L0X_DMAX_LUT_SIZE - + 1]; + } else{ + get_dmax_lut_points(CurrentParameters.dmax_lut, + VL53L0X_DMAX_LUT_SIZE, ambRateMeas, &index0, &index1); + + if (index0 == index1) { + dmax_mm = CurrentParameters.dmax_lut.dmax_mm[index0]; + } else { + amb0 = CurrentParameters.dmax_lut.ambRate_mcps[index0]; + amb1 = CurrentParameters.dmax_lut.ambRate_mcps[index1]; + dmax0 = CurrentParameters.dmax_lut.dmax_mm[index0]; + dmax1 = CurrentParameters.dmax_lut.dmax_mm[index1]; + if ((amb1 - amb0) != 0) { + /* Fix16:16/Fix16:8 => Fix16:8 */ + linearSlope = (dmax0-dmax1)/((amb1-amb0) >> 8); + + /* Fix16:8 * Fix16:8 => Fix16:16 */ + dmax_mm = + (((amb1 - + ambRateMeas) >> 8) * linearSlope) + + dmax1; + } else{ + dmax_mm = dmax0; + } + } + } + *pdmax_mm = (uint32_t)(dmax_mm >> 16); + + LOG_FUNCTION_END(Status); + + return Status; +} + +VL53L0X_Error VL53L0X_calc_sigma_estimate(VL53L0X_DEV Dev, + VL53L0X_RangingMeasurementData_t *pRangingMeasurementData, + FixPoint1616_t *pSigmaEstimate) +{ + /* Expressed in 100ths of a ns, i.e. centi-ns */ + const uint32_t cPulseEffectiveWidth_centi_ns = 800; + /* Expressed in 100ths of a ns, i.e. centi-ns */ + const uint32_t cAmbientEffectiveWidth_centi_ns = 600; + const FixPoint1616_t cDfltFinalRangeIntegrationTimeMilliSecs = + 0x00190000; /* 25ms */ + const uint32_t cVcselPulseWidth_ps = 4700; /* pico secs */ + const FixPoint1616_t cSigmaEstMax = 0x028F87AE; + const FixPoint1616_t cSigmaEstRtnMax = 0xF000; + const FixPoint1616_t cAmbToSignalRatioMax = 0xF0000000/ + cAmbientEffectiveWidth_centi_ns; + /* Time Of Flight per mm (6.6 pico secs) */ + const FixPoint1616_t cTOF_per_mm_ps = 0x0006999A; + const uint32_t c16BitRoundingParam = 0x00008000; + const FixPoint1616_t cMaxXTalk_kcps = 0x00320000; + const uint32_t cPllPeriod_ps = 1655; + + uint32_t vcselTotalEventsRtn; + uint32_t finalRangeTimeoutMicroSecs; + uint32_t preRangeTimeoutMicroSecs; + uint32_t finalRangeIntegrationTimeMilliSecs; + FixPoint1616_t sigmaEstimateP1; + FixPoint1616_t sigmaEstimateP2; + FixPoint1616_t sigmaEstimateP3; + FixPoint1616_t deltaT_ps; + FixPoint1616_t pwMult; + FixPoint1616_t sigmaEstRtn; + FixPoint1616_t sigmaEstimate; + FixPoint1616_t xTalkCorrection; + FixPoint1616_t ambientRate_kcps; + FixPoint1616_t peakSignalRate_kcps; + FixPoint1616_t xTalkCompRate_mcps; + uint32_t xTalkCompRate_kcps; + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + FixPoint1616_t diff1_mcps; + FixPoint1616_t diff2_mcps; + FixPoint1616_t sqr1; + FixPoint1616_t sqr2; + FixPoint1616_t sqrSum; + FixPoint1616_t sqrtResult_centi_ns; + FixPoint1616_t sqrtResult; + FixPoint1616_t totalSignalRate_mcps; + FixPoint1616_t sigmaEstRef; + uint32_t vcselWidth; + uint32_t finalRangeMacroPCLKS; + uint32_t preRangeMacroPCLKS; + uint32_t peakVcselDuration_us; + uint8_t finalRangeVcselPCLKS; + uint8_t preRangeVcselPCLKS; + /*! \addtogroup calc_sigma_estimate + * @{ + * + * Estimates the range sigma + */ + + LOG_FUNCTION_START(""); + + VL53L0X_GETPARAMETERFIELD(Dev, XTalkCompensationRateMegaCps, + xTalkCompRate_mcps); + + /* + * We work in kcps rather than mcps as this helps keep within the + * confines of the 32 Fix1616 type. + */ + + ambientRate_kcps = + (pRangingMeasurementData->AmbientRateRtnMegaCps * 1000) >> 16; + + Status = VL53L0X_get_total_signal_rate( + Dev, pRangingMeasurementData, &totalSignalRate_mcps); + Status = VL53L0X_get_total_xtalk_rate( + Dev, pRangingMeasurementData, &xTalkCompRate_mcps); + + + /* Signal rate measurement provided by device is the + * peak signal rate, not average. + */ + peakSignalRate_kcps = (totalSignalRate_mcps * 1000); + peakSignalRate_kcps = (peakSignalRate_kcps + 0x8000) >> 16; + + xTalkCompRate_kcps = xTalkCompRate_mcps * 1000; + + if (xTalkCompRate_kcps > cMaxXTalk_kcps) + xTalkCompRate_kcps = cMaxXTalk_kcps; + + if (Status == VL53L0X_ERROR_NONE) { + + /* Calculate final range macro periods */ + finalRangeTimeoutMicroSecs = VL53L0X_GETDEVICESPECIFICPARAMETER( + Dev, FinalRangeTimeoutMicroSecs); + + finalRangeVcselPCLKS = VL53L0X_GETDEVICESPECIFICPARAMETER( + Dev, FinalRangeVcselPulsePeriod); + + finalRangeMacroPCLKS = VL53L0X_calc_timeout_mclks( + Dev, finalRangeTimeoutMicroSecs, finalRangeVcselPCLKS); + + /* Calculate pre-range macro periods */ + preRangeTimeoutMicroSecs = VL53L0X_GETDEVICESPECIFICPARAMETER( + Dev, PreRangeTimeoutMicroSecs); + + preRangeVcselPCLKS = VL53L0X_GETDEVICESPECIFICPARAMETER( + Dev, PreRangeVcselPulsePeriod); + + preRangeMacroPCLKS = VL53L0X_calc_timeout_mclks( + Dev, preRangeTimeoutMicroSecs, preRangeVcselPCLKS); + + vcselWidth = 3; + if (finalRangeVcselPCLKS == 8) + vcselWidth = 2; + + + peakVcselDuration_us = vcselWidth * 2048 * + (preRangeMacroPCLKS + finalRangeMacroPCLKS); + peakVcselDuration_us = (peakVcselDuration_us + 500)/1000; + peakVcselDuration_us *= cPllPeriod_ps; + peakVcselDuration_us = (peakVcselDuration_us + 500)/1000; + + /* Fix1616 >> 8 = Fix2408 */ + totalSignalRate_mcps = (totalSignalRate_mcps + 0x80) >> 8; + + /* Fix2408 * uint32 = Fix2408 */ + vcselTotalEventsRtn = totalSignalRate_mcps * + peakVcselDuration_us; + + /* Fix2408 >> 8 = uint32 */ + vcselTotalEventsRtn = (vcselTotalEventsRtn + 0x80) >> 8; + + /* Fix2408 << 8 = Fix1616 = */ + totalSignalRate_mcps <<= 8; + } + + if (Status != VL53L0X_ERROR_NONE) { + LOG_FUNCTION_END(Status); + return Status; + } + + if (peakSignalRate_kcps == 0) { + *pSigmaEstimate = cSigmaEstMax; + PALDevDataSet(Dev, SigmaEstimate, cSigmaEstMax); + } else { + if (vcselTotalEventsRtn < 1) + vcselTotalEventsRtn = 1; + + sigmaEstimateP1 = cPulseEffectiveWidth_centi_ns; + + /* ((FixPoint1616 << 16)* uint32)/uint32 = FixPoint1616 */ + sigmaEstimateP2 = (ambientRate_kcps << 16)/peakSignalRate_kcps; + if (sigmaEstimateP2 > cAmbToSignalRatioMax) { + /* Clip to prevent overflow. Will ensure safe + * max result. + */ + sigmaEstimateP2 = cAmbToSignalRatioMax; + } + sigmaEstimateP2 *= cAmbientEffectiveWidth_centi_ns; + + sigmaEstimateP3 = 2 * VL53L0X_isqrt(vcselTotalEventsRtn * 12); + + /* uint32 * FixPoint1616 = FixPoint1616 */ + deltaT_ps = pRangingMeasurementData->RangeMilliMeter * + cTOF_per_mm_ps; + + /* + * vcselRate - xtalkCompRate + * (uint32 << 16) - FixPoint1616 = FixPoint1616. + * Divide result by 1000 to convert to mcps. + * 500 is added to ensure rounding when integer division + * truncates. + */ + diff1_mcps = (((peakSignalRate_kcps << 16) - + 2 * xTalkCompRate_kcps) + 500)/1000; + + /* vcselRate + xtalkCompRate */ + diff2_mcps = ((peakSignalRate_kcps << 16) + 500)/1000; + + /* Shift by 8 bits to increase resolution prior to the + * division + */ + diff1_mcps <<= 8; + + /* FixPoint0824/FixPoint1616 = FixPoint2408 */ + xTalkCorrection = abs(diff1_mcps/diff2_mcps); + + /* FixPoint2408 << 8 = FixPoint1616 */ + xTalkCorrection <<= 8; + + if (pRangingMeasurementData->RangeStatus != 0) { + pwMult = 1 << 16; + } else { + /* FixPoint1616/uint32 = FixPoint1616 */ + /* smaller than 1.0f */ + pwMult = deltaT_ps/cVcselPulseWidth_ps; + + /* + * FixPoint1616 * FixPoint1616 = FixPoint3232, however + * both values are small enough such that32 bits will + * not be exceeded. + */ + pwMult *= ((1 << 16) - xTalkCorrection); + + /* (FixPoint3232 >> 16) = FixPoint1616 */ + pwMult = (pwMult + c16BitRoundingParam) >> 16; + + /* FixPoint1616 + FixPoint1616 = FixPoint1616 */ + pwMult += (1 << 16); + + /* + * At this point the value will be 1.xx, therefore if we + * square the value this will exceed 32 bits. To address + * this perform a single shift to the right before the + * multiplication. + */ + pwMult >>= 1; + /* FixPoint1715 * FixPoint1715 = FixPoint3430 */ + pwMult = pwMult * pwMult; + + /* (FixPoint3430 >> 14) = Fix1616 */ + pwMult >>= 14; + } + + /* FixPoint1616 * uint32 = FixPoint1616 */ + sqr1 = pwMult * sigmaEstimateP1; + + /* (FixPoint1616 >> 16) = FixPoint3200 */ + sqr1 = (sqr1 + 0x8000) >> 16; + + /* FixPoint3200 * FixPoint3200 = FixPoint6400 */ + sqr1 *= sqr1; + + sqr2 = sigmaEstimateP2; + + /* (FixPoint1616 >> 16) = FixPoint3200 */ + sqr2 = (sqr2 + 0x8000) >> 16; + + /* FixPoint3200 * FixPoint3200 = FixPoint6400 */ + sqr2 *= sqr2; + + /* FixPoint64000 + FixPoint6400 = FixPoint6400 */ + sqrSum = sqr1 + sqr2; + + /* SQRT(FixPoin6400) = FixPoint3200 */ + sqrtResult_centi_ns = VL53L0X_isqrt(sqrSum); + + /* (FixPoint3200 << 16) = FixPoint1616 */ + sqrtResult_centi_ns <<= 16; + + /* + * Note that the Speed Of Light is expressed in um per 1E-10 + * seconds (2997) Therefore to get mm/ns we have to divide by + * 10000 + */ + sigmaEstRtn = (((sqrtResult_centi_ns+50)/100) / + sigmaEstimateP3); + sigmaEstRtn *= VL53L0X_SPEED_OF_LIGHT_IN_AIR; + + /* Add 5000 before dividing by 10000 to ensure rounding. */ + sigmaEstRtn += 5000; + sigmaEstRtn /= 10000; + + if (sigmaEstRtn > cSigmaEstRtnMax) { + /* Clip to prevent overflow. Will ensure safe + * max result. + */ + sigmaEstRtn = cSigmaEstRtnMax; + } + finalRangeIntegrationTimeMilliSecs = + (finalRangeTimeoutMicroSecs + preRangeTimeoutMicroSecs + + 500) / 1000; + + /* sigmaEstRef = 1mm * 25ms/final range integration time + * (inc pre-range) + * sqrt(FixPoint1616/int) = FixPoint2408) + */ + sigmaEstRef = + VL53L0X_isqrt((cDfltFinalRangeIntegrationTimeMilliSecs + + finalRangeIntegrationTimeMilliSecs/2)/ + finalRangeIntegrationTimeMilliSecs); + + /* FixPoint2408 << 8 = FixPoint1616 */ + sigmaEstRef <<= 8; + sigmaEstRef = (sigmaEstRef + 500)/1000; + + /* FixPoint1616 * FixPoint1616 = FixPoint3232 */ + sqr1 = sigmaEstRtn * sigmaEstRtn; + /* FixPoint1616 * FixPoint1616 = FixPoint3232 */ + sqr2 = sigmaEstRef * sigmaEstRef; + + /* sqrt(FixPoint3232) = FixPoint1616 */ + sqrtResult = VL53L0X_isqrt((sqr1 + sqr2)); + /* + * Note that the Shift by 4 bits increases resolution prior to + * the sqrt, therefore the result must be shifted by 2 bits to + * the right to revert back to the FixPoint1616 format. + */ + + sigmaEstimate = 1000 * sqrtResult; + + if ((peakSignalRate_kcps < 1) || (vcselTotalEventsRtn < 1) || + (sigmaEstimate > cSigmaEstMax)) { + sigmaEstimate = cSigmaEstMax; + } + + *pSigmaEstimate = (uint32_t)(sigmaEstimate); + PALDevDataSet(Dev, SigmaEstimate, *pSigmaEstimate); + } + + LOG_FUNCTION_END(Status); + return Status; +} + +VL53L0X_Error VL53L0X_get_pal_range_status(VL53L0X_DEV Dev, + uint8_t DeviceRangeStatus, + FixPoint1616_t SignalRate, + uint16_t EffectiveSpadRtnCount, + VL53L0X_RangingMeasurementData_t *pRangingMeasurementData, + uint8_t *pPalRangeStatus) +{ + VL53L0X_Error Status = VL53L0X_ERROR_NONE; + uint8_t NoneFlag; + uint8_t SigmaLimitflag = 0; + uint8_t SignalRefClipflag = 0; + uint8_t RangeIgnoreThresholdflag = 0; + uint8_t SigmaLimitCheckEnable = 0; + uint8_t SignalRateFinalRangeLimitCheckEnable = 0; + uint8_t SignalRefClipLimitCheckEnable = 0; + uint8_t RangeIgnoreThresholdLimitCheckEnable = 0; + FixPoint1616_t SigmaEstimate; + FixPoint1616_t SigmaLimitValue; + FixPoint1616_t SignalRefClipValue; + FixPoint1616_t RangeIgnoreThresholdValue; + FixPoint1616_t SignalRatePerSpad; + uint8_t DeviceRangeStatusInternal = 0; + uint16_t tmpWord = 0; + uint8_t Temp8; + uint32_t Dmax_mm = 0; + FixPoint1616_t LastSignalRefMcps; + + LOG_FUNCTION_START(""); + + + /* + * VL53L0X has a good ranging when the value of the + * DeviceRangeStatus = 11. This function will replace the value 0 with + * the value 11 in the DeviceRangeStatus. + * In addition, the SigmaEstimator is not included in the VL53L0X + * DeviceRangeStatus, this will be added in the PalRangeStatus. + */ + + DeviceRangeStatusInternal = ((DeviceRangeStatus & 0x78) >> 3); + + if (DeviceRangeStatusInternal == 0 || + DeviceRangeStatusInternal == 5 || + DeviceRangeStatusInternal == 7 || + DeviceRangeStatusInternal == 12 || + DeviceRangeStatusInternal == 13 || + DeviceRangeStatusInternal == 14 || + DeviceRangeStatusInternal == 15 + ) { + NoneFlag = 1; + } else { + NoneFlag = 0; + } + + /* + * Check if Sigma limit is enabled, if yes then do comparison with limit + * value and put the result back into pPalRangeStatus. + */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_GetLimitCheckEnable(Dev, + VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE, + &SigmaLimitCheckEnable); + + if ((SigmaLimitCheckEnable != 0) && (Status == VL53L0X_ERROR_NONE)) { + /* + * compute the Sigma and check with limit + */ + Status = VL53L0X_calc_sigma_estimate( + Dev, + pRangingMeasurementData, + &SigmaEstimate); + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_calc_dmax( + Dev, + pRangingMeasurementData->AmbientRateRtnMegaCps, + &Dmax_mm); + if (Status == VL53L0X_ERROR_NONE) + pRangingMeasurementData->RangeDMaxMilliMeter = Dmax_mm; + + if (Status == VL53L0X_ERROR_NONE) { + Status = VL53L0X_GetLimitCheckValue(Dev, + VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE, + &SigmaLimitValue); + + if ((SigmaLimitValue > 0) && + (SigmaEstimate > SigmaLimitValue)) + /* Limit Fail */ + SigmaLimitflag = 1; + } + } + + /* + * Check if Signal ref clip limit is enabled, if yes then do comparison + * with limit value and put the result back into pPalRangeStatus. + */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_GetLimitCheckEnable(Dev, + VL53L0X_CHECKENABLE_SIGNAL_REF_CLIP, + &SignalRefClipLimitCheckEnable); + + if ((SignalRefClipLimitCheckEnable != 0) && + (Status == VL53L0X_ERROR_NONE)) { + + Status = VL53L0X_GetLimitCheckValue(Dev, + VL53L0X_CHECKENABLE_SIGNAL_REF_CLIP, + &SignalRefClipValue); + + /* Read LastSignalRefMcps from device */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_WrByte(Dev, 0xFF, 0x01); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_RdWord(Dev, + VL53L0X_REG_RESULT_PEAK_SIGNAL_RATE_REF, + &tmpWord); + + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_WrByte(Dev, 0xFF, 0x00); + + LastSignalRefMcps = VL53L0X_FIXPOINT97TOFIXPOINT1616(tmpWord); + PALDevDataSet(Dev, LastSignalRefMcps, LastSignalRefMcps); + + if ((SignalRefClipValue > 0) && + (LastSignalRefMcps > SignalRefClipValue)) { + /* Limit Fail */ + SignalRefClipflag = 1; + } + } + + /* + * Check if Signal ref clip limit is enabled, if yes then do comparison + * with limit value and put the result back into pPalRangeStatus. + * EffectiveSpadRtnCount has a format 8.8 + * If (Return signal rate < (1.5 x Xtalk x number of Spads)) : FAIL + */ + if (Status == VL53L0X_ERROR_NONE) + Status = VL53L0X_GetLimitCheckEnable(Dev, + VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD, + &RangeIgnoreThresholdLimitCheckEnable); + + if ((RangeIgnoreThresholdLimitCheckEnable != 0) && + (Status == VL53L0X_ERROR_NONE)) { + + /* Compute the signal rate per spad */ + if (EffectiveSpadRtnCount == 0) { + SignalRatePerSpad = 0; + } else { + SignalRatePerSpad = (FixPoint1616_t)((256 * SignalRate) + / EffectiveSpadRtnCount); + } + + Status = VL53L0X_GetLimitCheckValue(Dev, + VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD, + &RangeIgnoreThresholdValue); + + if ((RangeIgnoreThresholdValue > 0) && + (SignalRatePerSpad < RangeIgnoreThresholdValue)) { + /* Limit Fail add 2^6 to range status */ + RangeIgnoreThresholdflag = 1; + } + } + + if (Status == VL53L0X_ERROR_NONE) { + if (NoneFlag == 1) { + *pPalRangeStatus = 255; /* NONE */ + } else if (DeviceRangeStatusInternal == 1 || + DeviceRangeStatusInternal == 2 || + DeviceRangeStatusInternal == 3) { + *pPalRangeStatus = 5; /* HW fail */ + } else if (DeviceRangeStatusInternal == 6 || + DeviceRangeStatusInternal == 9) { + *pPalRangeStatus = 4; /* Phase fail */ + } else if (DeviceRangeStatusInternal == 8 || + DeviceRangeStatusInternal == 10 || + SignalRefClipflag == 1) { + *pPalRangeStatus = 3; /* Min range */ + } else if (DeviceRangeStatusInternal == 4 || + RangeIgnoreThresholdflag == 1) { + *pPalRangeStatus = 2; /* Signal Fail */ + } else if (SigmaLimitflag == 1) { + *pPalRangeStatus = 1; /* Sigma Fail */ + } else { + *pPalRangeStatus = 0; /* Range Valid */ + } + } + + /* fill the Limit Check Status */ + + Status = VL53L0X_GetLimitCheckEnable(Dev, + VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE, + &SignalRateFinalRangeLimitCheckEnable); + + if (Status == VL53L0X_ERROR_NONE) { + if ((SigmaLimitCheckEnable == 0) || (SigmaLimitflag == 1)) + Temp8 = 1; + else + Temp8 = 0; + VL53L0X_SETARRAYPARAMETERFIELD(Dev, LimitChecksStatus, + VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE, Temp8); + + if ((DeviceRangeStatusInternal == 4) || + (SignalRateFinalRangeLimitCheckEnable == 0)) + Temp8 = 1; + else + Temp8 = 0; + VL53L0X_SETARRAYPARAMETERFIELD(Dev, LimitChecksStatus, + VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE, + Temp8); + + if ((SignalRefClipLimitCheckEnable == 0) || + (SignalRefClipflag == 1)) + Temp8 = 1; + else + Temp8 = 0; + + VL53L0X_SETARRAYPARAMETERFIELD(Dev, LimitChecksStatus, + VL53L0X_CHECKENABLE_SIGNAL_REF_CLIP, Temp8); + + if ((RangeIgnoreThresholdLimitCheckEnable == 0) || + (RangeIgnoreThresholdflag == 1)) + Temp8 = 1; + else + Temp8 = 0; + + VL53L0X_SETARRAYPARAMETERFIELD(Dev, LimitChecksStatus, + VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD, + Temp8); + } + + LOG_FUNCTION_END(Status); + return Status; + +} diff --git a/lib/vl53l0x/vl53l0x_api_core.h b/lib/vl53l0x/vl53l0x_api_core.h new file mode 100644 index 0000000..676cfb9 --- /dev/null +++ b/lib/vl53l0x/vl53l0x_api_core.h @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright 2016, STMicroelectronics International N.V. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of STMicroelectronics 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, FITNESS FOR A PARTICULAR PURPOSE, AND +NON-INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS ARE DISCLAIMED. +IN NO EVENT SHALL STMICROELECTRONICS INTERNATIONAL N.V. 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. +*******************************************************************************/ + +#ifndef _VL53L0X_API_CORE_H_ +#define _VL53L0X_API_CORE_H_ + +#include "vl53l0x_def.h" +#include "vl53l0x_platform.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +VL53L0X_Error VL53L0X_reverse_bytes(uint8_t *data, uint32_t size); + +VL53L0X_Error VL53L0X_measurement_poll_for_completion(VL53L0X_DEV Dev); + +uint8_t VL53L0X_encode_vcsel_period(uint8_t vcsel_period_pclks); + +uint8_t VL53L0X_decode_vcsel_period(uint8_t vcsel_period_reg); + +uint32_t VL53L0X_isqrt(uint32_t num); + +uint32_t VL53L0X_quadrature_sum(uint32_t a, uint32_t b); + +VL53L0X_Error VL53L0X_get_info_from_device(VL53L0X_DEV Dev, uint8_t option); + +VL53L0X_Error VL53L0X_set_vcsel_pulse_period(VL53L0X_DEV Dev, + VL53L0X_VcselPeriod VcselPeriodType, uint8_t VCSELPulsePeriodPCLK); + +VL53L0X_Error VL53L0X_get_vcsel_pulse_period(VL53L0X_DEV Dev, + VL53L0X_VcselPeriod VcselPeriodType, uint8_t *pVCSELPulsePeriodPCLK); + +uint32_t VL53L0X_decode_timeout(uint16_t encoded_timeout); + +VL53L0X_Error get_sequence_step_timeout(VL53L0X_DEV Dev, + VL53L0X_SequenceStepId SequenceStepId, + uint32_t *pTimeOutMicroSecs); + +VL53L0X_Error set_sequence_step_timeout(VL53L0X_DEV Dev, + VL53L0X_SequenceStepId SequenceStepId, + uint32_t TimeOutMicroSecs); + +VL53L0X_Error VL53L0X_set_measurement_timing_budget_micro_seconds( + VL53L0X_DEV Dev, + uint32_t MeasurementTimingBudgetMicroSeconds); + +VL53L0X_Error VL53L0X_get_measurement_timing_budget_micro_seconds( + VL53L0X_DEV Dev, + uint32_t *pMeasurementTimingBudgetMicroSeconds); + +VL53L0X_Error VL53L0X_load_tuning_settings(VL53L0X_DEV Dev, + uint8_t *pTuningSettingBuffer); + +VL53L0X_Error VL53L0X_calc_sigma_estimate(VL53L0X_DEV Dev, + VL53L0X_RangingMeasurementData_t *pRangingMeasurementData, + FixPoint1616_t *pSigmaEstimate); + +VL53L0X_Error VL53L0X_calc_dmax( + VL53L0X_DEV Dev, FixPoint1616_t ambRateMeas, uint32_t *pdmax_mm); + +VL53L0X_Error VL53L0X_get_total_xtalk_rate(VL53L0X_DEV Dev, + VL53L0X_RangingMeasurementData_t *pRangingMeasurementData, + FixPoint1616_t *ptotal_xtalk_rate_mcps); + +VL53L0X_Error VL53L0X_get_total_signal_rate(VL53L0X_DEV Dev, + VL53L0X_RangingMeasurementData_t *pRangingMeasurementData, + FixPoint1616_t *ptotal_signal_rate_mcps); + +VL53L0X_Error VL53L0X_get_pal_range_status(VL53L0X_DEV Dev, + uint8_t DeviceRangeStatus, + FixPoint1616_t SignalRate, + uint16_t EffectiveSpadRtnCount, + VL53L0X_RangingMeasurementData_t *pRangingMeasurementData, + uint8_t *pPalRangeStatus); + +uint32_t VL53L0X_calc_timeout_mclks(VL53L0X_DEV Dev, + uint32_t timeout_period_us, uint8_t vcsel_period_pclks); + +uint16_t VL53L0X_encode_timeout(uint32_t timeout_macro_clks); + +#ifdef __cplusplus +} +#endif + +#endif /* _VL53L0X_API_CORE_H_ */ diff --git a/lib/vl53l0x/vl53l0x_api_ranging.c b/lib/vl53l0x/vl53l0x_api_ranging.c new file mode 100644 index 0000000..06401b5 --- /dev/null +++ b/lib/vl53l0x/vl53l0x_api_ranging.c @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright 2016, STMicroelectronics International N.V. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of STMicroelectronics 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, FITNESS FOR A PARTICULAR PURPOSE, AND + NON-INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS ARE DISCLAIMED. + IN NO EVENT SHALL STMICROELECTRONICS INTERNATIONAL N.V. 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 "vl53l0x_api.h" +#include "vl53l0x_api_core.h" + + +#ifndef __KERNEL__ +#include +#endif +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(TRACE_MODULE_API, fmt, ##__VA_ARGS__) +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(TRACE_MODULE_API, status, ##__VA_ARGS__) +#define LOG_FUNCTION_END_FMT(status, fmt, ...) \ + _LOG_FUNCTION_END_FMT(TRACE_MODULE_API, status, fmt, ##__VA_ARGS__) + diff --git a/lib/vl53l0x/vl53l0x_api_ranging.h b/lib/vl53l0x/vl53l0x_api_ranging.h new file mode 100644 index 0000000..18ac399 --- /dev/null +++ b/lib/vl53l0x/vl53l0x_api_ranging.h @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright 2016, STMicroelectronics International N.V. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of STMicroelectronics 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, FITNESS FOR A PARTICULAR PURPOSE, AND +NON-INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS ARE DISCLAIMED. +IN NO EVENT SHALL STMICROELECTRONICS INTERNATIONAL N.V. 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. +*******************************************************************************/ + +#ifndef _VL53L0X_API_RANGING_H_ +#define _VL53L0X_API_RANGING_H_ + +#include "vl53l0x_def.h" +#include "vl53l0x_platform.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + + + +#ifdef __cplusplus +} +#endif + +#endif /* _VL53L0X_API_RANGING_H_ */ diff --git a/lib/vl53l0x/vl53l0x_api_strings.h b/lib/vl53l0x/vl53l0x_api_strings.h new file mode 100644 index 0000000..79dcde9 --- /dev/null +++ b/lib/vl53l0x/vl53l0x_api_strings.h @@ -0,0 +1,21 @@ +#ifndef VL53L0X_API_STRINGS_H_ +#define VL53L0X_API_STRINGS_H_ + +#include "vl53l0x_def.h" + +#ifndef VL53L0X_API +#define VL53L0X_API +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +VL53L0X_API VL53L0X_Error VL53L0X_GetStatusErrorString(VL53L0X_Error Status, char *pErrorString); +VL53L0X_API VL53L0X_Error VL53L0X_GetStatusString(VL53L0X_Error Status, char *pStatusString); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/vl53l0x/vl53l0x_def.h b/lib/vl53l0x/vl53l0x_def.h new file mode 100644 index 0000000..b67a1db --- /dev/null +++ b/lib/vl53l0x/vl53l0x_def.h @@ -0,0 +1,663 @@ +/******************************************************************************* + * Copyright 2016, STMicroelectronics International N.V. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of STMicroelectronics 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, FITNESS FOR A PARTICULAR PURPOSE, AND +NON-INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS ARE DISCLAIMED. +IN NO EVENT SHALL STMICROELECTRONICS INTERNATIONAL N.V. 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. +*******************************************************************************/ + +/** + * @file VL53L0X_def.h + * + * @brief Type definitions for VL53L0X API. + * + */ + + +#ifndef _VL53L0X_DEF_H_ +#define _VL53L0X_DEF_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup VL53L0X_globaldefine_group VL53L0X Defines + * @brief VL53L0X Defines + * @{ + */ + + +/** PAL SPECIFICATION major version */ +#define VL53L0X10_SPECIFICATION_VER_MAJOR 1 +/** PAL SPECIFICATION minor version */ +#define VL53L0X10_SPECIFICATION_VER_MINOR 2 +/** PAL SPECIFICATION sub version */ +#define VL53L0X10_SPECIFICATION_VER_SUB 7 +/** PAL SPECIFICATION sub version */ +#define VL53L0X10_SPECIFICATION_VER_REVISION 1440 + +/** VL53L0X PAL IMPLEMENTATION major version */ +#define VL53L0X10_IMPLEMENTATION_VER_MAJOR 1 +/** VL53L0X PAL IMPLEMENTATION minor version */ +#define VL53L0X10_IMPLEMENTATION_VER_MINOR 0 +/** VL53L0X PAL IMPLEMENTATION sub version */ +#define VL53L0X10_IMPLEMENTATION_VER_SUB 9 +/** VL53L0X PAL IMPLEMENTATION sub version */ +#define VL53L0X10_IMPLEMENTATION_VER_REVISION 3673 + +/** PAL SPECIFICATION major version */ +#define VL53L0X_SPECIFICATION_VER_MAJOR 1 +/** PAL SPECIFICATION minor version */ +#define VL53L0X_SPECIFICATION_VER_MINOR 2 +/** PAL SPECIFICATION sub version */ +#define VL53L0X_SPECIFICATION_VER_SUB 7 +/** PAL SPECIFICATION sub version */ +#define VL53L0X_SPECIFICATION_VER_REVISION 1440 + +/** VL53L0X PAL IMPLEMENTATION major version */ +#define VL53L0X_IMPLEMENTATION_VER_MAJOR 1 +/** VL53L0X PAL IMPLEMENTATION minor version */ +#define VL53L0X_IMPLEMENTATION_VER_MINOR 0 +/** VL53L0X PAL IMPLEMENTATION sub version */ +#define VL53L0X_IMPLEMENTATION_VER_SUB 4 +/** VL53L0X PAL IMPLEMENTATION sub version */ +#define VL53L0X_IMPLEMENTATION_VER_REVISION 4960 +#define VL53L0X_DEFAULT_MAX_LOOP 2000 +#define VL53L0X_MAX_STRING_LENGTH 32 + + +#include "vl53l0x_device.h" +#include "vl53l0x_types.h" + + +/**************************************** + * PRIVATE define do not edit + ****************************************/ + +/** @brief Defines the parameters of the Get Version Functions + */ +typedef struct { + uint32_t revision; /*!< revision number */ + uint8_t major; /*!< major number */ + uint8_t minor; /*!< minor number */ + uint8_t build; /*!< build number */ +} VL53L0X_Version_t; + + +/** @brief Defines the parameters of the Get Device Info Functions + */ +typedef struct { + char Name[VL53L0X_MAX_STRING_LENGTH]; + /*!< Name of the Device e.g. Left_Distance */ + char Type[VL53L0X_MAX_STRING_LENGTH]; + /*!< Type of the Device e.g VL53L0X */ + char ProductId[VL53L0X_MAX_STRING_LENGTH]; + /*!< Product Identifier String */ + uint8_t ProductType; + /*!< Product Type, VL53L0X = 1, VL53L1 = 2 */ + uint8_t ProductRevisionMajor; + /*!< Product revision major */ + uint8_t ProductRevisionMinor; + /*!< Product revision minor */ +} VL53L0X_DeviceInfo_t; + + +/** @defgroup VL53L0X_define_Error_group Error and Warning code returned by API + * The following DEFINE are used to identify the PAL ERROR + * @{ + */ + +typedef int8_t VL53L0X_Error; + +#define VL53L0X_ERROR_NONE ((VL53L0X_Error) 0) +#define VL53L0X_ERROR_CALIBRATION_WARNING ((VL53L0X_Error) - 1) + /*!< Warning invalid calibration data may be in used + * \a VL53L0X_InitData() + * \a VL53L0X_GetOffsetCalibrationData + * \a VL53L0X_SetOffsetCalibrationData + */ +#define VL53L0X_ERROR_MIN_CLIPPED ((VL53L0X_Error) - 2) + /*!< Warning parameter passed was clipped to min before to be applied */ + +#define VL53L0X_ERROR_UNDEFINED ((VL53L0X_Error) - 3) + /*!< Unqualified error */ +#define VL53L0X_ERROR_INVALID_PARAMS ((VL53L0X_Error) - 4) + /*!< Parameter passed is invalid or out of range */ +#define VL53L0X_ERROR_NOT_SUPPORTED ((VL53L0X_Error) - 5) + /*!< Function is not supported in current mode or configuration */ +#define VL53L0X_ERROR_RANGE_ERROR ((VL53L0X_Error) - 6) + /*!< Device report a ranging error interrupt status */ +#define VL53L0X_ERROR_TIME_OUT ((VL53L0X_Error) - 7) + /*!< Aborted due to time out */ +#define VL53L0X_ERROR_MODE_NOT_SUPPORTED ((VL53L0X_Error) - 8) + /*!< Asked mode is not supported by the device */ +#define VL53L0X_ERROR_BUFFER_TOO_SMALL ((VL53L0X_Error) - 9) + /*!< ... */ +#define VL53L0X_ERROR_GPIO_NOT_EXISTING ((VL53L0X_Error) - 10) + /*!< User tried to setup a non-existing GPIO pin */ +#define VL53L0X_ERROR_GPIO_FUNCTIONALITY_NOT_SUPPORTED ((VL53L0X_Error) - 11) + /*!< unsupported GPIO functionality */ +#define VL53L0X_ERROR_INTERRUPT_NOT_CLEARED ((VL53L0X_Error) - 12) + /*!< Error during interrupt clear */ +#define VL53L0X_ERROR_CONTROL_INTERFACE ((VL53L0X_Error) - 20) + /*!< error reported from IO functions */ +#define VL53L0X_ERROR_INVALID_COMMAND ((VL53L0X_Error) - 30) + /*!< The command is not allowed in the current device state + * (power down) + */ +#define VL53L0X_ERROR_DIVISION_BY_ZERO ((VL53L0X_Error) - 40) + /*!< In the function a division by zero occurs */ +#define VL53L0X_ERROR_REF_SPAD_INIT ((VL53L0X_Error) - 50) + /*!< Error during reference SPAD initialization */ +#define VL53L0X_ERROR_NOT_IMPLEMENTED ((VL53L0X_Error) - 99) + /*!< Tells requested functionality has not been implemented yet or + * not compatible with the device + */ +/** @} VL53L0X_define_Error_group */ + + +/** @defgroup VL53L0X_define_DeviceModes_group Defines Device modes + * Defines all possible modes for the device + * @{ + */ +typedef uint8_t VL53L0X_DeviceModes; + +#define VL53L0X_DEVICEMODE_SINGLE_RANGING ((VL53L0X_DeviceModes) 0) +#define VL53L0X_DEVICEMODE_CONTINUOUS_RANGING ((VL53L0X_DeviceModes) 1) +#define VL53L0X_DEVICEMODE_SINGLE_HISTOGRAM ((VL53L0X_DeviceModes) 2) +#define VL53L0X_DEVICEMODE_CONTINUOUS_TIMED_RANGING ((VL53L0X_DeviceModes) 3) +#define VL53L0X_DEVICEMODE_SINGLE_ALS ((VL53L0X_DeviceModes) 10) +#define VL53L0X_DEVICEMODE_GPIO_DRIVE ((VL53L0X_DeviceModes) 20) +#define VL53L0X_DEVICEMODE_GPIO_OSC ((VL53L0X_DeviceModes) 21) + /* ... Modes to be added depending on device */ +/** @} VL53L0X_define_DeviceModes_group */ + + + +/** @defgroup VL53L0X_define_HistogramModes_group Defines Histogram modes + * Defines all possible Histogram modes for the device + * @{ + */ +typedef uint8_t VL53L0X_HistogramModes; + +#define VL53L0X_HISTOGRAMMODE_DISABLED ((VL53L0X_HistogramModes) 0) + /*!< Histogram Disabled */ +#define VL53L0X_HISTOGRAMMODE_REFERENCE_ONLY ((VL53L0X_HistogramModes) 1) + /*!< Histogram Reference array only */ +#define VL53L0X_HISTOGRAMMODE_RETURN_ONLY ((VL53L0X_HistogramModes) 2) + /*!< Histogram Return array only */ +#define VL53L0X_HISTOGRAMMODE_BOTH ((VL53L0X_HistogramModes) 3) + /*!< Histogram both Reference and Return Arrays */ + /* ... Modes to be added depending on device */ +/** @} VL53L0X_define_HistogramModes_group */ + + +/** @defgroup VL53L0X_define_PowerModes_group List of available Power Modes + * List of available Power Modes + * @{ + */ + +typedef uint8_t VL53L0X_PowerModes; + +#define VL53L0X_POWERMODE_STANDBY_LEVEL1 ((VL53L0X_PowerModes) 0) + /*!< Standby level 1 */ +#define VL53L0X_POWERMODE_STANDBY_LEVEL2 ((VL53L0X_PowerModes) 1) + /*!< Standby level 2 */ +#define VL53L0X_POWERMODE_IDLE_LEVEL1 ((VL53L0X_PowerModes) 2) + /*!< Idle level 1 */ +#define VL53L0X_POWERMODE_IDLE_LEVEL2 ((VL53L0X_PowerModes) 3) + /*!< Idle level 2 */ + +/** @} VL53L0X_define_PowerModes_group */ + + +#define VL53L0X_DMAX_LUT_SIZE 7 + /*!< Defines the number of items in the DMAX lookup table */ + +/** @brief Structure defining data pair that makes up the DMAX Lookup table. + */ +typedef struct { + FixPoint1616_t ambRate_mcps[VL53L0X_DMAX_LUT_SIZE]; + /*!< Ambient rate (mcps) */ + FixPoint1616_t dmax_mm[VL53L0X_DMAX_LUT_SIZE]; + /*!< DMAX Value (mm) */ +} VL53L0X_DMaxLUT_t; + +/** @brief Defines all parameters for the device + */ +typedef struct { + VL53L0X_DeviceModes DeviceMode; + /*!< Defines type of measurement to be done for the next measure */ + VL53L0X_HistogramModes HistogramMode; + /*!< Defines type of histogram measurement to be done for the next + * measure + */ + uint32_t MeasurementTimingBudgetMicroSeconds; + /*!< Defines the allowed total time for a single measurement */ + uint32_t InterMeasurementPeriodMilliSeconds; + /*!< Defines time between two consecutive measurements (between two + * measurement starts). If set to 0 means back-to-back mode + */ + uint8_t XTalkCompensationEnable; + /*!< Tells if Crosstalk compensation shall be enable or not */ + uint16_t XTalkCompensationRangeMilliMeter; + /*!< CrossTalk compensation range in millimeter */ + FixPoint1616_t XTalkCompensationRateMegaCps; + /*!< CrossTalk compensation rate in Mega counts per seconds. + * Expressed in 16.16 fixed point format. + */ + int32_t RangeOffsetMicroMeters; + /*!< Range offset adjustment (mm). */ + + uint8_t LimitChecksEnable[VL53L0X_CHECKENABLE_NUMBER_OF_CHECKS]; + /*!< This Array store all the Limit Check enable for this device. */ + uint8_t LimitChecksStatus[VL53L0X_CHECKENABLE_NUMBER_OF_CHECKS]; + /*!< This Array store all the Status of the check linked to last + * measurement. + */ + FixPoint1616_t LimitChecksValue[VL53L0X_CHECKENABLE_NUMBER_OF_CHECKS]; + /*!< This Array store all the Limit Check value for this device */ + + VL53L0X_DMaxLUT_t dmax_lut; + /*!< Lookup table defining ambient rates and associated + * dmax values. + */ + + uint8_t WrapAroundCheckEnable; + /*!< Tells if Wrap Around Check shall be enable or not */ +} VL53L0X_DeviceParameters_t; + + +/** @defgroup VL53L0X_define_State_group Defines the current status + * of the device + * Defines the current status of the device + * @{ + */ + +typedef uint8_t VL53L0X_State; + +#define VL53L0X_STATE_POWERDOWN ((VL53L0X_State) 0) + /*!< Device is in HW reset */ +#define VL53L0X_STATE_WAIT_STATICINIT ((VL53L0X_State) 1) + /*!< Device is initialized and wait for static initialization */ +#define VL53L0X_STATE_STANDBY ((VL53L0X_State) 2) + /*!< Device is in Low power Standby mode */ +#define VL53L0X_STATE_IDLE ((VL53L0X_State) 3) + /*!< Device has been initialized and ready to do measurements */ +#define VL53L0X_STATE_RUNNING ((VL53L0X_State) 4) + /*!< Device is performing measurement */ +#define VL53L0X_STATE_UNKNOWN ((VL53L0X_State) 98) + /*!< Device is in unknown state and need to be rebooted */ +#define VL53L0X_STATE_ERROR ((VL53L0X_State) 99) + /*!< Device is in error state and need to be rebooted */ + +/** @} VL53L0X_define_State_group */ + + +/** + * @struct VL53L0X_RangeData_t + * @brief Range measurement data. + */ +typedef struct { + uint32_t TimeStamp; /*!< 32-bit time stamp. */ + uint32_t MeasurementTimeUsec; + /*!< Give the Measurement time needed by the device to do the + * measurement. + */ + + + uint16_t RangeMilliMeter; /*!< range distance in millimeter. */ + + uint16_t RangeDMaxMilliMeter; + /*!< Tells what is the maximum detection distance of the device + * in current setup and environment conditions (Filled when + * applicable) + */ + + FixPoint1616_t SignalRateRtnMegaCps; + /*!< Return signal rate (MCPS)\n these is a 16.16 fix point + * value, which is effectively a measure of target + * reflectance. + */ + FixPoint1616_t AmbientRateRtnMegaCps; + /*!< Return ambient rate (MCPS)\n these is a 16.16 fix point + * value, which is effectively a measure of the ambien + * t light. + */ + + uint16_t EffectiveSpadRtnCount; + /*!< Return the effective SPAD count for the return signal. + * To obtain Real value it should be divided by 256 + */ + + uint8_t ZoneId; + /*!< Denotes which zone and range scheduler stage the range + * data relates to. + */ + uint8_t RangeFractionalPart; + /*!< Fractional part of range distance. Final value is a + * FixPoint168 value. + */ + uint8_t RangeStatus; + /*!< Range Status for the current measurement. This is device + * dependent. Value = 0 means value is valid. + * See \ref RangeStatusPage + */ +} VL53L0X_RangingMeasurementData_t; + + +#define VL53L0X_HISTOGRAM_BUFFER_SIZE 24 + +/** + * @struct VL53L0X_HistogramData_t + * @brief Histogram measurement data. + */ +typedef struct { + /* Histogram Measurement data */ + uint32_t HistogramData[VL53L0X_HISTOGRAM_BUFFER_SIZE]; + /*!< Histogram data */ + /*!< Indicate the types of histogram data : + *Return only, Reference only, both Return and Reference + */ + uint8_t FirstBin; /*!< First Bin value */ + uint8_t BufferSize; /*!< Buffer Size - Set by the user.*/ + uint8_t NumberOfBins; + /*!< Number of bins filled by the histogram measurement */ + + VL53L0X_DeviceError ErrorStatus; + /*!< Error status of the current measurement. \n + * see @a ::VL53L0X_DeviceError @a VL53L0X_GetStatusErrorString() + */ +} VL53L0X_HistogramMeasurementData_t; + +#define VL53L0X_REF_SPAD_BUFFER_SIZE 6 + +/** + * @struct VL53L0X_SpadData_t + * @brief Spad Configuration Data. + */ +typedef struct { + uint8_t RefSpadEnables[VL53L0X_REF_SPAD_BUFFER_SIZE]; + /*!< Reference Spad Enables */ + uint8_t RefGoodSpadMap[VL53L0X_REF_SPAD_BUFFER_SIZE]; + /*!< Reference Spad Good Spad Map */ +} VL53L0X_SpadData_t; + +typedef struct { + FixPoint1616_t OscFrequencyMHz; /* Frequency used */ + + uint16_t LastEncodedTimeout; + /* last encoded Time out used for timing budget*/ + + VL53L0X_GpioFunctionality Pin0GpioFunctionality; + /* store the functionality of the GPIO: pin0 */ + + uint32_t FinalRangeTimeoutMicroSecs; + /*!< Execution time of the final range*/ + uint8_t FinalRangeVcselPulsePeriod; + /*!< Vcsel pulse period (pll clocks) for the final range measurement*/ + uint32_t PreRangeTimeoutMicroSecs; + /*!< Execution time of the final range*/ + uint8_t PreRangeVcselPulsePeriod; + /*!< Vcsel pulse period (pll clocks) for the pre-range measurement*/ + + uint16_t SigmaEstRefArray; + /*!< Reference array sigma value in 1/100th of [mm] e.g. 100 = 1mm */ + uint16_t SigmaEstEffPulseWidth; + /*!< Effective Pulse width for sigma estimate in 1/100th + * of ns e.g. 900 = 9.0ns + */ + uint16_t SigmaEstEffAmbWidth; + /*!< Effective Ambient width for sigma estimate in 1/100th of ns + * e.g. 500 = 5.0ns + */ + + + /* Indicate if read from device has been done (==1) or not (==0) */ + uint8_t ReadDataFromDeviceDone; + uint8_t ModuleId; /* Module ID */ + uint8_t Revision; /* test Revision */ + char ProductId[VL53L0X_MAX_STRING_LENGTH]; + /* Product Identifier String */ + uint8_t ReferenceSpadCount; /* used for ref spad management */ + uint8_t ReferenceSpadType; /* used for ref spad management */ + uint8_t RefSpadsInitialised; /* reports if ref spads are initialised. */ + uint32_t PartUIDUpper; /*!< Unique Part ID Upper */ + uint32_t PartUIDLower; /*!< Unique Part ID Lower */ + /*!< Peek Signal rate at 400 mm*/ + FixPoint1616_t SignalRateMeasFixed400mm; + +} VL53L0X_DeviceSpecificParameters_t; + +/** + * @struct VL53L0X_DevData_t + * + * @brief VL53L0X PAL device ST private data structure \n + * End user should never access any of these field directly + * + * These must never access directly but only via macro + */ +typedef struct { + int32_t Part2PartOffsetNVMMicroMeter; + /*!< backed up NVM value */ + int32_t Part2PartOffsetAdjustmentNVMMicroMeter; + /*!< backed up NVM value representing additional offset adjustment */ + VL53L0X_DeviceParameters_t CurrentParameters; + /*!< Current Device Parameter */ + VL53L0X_RangingMeasurementData_t LastRangeMeasure; + /*!< Ranging Data */ + VL53L0X_HistogramMeasurementData_t LastHistogramMeasure; + /*!< Histogram Data */ + VL53L0X_DeviceSpecificParameters_t DeviceSpecificParameters; + /*!< Parameters specific to the device */ + VL53L0X_SpadData_t SpadData; + /*!< Spad Data */ + uint8_t SequenceConfig; + /*!< Internal value for the sequence config */ + uint8_t RangeFractionalEnable; + /*!< Enable/Disable fractional part of ranging data */ + VL53L0X_State PalState; + /*!< Current state of the PAL for this device */ + VL53L0X_PowerModes PowerMode; + /*!< Current Power Mode */ + uint16_t SigmaEstRefArray; + /*!< Reference array sigma value in 1/100th of [mm] e.g. 100 = 1mm */ + uint16_t SigmaEstEffPulseWidth; + /*!< Effective Pulse width for sigma estimate in 1/100th + * of ns e.g. 900 = 9.0ns + */ + uint16_t SigmaEstEffAmbWidth; + /*!< Effective Ambient width for sigma estimate in 1/100th of ns + * e.g. 500 = 5.0ns + */ + uint8_t StopVariable; + /*!< StopVariable used during the stop sequence */ + uint16_t targetRefRate; + /*!< Target Ambient Rate for Ref spad management */ + FixPoint1616_t SigmaEstimate; + /*!< Sigma Estimate - based on ambient & VCSEL rates and + * signal_total_events + */ + FixPoint1616_t SignalEstimate; + /*!< Signal Estimate - based on ambient & VCSEL rates and cross talk */ + FixPoint1616_t LastSignalRefMcps; + /*!< Latest Signal ref in Mcps */ + uint8_t *pTuningSettingsPointer; + /*!< Pointer for Tuning Settings table */ + uint8_t UseInternalTuningSettings; + /*!< Indicate if we use Tuning Settings table */ + uint16_t LinearityCorrectiveGain; + /*!< Linearity Corrective Gain value in x1000 */ +} VL53L0X_DevData_t; + + +/** @defgroup VL53L0X_define_InterruptPolarity_group Defines the Polarity + * of the Interrupt + * Defines the Polarity of the Interrupt + * @{ + */ +typedef uint8_t VL53L0X_InterruptPolarity; + +#define VL53L0X_INTERRUPTPOLARITY_LOW ((VL53L0X_InterruptPolarity) 0) +/*!< Set active low polarity best setup for falling edge. */ +#define VL53L0X_INTERRUPTPOLARITY_HIGH ((VL53L0X_InterruptPolarity) 1) +/*!< Set active high polarity best setup for rising edge. */ + +/** @} VL53L0X_define_InterruptPolarity_group */ + + +/** @defgroup VL53L0X_define_VcselPeriod_group Vcsel Period Defines + * Defines the range measurement for which to access the vcsel period. + * @{ + */ +typedef uint8_t VL53L0X_VcselPeriod; + +#define VL53L0X_VCSEL_PERIOD_PRE_RANGE ((VL53L0X_VcselPeriod) 0) +/*!>9)&0xFFFF) +#define VL53L0X_FIXPOINT97TOFIXPOINT1616(Value) \ + (FixPoint1616_t)(Value<<9) + +#define VL53L0X_FIXPOINT1616TOFIXPOINT88(Value) \ + (uint16_t)((Value>>8)&0xFFFF) +#define VL53L0X_FIXPOINT88TOFIXPOINT1616(Value) \ + (FixPoint1616_t)(Value<<8) + +#define VL53L0X_FIXPOINT1616TOFIXPOINT412(Value) \ + (uint16_t)((Value>>4)&0xFFFF) +#define VL53L0X_FIXPOINT412TOFIXPOINT1616(Value) \ + (FixPoint1616_t)(Value<<4) + +#define VL53L0X_FIXPOINT1616TOFIXPOINT313(Value) \ + (uint16_t)((Value>>3)&0xFFFF) +#define VL53L0X_FIXPOINT313TOFIXPOINT1616(Value) \ + (FixPoint1616_t)(Value<<3) + +#define VL53L0X_FIXPOINT1616TOFIXPOINT08(Value) \ + (uint8_t)((Value>>8)&0x00FF) +#define VL53L0X_FIXPOINT08TOFIXPOINT1616(Value) \ + (FixPoint1616_t)(Value<<8) + +#define VL53L0X_FIXPOINT1616TOFIXPOINT53(Value) \ + (uint8_t)((Value>>13)&0x00FF) +#define VL53L0X_FIXPOINT53TOFIXPOINT1616(Value) \ + (FixPoint1616_t)(Value<<13) + +#define VL53L0X_FIXPOINT1616TOFIXPOINT102(Value) \ + (uint16_t)((Value>>14)&0x0FFF) +#define VL53L0X_FIXPOINT102TOFIXPOINT1616(Value) \ + (FixPoint1616_t)(Value<<12) + +#define VL53L0X_MAKEUINT16(lsb, msb) (uint16_t)((((uint16_t)msb)<<8) + \ + (uint16_t)lsb) + +/** @} VL53L0X_define_GeneralMacro_group */ + +/** @} VL53L0X_globaldefine_group */ + + + + + + + +#ifdef __cplusplus +} +#endif + + +#endif /* _VL53L0X_DEF_H_ */ diff --git a/lib/vl53l0x/vl53l0x_device.h b/lib/vl53l0x/vl53l0x_device.h new file mode 100644 index 0000000..3b9b88d --- /dev/null +++ b/lib/vl53l0x/vl53l0x_device.h @@ -0,0 +1,262 @@ +/******************************************************************************* + * Copyright 2016, STMicroelectronics International N.V. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of STMicroelectronics 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, FITNESS FOR A PARTICULAR PURPOSE, AND +NON-INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS ARE DISCLAIMED. +IN NO EVENT SHALL STMICROELECTRONICS INTERNATIONAL N.V. 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. +*******************************************************************************/ + +/** + * Device specific defines. To be adapted by implementer for the targeted + * device. + */ + +#ifndef _VL53L0X_DEVICE_H_ +#define _VL53L0X_DEVICE_H_ + +#include "vl53l0x_types.h" + + +/** @defgroup VL53L0X_DevSpecDefines_group VL53L0X cut1.1 Device + * Specific Defines + * @brief VL53L0X cut1.1 Device Specific Defines + * @{ + */ + + +/** @defgroup VL53L0X_DeviceError_group Device Error + * @brief Device Error code + * + * This enum is Device specific it should be updated in the implementation + * Use @a VL53L0X_GetStatusErrorString() to get the string. + * It is related to Status Register of the Device. + * @{ + */ +typedef uint8_t VL53L0X_DeviceError; + +#define VL53L0X_DEVICEERROR_NONE ((VL53L0X_DeviceError) 0) + /*!< 0 NoError */ +#define VL53L0X_DEVICEERROR_VCSELCONTINUITYTESTFAILURE ((VL53L0X_DeviceError) 1) +#define VL53L0X_DEVICEERROR_VCSELWATCHDOGTESTFAILURE ((VL53L0X_DeviceError) 2) +#define VL53L0X_DEVICEERROR_NOVHVVALUEFOUND ((VL53L0X_DeviceError) 3) +#define VL53L0X_DEVICEERROR_MSRCNOTARGET ((VL53L0X_DeviceError) 4) +#define VL53L0X_DEVICEERROR_SNRCHECK ((VL53L0X_DeviceError) 5) +#define VL53L0X_DEVICEERROR_RANGEPHASECHECK ((VL53L0X_DeviceError) 6) +#define VL53L0X_DEVICEERROR_SIGMATHRESHOLDCHECK ((VL53L0X_DeviceError) 7) +#define VL53L0X_DEVICEERROR_TCC ((VL53L0X_DeviceError) 8) +#define VL53L0X_DEVICEERROR_PHASECONSISTENCY ((VL53L0X_DeviceError) 9) +#define VL53L0X_DEVICEERROR_MINCLIP ((VL53L0X_DeviceError) 10) +#define VL53L0X_DEVICEERROR_RANGECOMPLETE ((VL53L0X_DeviceError) 11) +#define VL53L0X_DEVICEERROR_ALGOUNDERFLOW ((VL53L0X_DeviceError) 12) +#define VL53L0X_DEVICEERROR_ALGOOVERFLOW ((VL53L0X_DeviceError) 13) +#define VL53L0X_DEVICEERROR_RANGEIGNORETHRESHOLD ((VL53L0X_DeviceError) 14) + +/** @} end of VL53L0X_DeviceError_group */ + + +/** @defgroup VL53L0X_CheckEnable_group Check Enable list + * @brief Check Enable code + * + * Define used to specify the LimitCheckId. + * Use @a VL53L0X_GetLimitCheckInfo() to get the string. + * @{ + */ + +#define VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE 0 +#define VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE 1 +#define VL53L0X_CHECKENABLE_SIGNAL_REF_CLIP 2 +#define VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD 3 +#define VL53L0X_CHECKENABLE_SIGNAL_RATE_MSRC 4 +#define VL53L0X_CHECKENABLE_SIGNAL_RATE_PRE_RANGE 5 + +#define VL53L0X_CHECKENABLE_NUMBER_OF_CHECKS 6 + +/** @} end of VL53L0X_CheckEnable_group */ + + +/** @defgroup VL53L0X_GpioFunctionality_group Gpio Functionality + * @brief Defines the different functionalities for the device GPIO(s) + * @{ + */ +typedef uint8_t VL53L0X_GpioFunctionality; + +#define VL53L0X_GPIOFUNCTIONALITY_OFF \ + ((VL53L0X_GpioFunctionality) 0) /*!< NO Interrupt */ +#define VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_LOW \ + ((VL53L0X_GpioFunctionality) 1) /*!< Level Low (value < thresh_low) */ +#define VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_HIGH \ + ((VL53L0X_GpioFunctionality) 2) /*!< Level High (value>thresh_high) */ +#define VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_OUT \ + ((VL53L0X_GpioFunctionality) 3) + /*!< Out Of Window (value < thresh_low OR value > thresh_high) */ +#define VL53L0X_GPIOFUNCTIONALITY_NEW_MEASURE_READY \ + ((VL53L0X_GpioFunctionality) 4) /*!< New Sample Ready */ + +/** @} end of VL53L0X_GpioFunctionality_group */ + + +/* Device register map */ + +/** @defgroup VL53L0X_DefineRegisters_group Define Registers + * @brief List of all the defined registers + * @{ + */ +#define VL53L0X_REG_SYSRANGE_START 0x000 + /** mask existing bit in #VL53L0X_REG_SYSRANGE_START*/ + #define VL53L0X_REG_SYSRANGE_MODE_MASK 0x0F + /** bit 0 in #VL53L0X_REG_SYSRANGE_START write 1 toggle state in + * continuous mode and arm next shot in single shot mode + */ + #define VL53L0X_REG_SYSRANGE_MODE_START_STOP 0x01 + /** bit 1 write 0 in #VL53L0X_REG_SYSRANGE_START set single shot mode */ + #define VL53L0X_REG_SYSRANGE_MODE_SINGLESHOT 0x00 + /** bit 1 write 1 in #VL53L0X_REG_SYSRANGE_START set back-to-back + * operation mode + */ + #define VL53L0X_REG_SYSRANGE_MODE_BACKTOBACK 0x02 + /** bit 2 write 1 in #VL53L0X_REG_SYSRANGE_START set timed operation + * mode + */ + #define VL53L0X_REG_SYSRANGE_MODE_TIMED 0x04 + /** bit 3 write 1 in #VL53L0X_REG_SYSRANGE_START set histogram operation + * mode + */ + #define VL53L0X_REG_SYSRANGE_MODE_HISTOGRAM 0x08 + + +#define VL53L0X_REG_SYSTEM_THRESH_HIGH 0x000C +#define VL53L0X_REG_SYSTEM_THRESH_LOW 0x000E + + +#define VL53L0X_REG_SYSTEM_SEQUENCE_CONFIG 0x0001 +#define VL53L0X_REG_SYSTEM_RANGE_CONFIG 0x0009 +#define VL53L0X_REG_SYSTEM_INTERMEASUREMENT_PERIOD 0x0004 + + +#define VL53L0X_REG_SYSTEM_INTERRUPT_CONFIG_GPIO 0x000A + #define VL53L0X_REG_SYSTEM_INTERRUPT_GPIO_DISABLED 0x00 + #define VL53L0X_REG_SYSTEM_INTERRUPT_GPIO_LEVEL_LOW 0x01 + #define VL53L0X_REG_SYSTEM_INTERRUPT_GPIO_LEVEL_HIGH 0x02 + #define VL53L0X_REG_SYSTEM_INTERRUPT_GPIO_OUT_OF_WINDOW 0x03 + #define VL53L0X_REG_SYSTEM_INTERRUPT_GPIO_NEW_SAMPLE_READY 0x04 + +#define VL53L0X_REG_GPIO_HV_MUX_ACTIVE_HIGH 0x0084 + + +#define VL53L0X_REG_SYSTEM_INTERRUPT_CLEAR 0x000B + +/* Result registers */ +#define VL53L0X_REG_RESULT_INTERRUPT_STATUS 0x0013 +#define VL53L0X_REG_RESULT_RANGE_STATUS 0x0014 + +#define VL53L0X_REG_RESULT_CORE_PAGE 1 +#define VL53L0X_REG_RESULT_CORE_AMBIENT_WINDOW_EVENTS_RTN 0x00BC +#define VL53L0X_REG_RESULT_CORE_RANGING_TOTAL_EVENTS_RTN 0x00C0 +#define VL53L0X_REG_RESULT_CORE_AMBIENT_WINDOW_EVENTS_REF 0x00D0 +#define VL53L0X_REG_RESULT_CORE_RANGING_TOTAL_EVENTS_REF 0x00D4 +#define VL53L0X_REG_RESULT_PEAK_SIGNAL_RATE_REF 0x00B6 + +/* Algo register */ + +#define VL53L0X_REG_ALGO_PART_TO_PART_RANGE_OFFSET_MM 0x0028 + +#define VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS 0x008a + +/* Check Limit registers */ +#define VL53L0X_REG_MSRC_CONFIG_CONTROL 0x0060 + +#define VL53L0X_REG_PRE_RANGE_CONFIG_MIN_SNR 0X0027 +#define VL53L0X_REG_PRE_RANGE_CONFIG_VALID_PHASE_LOW 0x0056 +#define VL53L0X_REG_PRE_RANGE_CONFIG_VALID_PHASE_HIGH 0x0057 +#define VL53L0X_REG_PRE_RANGE_MIN_COUNT_RATE_RTN_LIMIT 0x0064 + +#define VL53L0X_REG_FINAL_RANGE_CONFIG_MIN_SNR 0X0067 +#define VL53L0X_REG_FINAL_RANGE_CONFIG_VALID_PHASE_LOW 0x0047 +#define VL53L0X_REG_FINAL_RANGE_CONFIG_VALID_PHASE_HIGH 0x0048 +#define VL53L0X_REG_FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT 0x0044 + + +#define VL53L0X_REG_PRE_RANGE_CONFIG_SIGMA_THRESH_HI 0X0061 +#define VL53L0X_REG_PRE_RANGE_CONFIG_SIGMA_THRESH_LO 0X0062 + +/* PRE RANGE registers */ +#define VL53L0X_REG_PRE_RANGE_CONFIG_VCSEL_PERIOD 0x0050 +#define VL53L0X_REG_PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI 0x0051 +#define VL53L0X_REG_PRE_RANGE_CONFIG_TIMEOUT_MACROP_LO 0x0052 + +#define VL53L0X_REG_SYSTEM_HISTOGRAM_BIN 0x0081 +#define VL53L0X_REG_HISTOGRAM_CONFIG_INITIAL_PHASE_SELECT 0x0033 +#define VL53L0X_REG_HISTOGRAM_CONFIG_READOUT_CTRL 0x0055 + +#define VL53L0X_REG_FINAL_RANGE_CONFIG_VCSEL_PERIOD 0x0070 +#define VL53L0X_REG_FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI 0x0071 +#define VL53L0X_REG_FINAL_RANGE_CONFIG_TIMEOUT_MACROP_LO 0x0072 +#define VL53L0X_REG_CROSSTALK_COMPENSATION_PEAK_RATE_MCPS 0x0020 + +#define VL53L0X_REG_MSRC_CONFIG_TIMEOUT_MACROP 0x0046 + + +#define VL53L0X_REG_SOFT_RESET_GO2_SOFT_RESET_N 0x00bf +#define VL53L0X_REG_IDENTIFICATION_MODEL_ID 0x00c0 +#define VL53L0X_REG_IDENTIFICATION_REVISION_ID 0x00c2 + +#define VL53L0X_REG_OSC_CALIBRATE_VAL 0x00f8 + + +#define VL53L0X_SIGMA_ESTIMATE_MAX_VALUE 65535 +/* equivalent to a range sigma of 655.35mm */ + +#define VL53L0X_REG_GLOBAL_CONFIG_VCSEL_WIDTH 0x032 +#define VL53L0X_REG_GLOBAL_CONFIG_SPAD_ENABLES_REF_0 0x0B0 +#define VL53L0X_REG_GLOBAL_CONFIG_SPAD_ENABLES_REF_1 0x0B1 +#define VL53L0X_REG_GLOBAL_CONFIG_SPAD_ENABLES_REF_2 0x0B2 +#define VL53L0X_REG_GLOBAL_CONFIG_SPAD_ENABLES_REF_3 0x0B3 +#define VL53L0X_REG_GLOBAL_CONFIG_SPAD_ENABLES_REF_4 0x0B4 +#define VL53L0X_REG_GLOBAL_CONFIG_SPAD_ENABLES_REF_5 0x0B5 + +#define VL53L0X_REG_GLOBAL_CONFIG_REF_EN_START_SELECT 0xB6 +#define VL53L0X_REG_DYNAMIC_SPAD_NUM_REQUESTED_REF_SPAD 0x4E /* 0x14E */ +#define VL53L0X_REG_DYNAMIC_SPAD_REF_EN_START_OFFSET 0x4F /* 0x14F */ +#define VL53L0X_REG_POWER_MANAGEMENT_GO1_POWER_FORCE 0x80 + +/* + * Speed of light in um per 1E-10 Seconds + */ + +#define VL53L0X_SPEED_OF_LIGHT_IN_AIR 2997 + +#define VL53L0X_REG_VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV 0x0089 + +#define VL53L0X_REG_ALGO_PHASECAL_LIM 0x0030 /* 0x130 */ +#define VL53L0X_REG_ALGO_PHASECAL_CONFIG_TIMEOUT 0x0030 + +/** @} VL53L0X_DefineRegisters_group */ + +/** @} VL53L0X_DevSpecDefines_group */ + + +#endif + +/* _VL53L0X_DEVICE_H_ */ + + diff --git a/lib/vl53l0x/vl53l0x_i2c_platform.h b/lib/vl53l0x/vl53l0x_i2c_platform.h new file mode 100644 index 0000000..cf95ce7 --- /dev/null +++ b/lib/vl53l0x/vl53l0x_i2c_platform.h @@ -0,0 +1,402 @@ +/* + * vl53l0x_i2c_platform.h - Linux kernel modules for STM VL53L0 FlightSense TOF + * sensor + * + * Copyright (C) 2016 STMicroelectronics Imaging Division. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/** + * @file VL53L0X_i2c_platform.h + * @brief Function prototype definitions for EWOK Platform layer. + * + */ + + +#ifndef _VL53L0X_I2C_PLATFORM_H_ +#define _VL53L0X_I2C_PLATFORM_H_ + +#include "vl53l0x_def.h" + + +/** Maximum buffer size to be used in i2c */ +#define VL53L0X_MAX_I2C_XFER_SIZE 64 + +/** + * @brief Typedef defining .\n + * The developer should modify this to suit the platform being deployed. + * + */ + +/** + * @brief Typedef defining 8 bit unsigned char type.\n + * The developer should modify this to suit the platform being deployed. + * + */ + +#ifndef bool_t +typedef unsigned char bool_t; +#endif + + +#define I2C 0x01 +#define SPI 0x00 + +#define COMMS_BUFFER_SIZE 64 +/*MUST be the same size as the SV task buffer */ + +#define BYTES_PER_WORD 2 +#define BYTES_PER_DWORD 4 + +#define VL53L0X_MAX_STRING_LENGTH_PLT 256 + +/** + * @brief Initialise platform comms. + * + * @param comms_type - selects between I2C and SPI + * @param comms_speed_khz - unsigned short containing the I2C speed in kHz + * + * @return status - status 0 = ok, 1 = error + * + */ + +int32_t VL53L0X_comms_initialise(uint8_t comms_type, + uint16_t comms_speed_khz); + +/** + * @brief Close platform comms. + * + * @return status - status 0 = ok, 1 = error + * + */ + +int32_t VL53L0X_comms_close(void); + +/** + * @brief Cycle Power to Device + * + * @return status - status 0 = ok, 1 = error + * + */ + +int32_t VL53L0X_cycle_power(void); + +int32_t VL53L0X_set_page(VL53L0X_DEV dev, uint8_t page_data); + +/** + * @brief Writes the supplied byte buffer to the device + * + * Wrapper for SystemVerilog Write Multi task + * + * @code + * + * Example: + * + * uint8_t *spad_enables; + * + * int status = VL53L0X_write_multi(RET_SPAD_EN_0, spad_enables, 36); + * + * @endcode + * + * @param address - uint8_t device address value + * @param index - uint8_t register index value + * @param pdata - pointer to uint8_t buffer containing the data to be written + * @param count - number of bytes in the supplied byte buffer + * + * @return status - SystemVerilog status 0 = ok, 1 = error + * + */ + +int32_t VL53L0X_write_multi(VL53L0X_DEV dev, uint8_t index, uint8_t *pdata, + int32_t count); + + +/** + * @brief Reads the requested number of bytes from the device + * + * Wrapper for SystemVerilog Read Multi task + * + * @code + * + * Example: + * + * uint8_t buffer[COMMS_BUFFER_SIZE]; + * + * int status = status = VL53L0X_read_multi(DEVICE_ID, buffer, 2) + * + * @endcode + * + * @param address - uint8_t device address value + * @param index - uint8_t register index value + * @param pdata - pointer to the uint8_t buffer to store read data + * @param count - number of uint8_t's to read + * + * @return status - SystemVerilog status 0 = ok, 1 = error + * + */ + +int32_t VL53L0X_read_multi(VL53L0X_DEV dev, uint8_t index, uint8_t *pdata, + int32_t count); + + +/** + * @brief Writes a single byte to the device + * + * Wrapper for SystemVerilog Write Byte task + * + * @code + * + * Example: + * + * uint8_t page_number = MAIN_SELECT_PAGE; + * + * int status = VL53L0X_write_byte(PAGE_SELECT, page_number); + * + * @endcode + * + * @param address - uint8_t device address value + * @param index - uint8_t register index value + * @param data - uint8_t data value to write + * + * @return status - SystemVerilog status 0 = ok, 1 = error + * + */ + +int32_t VL53L0X_write_byte(VL53L0X_DEV dev, uint8_t index, uint8_t data); + + +/** + * @brief Writes a single word (16-bit unsigned) to the device + * + * Manages the big-endian nature of the device (first byte written is the + * MS byte). + * Uses SystemVerilog Write Multi task. + * + * @code + * + * Example: + * + * uint16_t nvm_ctrl_pulse_width = 0x0004; + * + * int status = VL53L0X_write_word(NVM_CTRL__PULSE_WIDTH_MSB, + * nvm_ctrl_pulse_width); + * + * @endcode + * + * @param address - uint8_t device address value + * @param index - uint8_t register index value + * @param data - uin16_t data value write + * + * @return status - SystemVerilog status 0 = ok, 1 = error + * + */ + +int32_t VL53L0X_write_word(VL53L0X_DEV dev, uint8_t index, uint16_t data); + + +/** + * @brief Writes a single dword (32-bit unsigned) to the device + * + * Manages the big-endian nature of the device (first byte written is the + * MS byte). + * Uses SystemVerilog Write Multi task. + * + * @code + * + * Example: + * + * uint32_t nvm_data = 0x0004; + * + * int status = VL53L0X_write_dword(NVM_CTRL__DATAIN_MMM, nvm_data); + * + * @endcode + * + * @param address - uint8_t device address value + * @param index - uint8_t register index value + * @param data - uint32_t data value to write + * + * @return status - SystemVerilog status 0 = ok, 1 = error + * + */ + +int32_t VL53L0X_write_dword(VL53L0X_DEV dev, uint8_t index, uint32_t data); + + + +/** + * @brief Reads a single byte from the device + * + * Uses SystemVerilog Read Byte task. + * + * @code + * + * Example: + * + * uint8_t device_status = 0; + * + * int status = VL53L0X_read_byte(STATUS, &device_status); + * + * @endcode + * + * @param address - uint8_t device address value + * @param index - uint8_t register index value + * @param pdata - pointer to uint8_t data value + * + * @return status - SystemVerilog status 0 = ok, 1 = error + * + */ + +int32_t VL53L0X_read_byte(VL53L0X_DEV dev, uint8_t index, uint8_t *pdata); + + +/** + * @brief Reads a single word (16-bit unsigned) from the device + * + * Manages the big-endian nature of the device (first byte read is the MS byte). + * Uses SystemVerilog Read Multi task. + * + * @code + * + * Example: + * + * uint16_t timeout = 0; + * + * int status = VL53L0X_read_word(TIMEOUT_OVERALL_PERIODS_MSB, &timeout); + * + * @endcode + * + * @param address - uint8_t device address value + * @param index - uint8_t register index value + * @param pdata - pointer to uint16_t data value + * + * @return status - SystemVerilog status 0 = ok, 1 = error + * + */ + +int32_t VL53L0X_read_word(VL53L0X_DEV dev, uint8_t index, uint16_t *pdata); + + +/** + * @brief Reads a single dword (32-bit unsigned) from the device + * + * Manages the big-endian nature of the device (first byte read is the MS byte). + * Uses SystemVerilog Read Multi task. + * + * @code + * + * Example: + * + * uint32_t range_1 = 0; + * + * int status = VL53L0X_read_dword(RANGE_1_MMM, &range_1); + * + * @endcode + * + * @param address - uint8_t device address value + * @param index - uint8_t register index value + * @param pdata - pointer to uint32_t data value + * + * @return status - SystemVerilog status 0 = ok, 1 = error + * + */ + +int32_t VL53L0X_read_dword(VL53L0X_DEV dev, uint8_t index, uint32_t *pdata); + + +/** + * @brief Implements a programmable wait in us + * + * Wrapper for SystemVerilog Wait in micro seconds task + * + * @param wait_us - integer wait in micro seconds + * + * @return status - SystemVerilog status 0 = ok, 1 = error + * + */ + +int32_t VL53L0X_platform_wait_us(int32_t wait_us); + + +/** + * @brief Implements a programmable wait in ms + * + * Wrapper for SystemVerilog Wait in milli seconds task + * + * @param wait_ms - integer wait in milli seconds + * + * @return status - SystemVerilog status 0 = ok, 1 = error + * + */ + +int32_t VL53L0X_wait_ms(int32_t wait_ms); + + +/** + * @brief Set GPIO value + * + * @param level - input level - either 0 or 1 + * + * @return status - SystemVerilog status 0 = ok, 1 = error + * + */ + +int32_t VL53L0X_set_gpio(uint8_t level); + + +/** + * @brief Get GPIO value + * + * @param plevel - uint8_t pointer to store GPIO level (0 or 1) + * + * @return status - SystemVerilog status 0 = ok, 1 = error + * + */ + +int32_t VL53L0X_get_gpio(uint8_t *plevel); + +/** + * @brief Release force on GPIO + * + * @return status - SystemVerilog status 0 = ok, 1 = error + * + */ + +int32_t VL53L0X_release_gpio(void); + + +/** +* @brief Get the frequency of the timer used for ranging results time stamps +* +* @param[out] ptimer_freq_hz : pointer for timer frequency +* +* @return status : 0 = ok, 1 = error +* +*/ + +int32_t VL53L0X_get_timer_frequency(int32_t *ptimer_freq_hz); + +/** +* @brief Get the timer value in units of timer_freq_hz +* (see VL53L0X_get_timestamp_frequency()) +* +* @param[out] ptimer_count : pointer for timer count value +* +* @return status : 0 = ok, 1 = error +* +*/ + +int32_t VL53L0X_get_timer_value(int32_t *ptimer_count); +int VL53L0X_I2CWrite(VL53L0X_DEV dev, uint8_t *buff, uint8_t len); +int VL53L0X_I2CRead(VL53L0X_DEV dev, uint8_t *buff, uint8_t len); + +#endif /* _VL53L0X_I2C_PLATFORM_H_ */ + diff --git a/lib/vl53l0x/vl53l0x_interrupt_threshold_settings.h b/lib/vl53l0x/vl53l0x_interrupt_threshold_settings.h new file mode 100644 index 0000000..d3edb6c --- /dev/null +++ b/lib/vl53l0x/vl53l0x_interrupt_threshold_settings.h @@ -0,0 +1,194 @@ +/******************************************************************************* + * Copyright 2016, STMicroelectronics International N.V. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of STMicroelectronics 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, FITNESS FOR A PARTICULAR PURPOSE, AND +NON-INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS ARE DISCLAIMED. +IN NO EVENT SHALL STMICROELECTRONICS INTERNATIONAL N.V. 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. +*******************************************************************************/ + + +#ifndef _VL53L0X_INTERRUPT_THRESHOLD_SETTINGS_H_ +#define _VL53L0X_INTERRUPT_THRESHOLD_SETTINGS_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + + +uint8_t InterruptThresholdSettings[] = { + + /* Start of Interrupt Threshold Settings */ + 0x1, 0xff, 0x00, + 0x1, 0x80, 0x01, + 0x1, 0xff, 0x01, + 0x1, 0x00, 0x00, + 0x1, 0xff, 0x01, + 0x1, 0x4f, 0x02, + 0x1, 0xFF, 0x0E, + 0x1, 0x00, 0x03, + 0x1, 0x01, 0x84, + 0x1, 0x02, 0x0A, + 0x1, 0x03, 0x03, + 0x1, 0x04, 0x08, + 0x1, 0x05, 0xC8, + 0x1, 0x06, 0x03, + 0x1, 0x07, 0x8D, + 0x1, 0x08, 0x08, + 0x1, 0x09, 0xC6, + 0x1, 0x0A, 0x01, + 0x1, 0x0B, 0x02, + 0x1, 0x0C, 0x00, + 0x1, 0x0D, 0xD5, + 0x1, 0x0E, 0x18, + 0x1, 0x0F, 0x12, + 0x1, 0x10, 0x01, + 0x1, 0x11, 0x82, + 0x1, 0x12, 0x00, + 0x1, 0x13, 0xD5, + 0x1, 0x14, 0x18, + 0x1, 0x15, 0x13, + 0x1, 0x16, 0x03, + 0x1, 0x17, 0x86, + 0x1, 0x18, 0x0A, + 0x1, 0x19, 0x09, + 0x1, 0x1A, 0x08, + 0x1, 0x1B, 0xC2, + 0x1, 0x1C, 0x03, + 0x1, 0x1D, 0x8F, + 0x1, 0x1E, 0x0A, + 0x1, 0x1F, 0x06, + 0x1, 0x20, 0x01, + 0x1, 0x21, 0x02, + 0x1, 0x22, 0x00, + 0x1, 0x23, 0xD5, + 0x1, 0x24, 0x18, + 0x1, 0x25, 0x22, + 0x1, 0x26, 0x01, + 0x1, 0x27, 0x82, + 0x1, 0x28, 0x00, + 0x1, 0x29, 0xD5, + 0x1, 0x2A, 0x18, + 0x1, 0x2B, 0x0B, + 0x1, 0x2C, 0x28, + 0x1, 0x2D, 0x78, + 0x1, 0x2E, 0x28, + 0x1, 0x2F, 0x91, + 0x1, 0x30, 0x00, + 0x1, 0x31, 0x0B, + 0x1, 0x32, 0x00, + 0x1, 0x33, 0x0B, + 0x1, 0x34, 0x00, + 0x1, 0x35, 0xA1, + 0x1, 0x36, 0x00, + 0x1, 0x37, 0xA0, + 0x1, 0x38, 0x00, + 0x1, 0x39, 0x04, + 0x1, 0x3A, 0x28, + 0x1, 0x3B, 0x30, + 0x1, 0x3C, 0x0C, + 0x1, 0x3D, 0x04, + 0x1, 0x3E, 0x0F, + 0x1, 0x3F, 0x79, + 0x1, 0x40, 0x28, + 0x1, 0x41, 0x1E, + 0x1, 0x42, 0x2F, + 0x1, 0x43, 0x87, + 0x1, 0x44, 0x00, + 0x1, 0x45, 0x0B, + 0x1, 0x46, 0x00, + 0x1, 0x47, 0x0B, + 0x1, 0x48, 0x00, + 0x1, 0x49, 0xA7, + 0x1, 0x4A, 0x00, + 0x1, 0x4B, 0xA6, + 0x1, 0x4C, 0x00, + 0x1, 0x4D, 0x04, + 0x1, 0x4E, 0x01, + 0x1, 0x4F, 0x00, + 0x1, 0x50, 0x00, + 0x1, 0x51, 0x80, + 0x1, 0x52, 0x09, + 0x1, 0x53, 0x08, + 0x1, 0x54, 0x01, + 0x1, 0x55, 0x00, + 0x1, 0x56, 0x0F, + 0x1, 0x57, 0x79, + 0x1, 0x58, 0x09, + 0x1, 0x59, 0x05, + 0x1, 0x5A, 0x00, + 0x1, 0x5B, 0x60, + 0x1, 0x5C, 0x05, + 0x1, 0x5D, 0xD1, + 0x1, 0x5E, 0x0C, + 0x1, 0x5F, 0x3C, + 0x1, 0x60, 0x00, + 0x1, 0x61, 0xD0, + 0x1, 0x62, 0x0B, + 0x1, 0x63, 0x03, + 0x1, 0x64, 0x28, + 0x1, 0x65, 0x10, + 0x1, 0x66, 0x2A, + 0x1, 0x67, 0x39, + 0x1, 0x68, 0x0B, + 0x1, 0x69, 0x02, + 0x1, 0x6A, 0x28, + 0x1, 0x6B, 0x10, + 0x1, 0x6C, 0x2A, + 0x1, 0x6D, 0x61, + 0x1, 0x6E, 0x0C, + 0x1, 0x6F, 0x00, + 0x1, 0x70, 0x0F, + 0x1, 0x71, 0x79, + 0x1, 0x72, 0x00, + 0x1, 0x73, 0x0B, + 0x1, 0x74, 0x00, + 0x1, 0x75, 0x0B, + 0x1, 0x76, 0x00, + 0x1, 0x77, 0xA1, + 0x1, 0x78, 0x00, + 0x1, 0x79, 0xA0, + 0x1, 0x7A, 0x00, + 0x1, 0x7B, 0x04, + 0x1, 0xFF, 0x04, + 0x1, 0x79, 0x1D, + 0x1, 0x7B, 0x27, + 0x1, 0x96, 0x0E, + 0x1, 0x97, 0xFE, + 0x1, 0x98, 0x03, + 0x1, 0x99, 0xEF, + 0x1, 0x9A, 0x02, + 0x1, 0x9B, 0x44, + 0x1, 0x73, 0x07, + 0x1, 0x70, 0x01, + 0x1, 0xff, 0x01, + 0x1, 0x00, 0x01, + 0x1, 0xff, 0x00, + 0x00, 0x00, 0x00 +}; + +#ifdef __cplusplus +} +#endif + +#endif /* _VL53L0X_INTERRUPT_THRESHOLD_SETTINGS_H_ */ diff --git a/lib/vl53l0x/vl53l0x_platform.h b/lib/vl53l0x/vl53l0x_platform.h new file mode 100644 index 0000000..95e8f29 --- /dev/null +++ b/lib/vl53l0x/vl53l0x_platform.h @@ -0,0 +1,41 @@ +#ifndef VL53L0X_PLATFORM_H_ +#define VL53L0X_PLATFORM_H_ + +#include "vl53l0x_def.h" +#include "vl53l0x_platform_log.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L0X_MAX_I2C_XFER_SIZE 64 + +typedef struct { + VL53L0X_DevData_t Data; + uint8_t I2cDevAddr; + int i2c_fd; +} vl53l0x_dev_t; + +typedef vl53l0x_dev_t *VL53L0X_DEV; + +#define PALDevDataGet(Dev, field) (Dev->Data.field) +#define PALDevDataSet(Dev, field, data) ((Dev->Data.field) = (data)) + +VL53L0X_Error VL53L0X_LockSequenceAccess(VL53L0X_DEV Dev); +VL53L0X_Error VL53L0X_UnlockSequenceAccess(VL53L0X_DEV Dev); +VL53L0X_Error VL53L0X_WriteMulti(VL53L0X_DEV Dev, uint8_t index, uint8_t *pdata, uint32_t count); +VL53L0X_Error VL53L0X_ReadMulti(VL53L0X_DEV Dev, uint8_t index, uint8_t *pdata, uint32_t count); +VL53L0X_Error VL53L0X_WrByte(VL53L0X_DEV Dev, uint8_t index, uint8_t data); +VL53L0X_Error VL53L0X_WrWord(VL53L0X_DEV Dev, uint8_t index, uint16_t data); +VL53L0X_Error VL53L0X_WrDWord(VL53L0X_DEV Dev, uint8_t index, uint32_t data); +VL53L0X_Error VL53L0X_RdByte(VL53L0X_DEV Dev, uint8_t index, uint8_t *data); +VL53L0X_Error VL53L0X_RdWord(VL53L0X_DEV Dev, uint8_t index, uint16_t *data); +VL53L0X_Error VL53L0X_RdDWord(VL53L0X_DEV Dev, uint8_t index, uint32_t *data); +VL53L0X_Error VL53L0X_UpdateByte(VL53L0X_DEV Dev, uint8_t index, uint8_t AndData, uint8_t OrData); +VL53L0X_Error VL53L0X_PollingDelay(VL53L0X_DEV Dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/vl53l0x/vl53l0x_platform_log.h b/lib/vl53l0x/vl53l0x_platform_log.h new file mode 100644 index 0000000..5d09b73 --- /dev/null +++ b/lib/vl53l0x/vl53l0x_platform_log.h @@ -0,0 +1,14 @@ +#ifndef VL53L0X_PLATFORM_LOG_H_ +#define VL53L0X_PLATFORM_LOG_H_ + +#include + +#define VL53L0X_COPYSTRING(str, ...) strncpy(str, ##__VA_ARGS__, sizeof(str)) + +#define _LOG_FUNCTION_START(module, fmt, ...) (void)0 +#define _LOG_FUNCTION_END(module, status, ...) (void)0 +#define _LOG_FUNCTION_END_FMT(module, status, fmt, ...) (void)0 + +#define VL53L0X_ErrLog(...) (void)0 + +#endif diff --git a/lib/vl53l0x/vl53l0x_tuning.h b/lib/vl53l0x/vl53l0x_tuning.h new file mode 100644 index 0000000..fa8418d --- /dev/null +++ b/lib/vl53l0x/vl53l0x_tuning.h @@ -0,0 +1,146 @@ +/******************************************************************************* + * Copyright 2016, STMicroelectronics International N.V. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of STMicroelectronics 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, FITNESS FOR A PARTICULAR PURPOSE, AND +NON-INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS ARE DISCLAIMED. +IN NO EVENT SHALL STMICROELECTRONICS INTERNATIONAL N.V. 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. +*******************************************************************************/ + + +#ifndef _VL53L0X_TUNING_H_ +#define _VL53L0X_TUNING_H_ + +#include "vl53l0x_def.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +uint8_t DefaultTuningSettings[] = { + + /* update 02/11/2015_v36 */ + 0x01, 0xFF, 0x01, + 0x01, 0x00, 0x00, + + 0x01, 0xFF, 0x00, + 0x01, 0x09, 0x00, + 0x01, 0x10, 0x00, + 0x01, 0x11, 0x00, + + 0x01, 0x24, 0x01, + 0x01, 0x25, 0xff, + 0x01, 0x75, 0x00, + + 0x01, 0xFF, 0x01, + 0x01, 0x4e, 0x2c, + 0x01, 0x48, 0x00, + 0x01, 0x30, 0x20, + + 0x01, 0xFF, 0x00, + 0x01, 0x30, 0x09, /* mja changed from 0x64. */ + 0x01, 0x54, 0x00, + 0x01, 0x31, 0x04, + 0x01, 0x32, 0x03, + 0x01, 0x40, 0x83, + 0x01, 0x46, 0x25, + 0x01, 0x60, 0x00, + 0x01, 0x27, 0x00, + 0x01, 0x50, 0x06, + 0x01, 0x51, 0x00, + 0x01, 0x52, 0x96, + 0x01, 0x56, 0x08, + 0x01, 0x57, 0x30, + 0x01, 0x61, 0x00, + 0x01, 0x62, 0x00, + 0x01, 0x64, 0x00, + 0x01, 0x65, 0x00, + 0x01, 0x66, 0xa0, + + 0x01, 0xFF, 0x01, + 0x01, 0x22, 0x32, + 0x01, 0x47, 0x14, + 0x01, 0x49, 0xff, + 0x01, 0x4a, 0x00, + + 0x01, 0xFF, 0x00, + 0x01, 0x7a, 0x0a, + 0x01, 0x7b, 0x00, + 0x01, 0x78, 0x21, + + 0x01, 0xFF, 0x01, + 0x01, 0x23, 0x34, + 0x01, 0x42, 0x00, + 0x01, 0x44, 0xff, + 0x01, 0x45, 0x26, + 0x01, 0x46, 0x05, + 0x01, 0x40, 0x40, + 0x01, 0x0E, 0x06, + 0x01, 0x20, 0x1a, + 0x01, 0x43, 0x40, + + 0x01, 0xFF, 0x00, + 0x01, 0x34, 0x03, + 0x01, 0x35, 0x44, + + 0x01, 0xFF, 0x01, + 0x01, 0x31, 0x04, + 0x01, 0x4b, 0x09, + 0x01, 0x4c, 0x05, + 0x01, 0x4d, 0x04, + + + 0x01, 0xFF, 0x00, + 0x01, 0x44, 0x00, + 0x01, 0x45, 0x20, + 0x01, 0x47, 0x08, + 0x01, 0x48, 0x28, + 0x01, 0x67, 0x00, + 0x01, 0x70, 0x04, + 0x01, 0x71, 0x01, + 0x01, 0x72, 0xfe, + 0x01, 0x76, 0x00, + 0x01, 0x77, 0x00, + + 0x01, 0xFF, 0x01, + 0x01, 0x0d, 0x01, + + 0x01, 0xFF, 0x00, + 0x01, 0x80, 0x01, + 0x01, 0x01, 0xF8, + + 0x01, 0xFF, 0x01, + 0x01, 0x8e, 0x01, + 0x01, 0x00, 0x01, + 0x01, 0xFF, 0x00, + 0x01, 0x80, 0x00, + + 0x00, 0x00, 0x00 +}; + +#ifdef __cplusplus +} +#endif + +#endif /* _VL53L0X_TUNING_H_ */ diff --git a/lib/vl53l0x/vl53l0x_types.h b/lib/vl53l0x/vl53l0x_types.h new file mode 100644 index 0000000..c205f46 --- /dev/null +++ b/lib/vl53l0x/vl53l0x_types.h @@ -0,0 +1,13 @@ +#ifndef VL53L0X_TYPES_H_ +#define VL53L0X_TYPES_H_ + +#include +#include + +#ifndef NULL +#define NULL 0 +#endif + +typedef unsigned int FixPoint1616_t; + +#endif diff --git a/lib/vl53l0x_direct.h b/lib/vl53l0x_direct.h new file mode 100644 index 0000000..f7a7163 --- /dev/null +++ b/lib/vl53l0x_direct.h @@ -0,0 +1,23 @@ +#pragma once + +extern "C" { +#include "vl53l0x_platform.h" +} + +class VL53L0X_Direct +{ +public: + VL53L0X_Direct(); + ~VL53L0X_Direct(); + + bool init(); + bool readRange(VL53L0X_RangingMeasurementData_t &data); + void stop(); + bool isInitialized() const { return initialized; } + +private: + bool initialized; + vl53l0x_dev_t dev; + + static void unbindKernel(); +}; diff --git a/main/main.cpp b/main/main.cpp index 1c4c0d6..702d389 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "global.h" #include "camera.h" @@ -25,6 +27,15 @@ int main(void) if (avoid_range_val > 0) g_cfg.cone_avoid_range = avoid_range_val; if (hold_frames_val > 0) g_cfg.cone_hold_frames = hold_frames_val; + double lidar_gain_val = readDoubleFromFile(lidar_avoid_gain_file); + int lidar_range_val = (int)readDoubleFromFile(lidar_avoid_range_file); + int lidar_hold_val = (int)readDoubleFromFile(lidar_hold_frames_file); + int lidar_thresh_val = (int)readDoubleFromFile(lidar_thresh_file); + if (lidar_gain_val > 0) g_cfg.lidar_avoid_gain = lidar_gain_val; + if (lidar_range_val > 0) g_cfg.lidar_avoid_range = lidar_range_val; + if (lidar_hold_val > 0) g_cfg.lidar_hold_frames = lidar_hold_val; + if (lidar_thresh_val > 0) g_cfg.lidar_thresh = lidar_thresh_val; + if (CameraInit(0) < 0) { std::cerr << "CameraInit failed" << std::endl; return -1; @@ -32,9 +43,12 @@ int main(void) ControlInit(); std::cout << "All services started" << std::endl; + setpriority(PRIO_PROCESS, 0, -10); + while (running.load()) { CameraHandler(); target_speed = g_cfg.speed; + sched_yield(); } std::cout << "Stopping..." << std::endl; diff --git a/mild_v12.bin b/mild_v12.bin index a7afcff80232e7dc64f4ff34c0e03f1808487839..45d3ba4ecfee9608babfa72ea1d76404a8d6bf1f 100644 GIT binary patch literal 105014 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 LkkdQ8S(l?cv<@ib literal 105014 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

1pb<=PbB9{9!`7WPPpry(ru_JBZTiy43%^9> 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%0A&^Iz=%tXn=Y$O(@djLDkTk4XERuMT$5e# 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 AmH+?% diff --git a/src/camera.cpp b/src/camera.cpp index 6fd6c82..ec2ece4 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -1,5 +1,6 @@ #include "camera.h" #include "model_v10.hpp" +#include "vl53l0x.h" #include #include @@ -72,6 +73,19 @@ static int g_cone_hold_ctr = 0; static int g_cone_hold_src_row = -1; static int g_cone_hold_src_col = -1; +// ═══════════════════════════════════════════════════════════ +// 激光雷达挡板避障 (主循环每 5 帧同步读取一次) +// ═══════════════════════════════════════════════════════════ +static VL53L0X g_lidar_sensor; +static bool g_lidar_ok = false; +static int g_lidar_frames = 0; +static bool g_lidar_confirmed = false; +static int g_lidar_obstacle_row = -1; +static int g_lidar_ref_row = -1; +static int g_lidar_hold_ctr = 0; +static int g_lidar_hold_src_row = -1; +static double g_lidar_hold_dir = 0; + // ═══════════════════════════════════════════════════════════ // LCD & FPS // ═══════════════════════════════════════════════════════════ @@ -178,6 +192,18 @@ int CameraInit(int camera_id) printf("[MODEL] Mild v12 模型已加载\n"); i2c_audio_open(); + + // 只在启用时才初始化激光,避免驱动内核定时器拖慢 CPU + if (g_cfg.lidar_enable) { + g_lidar_ok = g_lidar_sensor.init(); + if (g_lidar_ok) + printf("[LIDAR] VL53L0X 已初始化\n"); + else + printf("[LIDAR] VL53L0X 未连接, 避障禁用\n"); + } else { + printf("[LIDAR] 已禁用\n"); + } + return 0; } @@ -191,6 +217,7 @@ void cameraDeInit(void) } close(fb); if (i2c_audio_fd >= 0) close(i2c_audio_fd); + if (g_lidar_ok) g_lidar_sensor.stop(); model_v10_deinit(); } @@ -350,6 +377,116 @@ static bool traffic_light_process() return (g_tl_state != TL_NORMAL); } +// ═══════════════════════════════════════════════════════════ +// 激光雷达挡板避障 +// ═══════════════════════════════════════════════════════════ +static bool lidar_is_active() { + return g_lidar_confirmed || + (g_lidar_hold_ctr > 0 && g_lidar_hold_ctr <= g_cfg.lidar_hold_frames); +} + +static void lidar_avoid_process() +{ + // 每 5 帧读取一次, 单次测距 ~20ms (高速模式) + static int lidar_skip = 0; + if (!g_lidar_ok || !g_cfg.lidar_enable) return; + if (++lidar_skip < 5) return; + lidar_skip = 0; + + // ── 1. 单次激光测距 (高速模式 ~20ms) ── + VL53L0X_RangingMeasurementData_t data; + if (!g_lidar_sensor.readRange(data)) return; + int d_mm = data.RangeMilliMeter; + + if (d_mm >= g_cfg.lidar_thresh) { + if (g_lidar_frames > 0) + g_lidar_frames = std::max(0, g_lidar_frames - 1); + goto hold_decay; + } + + // ── 2. 距离 → 图像行映射 ── + { + int lt_h = line_tracking_height; + int row = lt_h - 1 - (d_mm - g_cfg.lidar_near) * (lt_h - 11) + / (g_cfg.lidar_far - g_cfg.lidar_near); + if (row < 10) row = 10; + if (row >= lt_h) row = lt_h - 1; + + // ── 3a. 近处边线有效检查 ── + bool near_valid = false; + int near_row = -1; + int near_start = std::min(row + g_cfg.lidar_near_start, lt_h - 1); + int near_end = std::min(row + g_cfg.lidar_near_end, lt_h - 1); + for (int r = near_start; r <= near_end; ++r) { + if (left_line[r] != -1 && right_line[r] != -1) { + near_valid = true; + near_row = r; + break; + } + } + + // ── 3b. 远处丢线检查 ── + bool far_lost = false; + int far_start = std::max(row - g_cfg.lidar_far_span, 10); + for (int r = row; r >= far_start; --r) { + if (left_line[r] == -1 || right_line[r] == -1) { + far_lost = true; + break; + } + } + + // ── 3c. 联合判定 ── + if (near_valid && far_lost) { + g_lidar_frames++; + g_lidar_obstacle_row = row; + g_lidar_ref_row = near_row; + } else { + if (g_lidar_frames > 0) + g_lidar_frames = std::max(0, g_lidar_frames - 1); + } + } + + g_lidar_confirmed = (g_lidar_frames >= g_cfg.lidar_min_frames); + + // ── 保持/衰减 ── + if (g_lidar_confirmed) { + g_lidar_hold_ctr = 0; + g_lidar_hold_src_row = g_lidar_obstacle_row; + // 在参考行判断左右空间,往宽侧推 + int r = g_lidar_ref_row; + double lspace = mid_line[r] - left_line[r]; + double rspace = right_line[r] - mid_line[r]; + g_lidar_hold_dir = (lspace > rspace) ? 1.0 : -1.0; + if (g_cfg.debug) + printf("[LIDAR] 触发 d=%d row=%d dir=%.0f\n", d_mm, g_lidar_obstacle_row, g_lidar_hold_dir); + g_lidar_frames = 0; + } + +hold_decay: + if (g_lidar_hold_src_row > 0 && g_lidar_hold_ctr <= g_cfg.lidar_hold_frames) { + g_lidar_hold_ctr++; + } + + if (!lidar_is_active()) return; + + // ── 中线变形 ── + int src_row = g_lidar_hold_src_row; + double rng = (double)g_cfg.lidar_avoid_range; + double half_w = line_tracking_width / 2.0; + double dir = g_lidar_hold_dir; + double decay = g_lidar_confirmed ? 1.0 : + (1.0 - (double)g_lidar_hold_ctr / g_cfg.lidar_hold_frames); + + for (int row = 10; row <= src_row; ++row) { + double t = std::clamp(((row - 10) / rng), 0.0, 1.0); + double push = t * g_cfg.lidar_avoid_gain * half_w * dir * decay; + double nm = std::clamp(mid_line[row] + push, + (double)left_line[row] + 2.0, + (double)right_line[row] - 2.0); + mid_line[row] = (int)(nm + 0.5); + } +} + // ═══════════════════════════════════════════════════════════ // 锥桶检测 + 中线变形 // ═══════════════════════════════════════════════════════════ @@ -476,6 +613,8 @@ static void motor_update(bool zebra_block, bool tl_block) double spd = target_speed; if (cone_is_slow() && g_zstate != Z_STOP && g_tl_state == TL_NORMAL) spd *= g_cfg.cone_speed; + if (lidar_is_active() && g_zstate != Z_STOP && g_tl_state == TL_NORMAL) + spd *= g_cfg.lidar_speed; ControlUpdate(spd, zebra_block || tl_block); } @@ -534,6 +673,7 @@ static void lcd_render() // 状态指示 char ztxt[8]; int zi = 0; + if (lidar_is_active()) ztxt[zi++] = 'L'; if (g_tl_state == TL_STOP) ztxt[zi++] = 'R'; else if (g_tl_state == TL_WAIT_GREEN) ztxt[zi++] = 'G'; if (g_zstate == Z_STOP) ztxt[zi++] = 'S'; @@ -596,6 +736,7 @@ int CameraHandler(void) // 5. 场景识别 bool zebra_block = zebra_process(); bool tl_block = traffic_light_process(); + lidar_avoid_process(); cone_detect_and_deform(); // 6. 舵机 diff --git a/src/global.cpp b/src/global.cpp index f2a1383..28d8ebf 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -39,4 +39,17 @@ void cfg_load_all() g_cfg.brake_scale = readDoubleFromFile(brake_scale_file); g_cfg.brake_max = readDoubleFromFile(brake_max_file); + + g_cfg.lidar_thresh = (int)readDoubleFromFile(lidar_thresh_file); + g_cfg.lidar_near = (int)readDoubleFromFile(lidar_near_file); + g_cfg.lidar_far = (int)readDoubleFromFile(lidar_far_file); + g_cfg.lidar_near_start = (int)readDoubleFromFile(lidar_near_start_file); + g_cfg.lidar_near_end = (int)readDoubleFromFile(lidar_near_end_file); + g_cfg.lidar_far_span = (int)readDoubleFromFile(lidar_far_span_file); + g_cfg.lidar_min_frames = (int)readDoubleFromFile(lidar_min_frames_file); + g_cfg.lidar_avoid_gain = readDoubleFromFile(lidar_avoid_gain_file); + g_cfg.lidar_avoid_range = (int)readDoubleFromFile(lidar_avoid_range_file); + g_cfg.lidar_speed = readDoubleFromFile(lidar_speed_file); + g_cfg.lidar_hold_frames = (int)readDoubleFromFile(lidar_hold_frames_file); + g_cfg.lidar_enable = (int)readDoubleFromFile(lidar_enable_file); } diff --git a/src/vl53l0x.cpp b/src/vl53l0x.cpp index 524effd..bba6e38 100644 --- a/src/vl53l0x.cpp +++ b/src/vl53l0x.cpp @@ -2,71 +2,143 @@ #include #include #include +#include #include -#include #include +#include -#define VL53L0X_IOCTL_INIT _IO('p', 0x01) -#define VL53L0X_IOCTL_STOP _IO('p', 0x05) -#define VL53L0X_IOCTL_GETDATA _IOR('p', 0x0b, VL53L0X_RangingMeasurementData_t) +extern "C" { +#include "vl53l0x_api.h" +} -VL53L0X::VL53L0X() : fd(-1) {} +static void unbind_kernel_driver() +{ + FILE *f = fopen("/sys/bus/i2c/drivers/stmvl53l0/unbind", "w"); + if (f) { + fprintf(f, "1-0029"); + fclose(f); + } +} + +VL53L0X::VL53L0X() + : fd(-1), initialized(false) +{ + memset(&dev, 0, sizeof(dev)); +} VL53L0X::~VL53L0X() { - if (fd > 0) - { - stop(); - close(fd); - fd = -1; - } + stop(); } bool VL53L0X::init() { - fd = open("/dev/stmvl53l0x_ranging", O_RDWR | O_SYNC); - if (fd <= 0) - { - fprintf(stderr, "[VL53L0X] open failed: %s\n", strerror(errno)); - return false; - } + unbind_kernel_driver(); + usleep(100000); - ioctl(fd, VL53L0X_IOCTL_STOP, nullptr); + fd = open("/dev/i2c-1", O_RDWR); + if (fd < 0) { + fprintf(stderr, "[VL53L0X] open /dev/i2c-1 failed: %s\n", strerror(errno)); + return false; + } - if (ioctl(fd, VL53L0X_IOCTL_INIT, nullptr) < 0) - { - fprintf(stderr, "[VL53L0X] init failed: %s\n", strerror(errno)); - close(fd); - fd = -1; - return false; - } + if (ioctl(fd, I2C_SLAVE, 0x29) < 0) { + fprintf(stderr, "[VL53L0X] I2C_SLAVE failed: %s\n", strerror(errno)); + close(fd); + fd = -1; + return false; + } - fprintf(stderr, "[VL53L0X] init OK\n"); - return true; + dev.I2cDevAddr = 0x29; + dev.i2c_fd = fd; + + VL53L0X_Error Status = VL53L0X_DataInit(&dev); + if (Status != VL53L0X_ERROR_NONE) { + fprintf(stderr, "[VL53L0X] DataInit failed: %d\n", Status); + close(fd); + fd = -1; + return false; + } + + Status = VL53L0X_StaticInit(&dev); + if (Status != VL53L0X_ERROR_NONE) { + fprintf(stderr, "[VL53L0X] StaticInit failed: %d\n", Status); + close(fd); + fd = -1; + return false; + } + + uint32_t refSpadCount; + uint8_t isApertureSpads; + uint8_t VhvSettings; + uint8_t PhaseCal; + + VL53L0X_PerformRefCalibration(&dev, &VhvSettings, &PhaseCal); + VL53L0X_PerformRefSpadManagement(&dev, &refSpadCount, &isApertureSpads); + + Status = VL53L0X_SetDeviceMode(&dev, VL53L0X_DEVICEMODE_SINGLE_RANGING); + if (Status != VL53L0X_ERROR_NONE) { + fprintf(stderr, "[VL53L0X] SetDeviceMode failed: %d\n", Status); + close(fd); + fd = -1; + return false; + } + + VL53L0X_SetMeasurementTimingBudgetMicroSeconds(&dev, 20000); + + uint32_t actualBudget = 0; + VL53L0X_GetMeasurementTimingBudgetMicroSeconds(&dev, &actualBudget); + fprintf(stderr, "[VL53L0X] timing budget = %u us\n", actualBudget); + + initialized = true; + fprintf(stderr, "[VL53L0X] init OK\n"); + return true; } bool VL53L0X::readRange(VL53L0X_RangingMeasurementData_t &data) { - if (fd <= 0) - return false; + if (!initialized) return false; - if (ioctl(fd, VL53L0X_IOCTL_GETDATA, &data) < 0) - { - fprintf(stderr, "[VL53L0X] read failed: %s\n", strerror(errno)); - return false; - } - return true; + VL53L0X_Error Status; + + VL53L0X_ClearInterruptMask(&dev, 0); + Status = VL53L0X_StartMeasurement(&dev); + if (Status != VL53L0X_ERROR_NONE) { + fprintf(stderr, "[VL53L0X] StartMeasurement failed: %d\n", Status); + return false; + } + + uint8_t ready = 0; + int timeout = 500; + while (!ready && --timeout > 0) { + Status = VL53L0X_GetMeasurementDataReady(&dev, &ready); + if (Status != VL53L0X_ERROR_NONE) + break; + if (!ready) + usleep(1000); + } + + if (!ready) { + fprintf(stderr, "[VL53L0X] measurement timeout\n"); + return false; + } + + Status = VL53L0X_GetRangingMeasurementData(&dev, &data); + if (Status != VL53L0X_ERROR_NONE) { + fprintf(stderr, "[VL53L0X] GetRangingMeasurementData failed: %d\n", Status); + return false; + } + + VL53L0X_ClearInterruptMask(&dev, 0); + return true; } bool VL53L0X::stop() { - if (fd <= 0) - return false; - - if (ioctl(fd, VL53L0X_IOCTL_STOP, nullptr) < 0) - { - fprintf(stderr, "[VL53L0X] stop failed: %s\n", strerror(errno)); - return false; - } - return true; + if (fd >= 0) { + close(fd); + fd = -1; + } + initialized = false; + return true; } diff --git a/src/vl53l0x_platform_user.cpp b/src/vl53l0x_platform_user.cpp new file mode 100644 index 0000000..5fa78c6 --- /dev/null +++ b/src/vl53l0x_platform_user.cpp @@ -0,0 +1,159 @@ +extern "C" { +#include "vl53l0x_platform.h" +} + +#include +#include + +VL53L0X_Error VL53L0X_LockSequenceAccess(VL53L0X_DEV Dev) +{ + (void)Dev; + return VL53L0X_ERROR_NONE; +} + +VL53L0X_Error VL53L0X_UnlockSequenceAccess(VL53L0X_DEV Dev) +{ + (void)Dev; + return VL53L0X_ERROR_NONE; +} + +VL53L0X_Error VL53L0X_WriteMulti(VL53L0X_DEV Dev, uint8_t index, + uint8_t *pdata, uint32_t count) +{ + if (count >= VL53L0X_MAX_I2C_XFER_SIZE) + return VL53L0X_ERROR_INVALID_PARAMS; + + uint8_t buf[VL53L0X_MAX_I2C_XFER_SIZE + 1]; + buf[0] = index; + memcpy(buf + 1, pdata, count); + + if (write(Dev->i2c_fd, buf, count + 1) != (ssize_t)(count + 1)) + return VL53L0X_ERROR_CONTROL_INTERFACE; + + return VL53L0X_ERROR_NONE; +} + +VL53L0X_Error VL53L0X_ReadMulti(VL53L0X_DEV Dev, uint8_t index, + uint8_t *pdata, uint32_t count) +{ + if (count >= VL53L0X_MAX_I2C_XFER_SIZE) + return VL53L0X_ERROR_INVALID_PARAMS; + + if (write(Dev->i2c_fd, &index, 1) != 1) + return VL53L0X_ERROR_CONTROL_INTERFACE; + + if (read(Dev->i2c_fd, pdata, count) != (ssize_t)count) + return VL53L0X_ERROR_CONTROL_INTERFACE; + + return VL53L0X_ERROR_NONE; +} + +VL53L0X_Error VL53L0X_WrByte(VL53L0X_DEV Dev, uint8_t index, uint8_t data) +{ + return VL53L0X_WriteMulti(Dev, index, &data, 1); +} + +VL53L0X_Error VL53L0X_WrWord(VL53L0X_DEV Dev, uint8_t index, uint16_t data) +{ + uint8_t buf[2]; + buf[0] = (data >> 8) & 0xFF; + buf[1] = data & 0xFF; + return VL53L0X_WriteMulti(Dev, index, buf, 2); +} + +VL53L0X_Error VL53L0X_WrDWord(VL53L0X_DEV Dev, uint8_t index, uint32_t data) +{ + uint8_t buf[4]; + buf[0] = (data >> 24) & 0xFF; + buf[1] = (data >> 16) & 0xFF; + buf[2] = (data >> 8) & 0xFF; + buf[3] = data & 0xFF; + return VL53L0X_WriteMulti(Dev, index, buf, 4); +} + +VL53L0X_Error VL53L0X_RdByte(VL53L0X_DEV Dev, uint8_t index, uint8_t *data) +{ + return VL53L0X_ReadMulti(Dev, index, data, 1); +} + +VL53L0X_Error VL53L0X_RdWord(VL53L0X_DEV Dev, uint8_t index, uint16_t *data) +{ + uint8_t buf[2]; + VL53L0X_Error Status = VL53L0X_ReadMulti(Dev, index, buf, 2); + *data = ((uint16_t)buf[0] << 8) | buf[1]; + return Status; +} + +VL53L0X_Error VL53L0X_RdDWord(VL53L0X_DEV Dev, uint8_t index, uint32_t *data) +{ + uint8_t buf[4]; + VL53L0X_Error Status = VL53L0X_ReadMulti(Dev, index, buf, 4); + *data = ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | + ((uint32_t)buf[2] << 8) | buf[3]; + return Status; +} + +VL53L0X_Error VL53L0X_UpdateByte(VL53L0X_DEV Dev, uint8_t index, + uint8_t AndData, uint8_t OrData) +{ + uint8_t data; + VL53L0X_Error Status = VL53L0X_RdByte(Dev, index, &data); + if (Status != VL53L0X_ERROR_NONE) + return Status; + data = (data & AndData) | OrData; + return VL53L0X_WrByte(Dev, index, data); +} + +VL53L0X_Error VL53L0X_PollingDelay(VL53L0X_DEV Dev) +{ + (void)Dev; + usleep(1000); + return VL53L0X_ERROR_NONE; +} + +/* string/internal stubs — never called but needed for linking */ +extern "C" { + +VL53L0X_Error VL53L0X_get_device_info(VL53L0X_DEV Dev, VL53L0X_DeviceInfo_t *pVL53L0X_DeviceInfo) +{ + (void)Dev; (void)pVL53L0X_DeviceInfo; + return VL53L0X_ERROR_NONE; +} + +VL53L0X_Error VL53L0X_get_device_error_string(VL53L0X_DeviceError ErrorCode, char *pDeviceErrorString) +{ + (void)ErrorCode; (void)pDeviceErrorString; + return VL53L0X_ERROR_NONE; +} + +VL53L0X_Error VL53L0X_get_range_status_string(uint8_t RangeStatus, char *pRangeStatusString) +{ + (void)RangeStatus; (void)pRangeStatusString; + return VL53L0X_ERROR_NONE; +} + +VL53L0X_Error VL53L0X_get_pal_error_string(VL53L0X_Error PalErrorCode, char *pPalErrorString) +{ + (void)PalErrorCode; (void)pPalErrorString; + return VL53L0X_ERROR_NONE; +} + +VL53L0X_Error VL53L0X_get_pal_state_string(VL53L0X_State PalStateCode, char *pPalStateString) +{ + (void)PalStateCode; (void)pPalStateString; + return VL53L0X_ERROR_NONE; +} + +VL53L0X_Error VL53L0X_get_sequence_steps_info(VL53L0X_SequenceStepId SequenceStepId, char *pSequenceStepsString) +{ + (void)SequenceStepId; (void)pSequenceStepsString; + return VL53L0X_ERROR_NONE; +} + +VL53L0X_Error VL53L0X_get_limit_check_info(VL53L0X_DEV Dev, uint16_t LimitCheckId, char *pLimitCheckString) +{ + (void)Dev; (void)LimitCheckId; (void)pLimitCheckString; + return VL53L0X_ERROR_NONE; +} + +}