替换 Mild v12 模型 + 同事编码器刹车红绿灯状态机合并

This commit is contained in:
spdis
2026-06-13 13:05:18 +08:00
parent 457d608621
commit b9e32c5a40
66 changed files with 1698 additions and 5625 deletions
-172
View File
@@ -1,172 +0,0 @@
# 2K0300智能车项目代码文档
## 项目概述
该项目是一个智能车控制系统,包含摄像头图像处理、电机控制、编码器反馈、PID 控制、帧缓冲区显示等功能。项目使用 C++ 编写,依赖 OpenCV 库进行图像处理,并通过 CMake 进行构建。
## 目录结构
- **CMakeLists.txt**: 项目的构建配置文件。
- **cross.cmake**: 交叉编译工具链配置文件。
- **src/**: 库文件目录,包含摄像头、电机控制、PID 控制等模块的实现。
- **lib/**: 库文件目录,包含摄像头、电机控制、PID 控制等模块的头文件。
- **main/**: 主程序目录。
- **xx_demo/**: 示例程序目录,包含多个演示程序。
---
## 主要模块介绍
### 1. 摄像头模块 (`camera.cpp`, `camera.h`)
摄像头模块负责初始化摄像头设备、捕获图像并进行处理。主要功能包括:
- **摄像头初始化**: 打开摄像头设备,设置分辨率、帧率等参数。
- **图像捕获**: 通过 OpenCV 捕获摄像头图像,并进行缩放、二值化等处理。
- **帧缓冲区显示**: 将处理后的图像显示到帧缓冲区设备(如 LCD 屏幕)。
- **图像保存**: 支持将捕获的图像保存为文件。
#### 关键函数:
- `CameraInit(uint8_t camera_id, double dest_fps, int width, int height)`: 初始化摄像头设备。
- `CameraHandler(void)`: 处理摄像头捕获的图像。
- `streamCapture(void)`: 持续捕获摄像头图像,存储到全局变量`cv::Mat pubframe`中,`std::mutex frameMutex`为互斥锁。
- `cameraDeInit(void)`: 释放摄像头资源。
---
### 2. 电机控制模块 (`MotorController.cpp`, `MotorController.h`)
电机控制模块负责控制智能车的电机,支持 PWM 调速和方向控制。主要功能包括:
- **电机初始化**: 初始化 PWM 控制器和 GPIO 引脚。
- **速度控制**: 通过 PID 控制器调节电机速度。
- **方向控制**: 控制电机的正反转。
#### 关键函数:
- `MotorController(int pwmchip, int pwmnum, int gpioNum, unsigned int period_ns, double kp, double ki, double kd, double targetSpeed, int encoder_pwmNum, int encoder_gpioNum, int encoder_dir_)`: 构造函数,初始化电机控制器,`encoder_dir_`取值为1/-1,在更新速度时自动乘算到编码器速度上。
- `updateSpeed(void)`: 从编码器更新当前速度。
- `updateTarget(int speed)`: 设置目标速度。
- `updateduty(double dutyCycle)`: 设置 PWM 占空比,为负值时反转。
---
### 3. PID 控制模块 (`PIDController.cpp`, `PIDController.h`)
PID 控制模块实现了位置式和增量式 PID 控制器,用于电机速度和舵机角度的精确控制。主要功能包括:
- **PID 参数设置**: 支持动态调整比例、积分、微分参数。
- **控制模式切换**: 支持位置式和增量式 PID 控制。
- **输出限幅**: 防止输出值超出设定范围。
- **积分限幅**:放置积分项超出设定范围。
#### 关键函数:
- `PIDController(double kp, double ki, double kd, double target, PIDMode mode, double output_limit, double integral_limit)`: 构造函数,初始化 PID 参数。
- `update(double measured_value)`: 输入测量值,更新 PID 控制器输出。
- `setTarget(double target)`:设置PID控制器目标值。
- `setPID(double kp, double ki, double kd)`: 设置 PID 参数。
- `setMode(PIDMode mode)`: 设置控制模式(位置式或增量式)。
---
### 4. 编码器模块 (`encoder.cpp`, `encoder.h`)
编码器模块用于读取电机编码器的脉冲信号,计算电机转速。主要功能包括:
- **编码器初始化**: 映射寄存器,初始化编码器设备。
- **脉冲计数**: 读取编码器脉冲信号,计算转速。
- **方向检测**: 通过 GPIO 读取电机方向。
#### 关键函数:
- `ENCODER(int pwmNum, int gpioNum)`: 构造函数,初始化编码器。
- `pulse_counter_update(void)`: 更新脉冲计数并计算转速(单位:rps)。
---
### 5. 帧缓冲区模块 (`frame_buffer.cpp`, `frame_buffer.h`)
帧缓冲区模块负责将 OpenCV 图像转换为 RGB565 格式并显示到帧缓冲区设备(如 LCD 屏幕)。主要功能包括:
- **图像格式转换**: 将 OpenCV 的 RGB888 图像转换为 RGB565 格式。
- **帧缓冲区写入**: 将转换后的图像写入帧缓冲区设备。
#### 关键函数:
- `convertMatToRGB565(const cv::Mat &frame, uint16_t *buffer, int width, int height)`: 将 OpenCV 图像转换为 RGB565 格式,写入到buffer中。
- `convertRGBToRGB565(uint8_t r, uint8_t g, uint8_t b)`: 将 RGB888 颜色值转换为 RGB565 格式。
---
### 6. GPIO 模块 (`GPIO.cpp`, `GPIO.h`)
GPIO 模块用于控制 GPIO 引脚,支持方向设置、值读取和写入。主要功能包括:
- **GPIO 初始化**: 导出 GPIO 引脚并设置方向。
- **值读写**: 读取或写入 GPIO 引脚的值。
#### 关键函数:
- `GPIO(int gpioNum)`: 构造函数,初始化 GPIO 引脚。
- `setDirection(const std::string &direction)`: 设置 GPIO 引脚方向。
- `setValue(bool value)`: 设置 GPIO 引脚值。
- `readValue(void)`: 读取 GPIO 引脚值。
---
### 7. PWM 控制模块 (`PwmController.cpp`, `PwmController.h`)
PWM 控制模块用于生成 PWM 信号,控制电机速度和舵机角度。主要功能包括:
- **PWM 初始化**: 初始化 PWM 设备。
- **周期和占空比设置**: 设置 PWM 信号的周期和占空比。
- **PWM 启用/禁用**: 控制 PWM 信号的输出。
#### 关键函数:
- `PwmController(int pwmchip, int pwmnum)`: 构造函数,初始化 PWM 设备。
- `setPeriod(unsigned int period_ns)`: 设置 PWM 周期。
- `setDutyCycle(unsigned int duty_cycle_ns)`: 设置 PWM 占空比。
- `enable(void)`: 启用 PWM 输出。
- `disable(void)`: 禁用 PWM 输出。
---
### 8. 图像处理模块 (`image_cv.cpp`, `image_cv.h`)
图像处理模块负责对摄像头捕获的图像进行处理,提取赛道边缘和中线。主要功能包括:
- **图像缩放**: 将图像缩放到指定大小。
- **二值化处理**: 将图像转换为二值图像。
- **边缘提取**: 提取赛道的左右边缘和中线。
#### 关键函数:
- `image_main(void)`: 主图像处理函数,执行缩放、二值化和边缘提取。
---
### 9. 全局变量模块 (`global.cpp`, `global.h`)
全局变量模块定义了项目中使用的全局变量和工具函数。主要功能包括:
- **文件读取**: 从文件中读取双精度值和标志。
- **全局变量**: 定义 PID 参数、目标速度等全局变量。
#### 关键函数:
- `readDoubleFromFile(const std::string &filename)`: 从文件中读取双精度值。
- `readFlag(const std::string &filename)`: 从文件中读取标志。
---
### 10. 定时器模块 (`Timer.cpp`, `Timer.h`)
定时器模块用于创建定时任务,定期执行指定函数。主要功能包括:
- **定时任务创建**: 创建定时任务并启动。
- **定时任务停止**: 停止定时任务。
#### 关键函数:
- `Timer(int interval_ms, std::function<void()> task)`: 构造函数,初始化定时器。
- `start(void)`: 启动定时任务。
- `stop(void)`: 停止定时任务。
---
## 示例程序
### 1. 编码器测试程序 (`encoder_demo.cpp`)
该程序用于测试编码器模块,读取编码器脉冲信号并输出转速。
### 2. 帧缓冲区测试程序 (`framebuffer_demo.cpp`)
该程序用于测试帧缓冲区模块,绘制图形并显示到帧缓冲区设备。
### 3. OpenCV 测试程序 (`opencv_demo1.cpp`, `opencv_demo2.cpp`, `opencv_demo3.cpp`)
这些程序用于测试 OpenCV 图像处理功能,分别为显示摄像头图像、显示旋转立方体、目标匹配。
- `opencv_demo3.cpp`在运行时依赖`opencv/share/opencv4/haarcascades/haarcascade_frontalface_default.xml`,需要在`opencv`文件夹同一目录下运行。
### 4. 遥控车演示程序 (`demo1.cpp`)
该程序通过键盘控制智能车的运动,支持前进、后退、左转、右转。
---
## 构建项目
使用 CMake 构建项目:
```bash
mkdir build
cd build
cmake ..
make
```
+94
View File
@@ -0,0 +1,94 @@
# AGENTS.md — SmartCar Demo
## Project overview
LoongArch64 embedded autonomous smart car using OpenCV + NanoDet V10 model.
Runs on-device (Loongson board) with cross-compilation from x86 Linux.
## Build
```sh
# On the build host (x86 Linux):
mkdir build && cd build
cmake ..
make
```
- **Cross-compilation target**: LoongArch64 (`-march=loongarch64`)
- **Toolchain**: `/opt/loongson-gnu-toolchain-8.3-x86_64-loongarch64-linux-gnu-rc1.6`
- **C++ standard**: 17
- **OpenCV path** (device-side): `/mnt/d/PPPProgram/smartcar/opencv_device/`
- `cross.cmake` is included **before** `cmake_minimum_required` — this is intentional.
## Project structure
| Directory | Purpose |
|-----------|---------|
| `src/` | Source → compiled into `common_lib` (static lib) |
| `lib/` | Public headers only (no .cpp) |
| `main/` | Entry point → executable `smartcar_demo1` |
| `docs/` | Architecture docs (`ARCHITECTURE.md` is the design reference) |
| `build/` | CMake build output (gitignored) |
**Executable name**: `smartcar_demo1` (the CMake project name `smartcar_demo2` is different — don't assume they match).
## Excluded modules
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 NanoDet model
- `encoder.cpp` — referenced in exclusion list but file does **not** exist in repo
## Device-side operation
The onboard binary is controlled via text files in the working directory — no CLI args.
```sh
# On device:
sh ctl.sh init # initialize GPIO/PWM pins
sh ctl.sh start # start the demo
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. `start.sh` uses `LD_PRELOAD=./gpio_fix_final.so` (GPIO workaround).
## Configuration system
All config is via single-value text files in the working directory:
- `./speed` (double) — target speed
- `./start` (0/1) — motor enable switch
- `./debug` (0/1) — when 1, reloads all config files every 7 frames
- `./showImg` (0/1) — LCD display toggle
- `./deadband`, `./steer_gain`, `./center_bias` — steering tuning
- `./foresee` — look-ahead row for steering
- `./zebrasee` — zebra crossing near-threshold
- `./destfps` — target framerate
- `./kp`, `./ki`, `./kd` — PID gains (currently **not used**; motor is open-loop)
- `./mortor_kp`, `./mortor_ki`, `./mortor_kd` — motor encoder PID (spelling `mortor` is intentional)
## Architecture
9-step per-frame pipeline in `CameraHandler()` (`src/camera.cpp`):
1. Capture frame (640×480 MJPEG → BGR)
2. Optionally save image
3. Vision line tracking (80×60 downscale → HSV+Otsu → floodFill → mid_line[60])
4. Model inference every 2nd frame (NanoDet V10, 4 classes: cone/red/green/zebra)
5. Zebra crossing state machine (NORMAL→STOP(4s)→COOLDOWN(5s))
6. Steering servo control (PWM, deadband-filtered)
7. Motor control (open-loop duty cycle, curve-based slowdown in turns)
8. LCD rendering (`/dev/fb0` mmap)
9. FPS logging
**Key detail**: Motor control is **open-loop**`MotorController::updateSpeed()` and encoder PID exist in code but are never called from the main loop. Only `updateduty()` (direct PWM) is used.
Currently **cones (cls=0), red lights (cls=1), and green lights (cls=2)** are detected by the model but ignored in decision logic.
## Style and conventions
- C++ files have **no copyright headers** and **minimal comments** — comments are ascii-box-style block comments when present
- Header guards use the form `#ifndef FILENAME_H_` / `#define FILENAME_H_`
- Global state uses `extern` globals (e.g., `g_cfg`, `g_steer_deviation`, `g_boxes`)
- `lib/` contains .h files only; `src/` contains .cpp files only
- No unit tests, no CI, no linting — this is embedded code tested on-device
-256
View File
@@ -1,256 +0,0 @@
# SmartCar Demo — 使用手册 & 踩坑记录
## 项目概述
龙芯 2K0300 智能车卖家 Demo,简易架构:`sysfs` 直接控制 GPIO/PWM + mmap 读编码器 + OpenCV 巡线。
**赛道**:蓝底黄线边界,灰度路面
**驱动**:前轮舵机转向 + 后轮双电机差速辅助
---
## 硬件信息
| 部件 | 型号/规格 | 说明 |
|------|----------|------|
| 主控 | 龙芯 2K0300 (LA264, 1.2GHz) | LoongArch64 |
| 摄像头 | USB UVC | 320×240 MJPG, `/dev/video0` |
| 显示器 | SPI LCD | `/dev/fb0`, RGB565, 160×128 (实际) |
| 舵机 | 标准 PWM 舵机 | pwmchip1/pwm0, 3ms 周期, 1.52ms 中位 |
| 电机驱动 | DRV8701E (双路) | pwmchip8/pwm1→右, pwmchip8/pwm2→左 |
| 编码器 | 硬件脉冲计数 | `/dev/mem` mmap 基址 `0x1611B000` |
| IMU | JY62 六轴陀螺仪 | `/dev/ttyS1`, 115200bps |
| ToF 测距 | VL53L0X | `/dev/stmvl53l0x_ranging` |
| 音箱 | wonderEcho | I2C-2, 地址 0x6E (有应答, 无驱动) |
### 关键引脚映射
| 功能 | GPIO / PWM | 备注 |
|------|-----------|------|
| 电机使能 | GPIO73 | 高有效, 1=启动, 0=紧急制动 |
| 左电机 IN1 (正转) | GPIO12 + pwmchip8/pwm2 | DRV8701 IN/IN 模式 |
| 左电机 IN2 (反转) | GPIO13 | **必须锁 0** |
| 右电机 PWM | pwmchip8/pwm1 | 方向脚 GPIO67 是哑脚 |
| 左编码器 | 通道0 + GPIO75 | 方向检测 |
| 右编码器 | 通道3 + GPIO72 | 方向检测, 极性 -1 |
| 舵机 | pwmchip1/pwm0 | 3ms, 1.31~1.73ms (±7 刻度) |
---
## 构建环境
### 交叉编译工具链
```
路径: /opt/loongson-gnu-toolchain-8.3-x86_64-loongarch64-linux-gnu-rc1.6
编译器: loongarch64-linux-gnu-g++ (GCC 8.3)
架构: -march=loongarch64
```
### OpenCV
```
路径: /home/ilikara/loongson/opencv-4.11.0/loongson
版本: 4.11.0 (必须匹配设备上的 libopencv)
```
### 编译
```bash
cd smartcar1
mkdir -p build && cd build
cmake .. # CROSS_COMPILE=1 在 cross.cmake 中设置
make -j$(nproc)
```
产物: `build/main/smartcar_demo` (约 90KB)
**Windows 上不可编译!** 必须在 Linux 下用交叉工具链。可在 WSL Ubuntu 中用 `g++ -std=c++17 -fsyntax-only` 做部分语法检查。
---
## 部署与启动
### 板端目录结构
```
/home/root/smartcar/
├── smartcar_demo # 主程序
├── ctl.sh # 启停脚本
├── gpio_fix_final.so # GPIO74→73 重定向 (LD_PRELOAD)
├── start.sh # 推荐启动脚本 (已内置 LD_PRELOAD)
├── showImg # LCD 预览开关
├── start # 车控启停
├── speed # 目标速度 (占空比)
├── deadband # 舵机死区
├── steer_gain # 转向增益
├── kp / ki / kd # 舵机 PID
├── mortor_kp / mortor_ki / mortor_kd # 电机 PID
└── foresee # 前瞻行号
```
### 推荐启动
```bash
# 方式 1: 使用包装脚本 (已固化 LD_PRELOAD)
sh /home/root/smartcar/start.sh
# 方式 2: 手动
LD_PRELOAD=/home/root/smartcar/gpio_fix_final.so /home/root/smartcar/smartcar_demo
```
---
## 运行时参数热调
**无需重新编译**,直接 `echo` 写文件即可实时生效:
```bash
echo 11 > /home/root/smartcar/speed # 目标占空比 (0~100)
echo 8 > /home/root/smartcar/deadband # 舵机死区 (像素)
echo 1.5 > /home/root/smartcar/steer_gain # 转向增益
echo 3.5 > /home/root/smartcar/kp # 舵机 P
echo 0.3 > /home/root/smartcar/ki # 舵机 I
echo 2.0 > /home/root/smartcar/kd # 舵机 D
echo 40 > /home/root/smartcar/foresee # 前瞻行 (0~120, 越大看越远)
echo 1 > /home/root/smartcar/showImg # 1=LCD 预览, 0=关闭
echo 1 > /home/root/smartcar/start # 1=启动, 0=停止
echo 1 > /home/root/smartcar/saveImg # 保存当前帧到 ./image/
```
---
## 踩坑记录 (关键!)
### 1. GPIO74 与 LCD 驱动冲突 — 程序无法启动
**现象**: `std::runtime_error: Failed to open GPIO value file`
**原因**: Demo 二进制硬编码 GPIO_EN=74, 但 GPIO74 被 LCD 驱动 `fb_st7735r` 占用 (内核级, 无法释放)。
**解决**: 编译 `gpio_fix_final.so`, 用 LD_PRELOAD 劫持 `open()` 系统调用, 将对 gpio74 的访问重定向到 gpio73。启动时必须带 `LD_PRELOAD`
**源代码修复**: `control.cpp``GPIO mortorEN(73)` — 如果从源码编译, 直接改成 GPIO73, 无需 LD_PRELOAD。
### 2. 右电机方向硬件锁死 — 差速失效
**现象**: 右轮只能单向旋转, 代码写 GPIO67 无效。
**原因**: DRV8701 的右路方向脚 (原本应对应某个 GPIO) 在 PCB 上直接拉高/拉低了。代码中的 GPIO67 是哑脚, 实际不控制任何硬件。
**解决**: 物理剪线交换右电机电源线 (M+/M- 对调), 代码中 GPIO67 作为占位符不动。这样一来, 代码输出正占空比时电机实际反向。
### 3. GPIO13 实测映射 — 文档与实物不符
**文档称**: GPIO12=左IN1, GPIO13=右IN1。
**实测**: GPIO13 控制的是**左电机 IN2 (反转)**, 不是右电机方向!
**影响**: 若 GPIO13=1, 左电机 IN1=1 IN2=1 → 制动状态, 左轮抱死。
**解决**: 代码中显式 `leftIn2.setValue(0)` 把 GPIO13 锁 0, 确保左电机只有正转 (IN1=1, IN2=0)。
### 4. 摄像头分辨率不稳定
**现象**: 摄像头偶尔返回 640×480 而非 320×240。
**原因**: 某些 UVC 摄像头不响应 `CAP_PROP_FRAME_WIDTH/HEIGHT` 设置, 按默认格式输出。
**解决**:
```cpp
cap.open(0, cv::CAP_V4L2); // 强制 V4L2 后端
cap.set(cv::CAP_PROP_FRAME_WIDTH, 320); // 设宽
cap.set(cv::CAP_PROP_FRAME_HEIGHT, 240); // 设高
cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M','J','P','G')); // MJPG
```
即使摄像头不遵从, `resize()` 也能在软件层统一到 80×60。
### 5. 编码器必须 root 权限
**现象**: `ENCODER` 构造失败, mmap 报错。
**原因**: 编码器通过 `/dev/mem` 直接映射 PWM 硬件寄存器 (物理地址 `0x1611B000`), 需要 root。
**解决**: 必须以 root 运行程序。`sudo` 或直接以 root 登录。
### 6. IMU 端口纠正
**文档称**: IMU 在 `/dev/ttyS2`
**实测**: IMU 在 `/dev/ttyS1` (115200bps)。
### 7. 网络热插拔导致断连
**现象**: 拔掉网线再插上, SSH 连不上。
**原因**: `/etc/network/interfaces` 中静态 eth0 配置与 connman 冲突。
**解决**: 删除 `/etc/network/interfaces` 中的 eth0 段, 让 connman 全权管理有线网络。修改后即使先开机后插网线也能自动获取 IP。
### 8. GCC 14.2.0 工具链 ABI 不兼容
**现象**: 用新版 GCC 14.2.0 (当前 WSL 环境) 编译的程序在设备上运行报 `GLIBCXX_3.4.30 not found`
**原因**: GCC 14.2.0 的 libstdc++ ABI 与设备上的 glibc 2.28 / libstdc++ 6.0.25 不兼容。
**解决**:
- **必须使用原始 GCC 8.3 rc1.6 工具链**。路径: `/opt/loongson-gnu-toolchain-8.3-x86_64-loongarch64-linux-gnu-rc1.6`
- 可用 C 编译器 (`gcc` 而非 `g++`) + 显式 `-march=la64v1.0` + 指定 `-Wl,-dynamic-linker=...` 编译纯 C 程序
### 9. 视觉管线分辨率选择
**Demo 内部处理**: 摄像头 320×240 → 缩放至显示分辨率 (160×120) → 再除以 `calc_scale=2` → 最终 **80×60** 做 HSV 二值化 + 洪泛填充 + 搜线。
**关键**: 80×60 是最优平衡点——再大 Otsu 不稳, 再小细节丢失。
### 10. 启动顺序 & 竞态
- GPIO 必须先 export + 设方向, 再启动程序
- 舵机 PWM 必须先设 period + enable, 才能写 duty_cycle
- 程序需要几秒初始化 IMU (校准 200 样本 × 5ms = 1 秒)
### 11. PWM 频率
| PWM | 频率 | 周期 | 用途 |
|-----|------|------|------|
| pwmchip8/pwm1,2 | 20kHz | 50000ns | 电机驱动 (DRV8701 需 ≥ 20kHz) |
| pwmchip1/pwm0 | 333Hz | 3000000ns | 舵机 (标准 3ms 周期) |
### 12. 算力实测 (2K0300 CPU)
| 精度 | 算力 | 备注 |
|------|------|------|
| FP32 | 0.43 GFLOPS | 无 SIMD, 纯标量 |
| INT8 | 0.44 GOPS | 几乎无加速, 与 FP32 持平 |
| INT4 | 0.24 GOPS | 比 INT8 更慢 (位操作开销) |
**结论**: 2K0300 没有 SIMD/NPU, 纯 CPU 推理极弱。模型需 ≤15M MACs 才能跑 30+ FPS。只适合微型 CNN (2-3 层, 32×32 输入, 8-16 通道) 做简单分类。
---
## Demo 程序列表
| Demo | 功能 |
|------|------|
| `main/` | **完整自动驾驶巡线** — CameraTimer + MortorTimer + 文件热调 |
| `demo1/` | 键盘 WASD 遥控 — 非阻塞终端 |
| `image_test/` | 离线图像处理调试器 — 方向键翻页, 7 种显示模式 |
| `udp_receive/` | UDP 遥控 (端口 8888) + Python 滑块 GUI |
| `encoder_demo/` | 编码器原始读数测试 |
| `jy62_demo/` | JY62 IMU 六轴数据读取 |
| `key_demo/` | 9 键 GPIO 状态面板 |
| `wonderEcho_demo/` | I2C smbus 读写实验 |
| `opencv_demo1/` | USB 摄像头 → framebuffer 直显 + FPS |
| `opencv_demo2/` | 3D 旋转立方体软件渲染 |
| `opencv_demo3/` | Haar 级联人脸检测 |
| `framebuffer_demo/` | 纯软件 8×8 字体 + 几何绘图 |
| `vl53l0x_test/` | VL53L0X 测距传感器 (ST 官方) |
---
## 文件说明
| 文件 | 用途 |
|------|------|
| `CMakeLists.txt` | 构建配置: `src/` → 静态库, 各 demo 独立链接 |
| `cross.cmake` | 交叉工具链切换 (`CROSS_COMPILE=1`) |
| `lib/` | 头文件: GPIO, PWM, PID, Motor, Encoder, Camera, Timer, serial 等 |
| `src/` | 实现文件: 每个 `.cpp` → 编译进 `libcommon_lib.a` |
-18
View File
@@ -1,18 +0,0 @@
#!/bin/bash
export PATH=/opt/loongson-gnu-toolchain-8.3-x86_64-loongarch64-linux-gnu-rc1.6/bin:/usr/bin:/bin
CC=loongarch64-linux-gnu-g++
SRC_FW=/mnt/d/PPPProgram/smartcar/smartcar_framework
SRC_CAR=/mnt/d/PPPProgram/smartcar/smartcar2
INC="-I$SRC_FW/model/v10/release -I$SRC_CAR/src -I/mnt/d/PPPProgram/smartcar/opencv_device/include/opencv4"
LIB="-L/mnt/d/PPPProgram/smartcar/opencv_device/lib -lopencv_core -lopencv_imgproc -lopencv_imgcodecs -lpthread -lrt"
FLAGS="-O3 -std=c++17 -march=loongarch64 -mtune=loongarch64 -ffast-math -funroll-loops -fomit-frame-pointer"
echo "=== NOSIMD ==="
$CC $FLAGS $INC $SRC_FW/tools/v10_demo.cpp $SRC_FW/model/v10/release/model_v10.cpp -o /tmp/vd_nosimd $LIB
echo "rc=$?"
echo "=== LSX ==="
$CC $FLAGS -mlsx $INC $SRC_FW/tools/v10_demo.cpp $SRC_FW/model/v10/release/model_v10.cpp -o /tmp/vd_simd $LIB
echo "rc=$?"
ls -la /tmp/vd_nosimd /tmp/vd_simd 2>/dev/null || echo "some binaries missing"
+13 -2
View File
@@ -15,14 +15,13 @@ init_pins() {
echo out > /sys/class/gpio/gpio12/direction 2>/dev/null
echo out > /sys/class/gpio/gpio13/direction 2>/dev/null
echo out > /sys/class/gpio/gpio66/direction 2>/dev/null
echo out > /sys/class/gpio/gpio67/direction 2>/dev/null
echo in > /sys/class/gpio/gpio67/direction 2>/dev/null
echo out > /sys/class/gpio/gpio75/direction 2>/dev/null
echo in > /sys/class/gpio/gpio72/direction 2>/dev/null
echo 1 > /sys/class/gpio/gpio73/value 2>/dev/null
echo 0 > /sys/class/gpio/gpio12/value 2>/dev/null
echo 0 > /sys/class/gpio/gpio13/value 2>/dev/null
echo 0 > /sys/class/gpio/gpio66/value 2>/dev/null
echo 0 > /sys/class/gpio/gpio67/value 2>/dev/null
for ch in 1 2; do
echo $ch > /sys/class/pwm/pwmchip${PWMCHIP}/export 2>/dev/null
@@ -47,6 +46,18 @@ init_pins() {
echo 0 > "$DIR/mortor_kd" 2>/dev/null
echo 40 > "$DIR/foresee" 2>/dev/null
echo 0 > "$DIR/start" 2>/dev/null
echo 0.3 > "$DIR/cone_avoid_gain" 2>/dev/null
echo 30 > "$DIR/cone_avoid_range" 2>/dev/null
echo 0.5 > "$DIR/cone_speed" 2>/dev/null
echo 3 > "$DIR/cone_min_frames" 2>/dev/null
echo 0 > "$DIR/cone_margin" 2>/dev/null
echo 0.80 > "$DIR/cone_thresh" 2>/dev/null
echo 30 > "$DIR/cone_hold_frames" 2>/dev/null
echo 10 > "$DIR/brake_scale" 2>/dev/null
echo 10000 > "$DIR/brake_max" 2>/dev/null
echo "[demo] 引脚初始化完成"
}
-5
View File
@@ -1,5 +0,0 @@
# demo1
add_executable(demo1 demo1.cpp)
# 链接 OpenCV 库
target_link_libraries(demo1 common_lib ${OpenCV_LIBS})
-125
View File
@@ -1,125 +0,0 @@
/*
* @Author: ilikara 3435193369@qq.com
* @Date: 2025-01-07 02:21:05
* @LastEditors: ilikara 3435193369@qq.com
* @LastEditTime: 2025-03-29 01:36:06
* @FilePath: /smartcar/demo1/demo1.cpp
* @Description: 遥控车Demo,通过键盘控制小车的运动,WASD分别控制前后左右,按下Q退出。由于SSH可能会只捕获到一个按键,所以设定按下WS时清除转向,不按WS时不清除前进后退。
*/
#include <iostream>
#include <unordered_set>
#include <termios.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "MotorController.h"
#include "PwmController.h"
#include "GPIO.h"
GPIO mortorEN(73);
MotorController *motorController[2] = {nullptr, nullptr};
PwmController servo(1, 0);
void Init()
{
mortorEN.setDirection("out");
mortorEN.setValue(1);
const int pwmchip[2] = {8, 8};
const int pwmnum[2] = {2, 1};
const int gpioNum[2] = {12, 13};
const int encoder_pwmchip[2] = {0, 3};
const int encoder_gpioNum[2] = {75, 72};
const int encoder_dir[2] = {1, -1};
const unsigned int period_ns = 50000; // 20 kHz
for (int i = 0; i < 2; ++i)
{
motorController[i] = new MotorController(pwmchip[i], pwmnum[i], gpioNum[i], period_ns,
0, 0, 0, 0,
encoder_pwmchip[i], encoder_gpioNum[i], encoder_dir[i]);
}
servo.setPeriod(3000000);
servo.setDutyCycle(1500000);
servo.enable();
}
// 设置非阻塞输入
void setNonBlockingInput(bool enable)
{
termios tty;
tcgetattr(STDIN_FILENO, &tty);
if (enable)
{
tty.c_lflag &= ~ICANON; // 禁用规范模式
tty.c_lflag &= ~ECHO; // 禁用回显
}
else
{
tty.c_lflag |= ICANON;
tty.c_lflag |= ECHO;
}
tcsetattr(STDIN_FILENO, TCSANOW, &tty);
}
int main()
{
std::unordered_set<char> pressedKeys;
setNonBlockingInput(true);
std::cout << "Press keys (WASD). Press 'q' to quit." << std::endl;
Init();
while (true)
{
char ch = getchar();
if (ch == 'q')
{ // 按 'q' 退出
break;
}
// 添加按下的键到集合中,并响应按下的键
if (ch == 'w' || ch == 'W')
{
pressedKeys.insert('W');
motorController[0]->updateduty(17);
motorController[1]->updateduty(17);
servo.setDutyCycle(1520000);
}
else if (ch == 'a' || ch == 'A')
{
pressedKeys.insert('A');
servo.setDutyCycle(1360000);
}
else if (ch == 's' || ch == 'S')
{
pressedKeys.insert('S');
motorController[0]->updateduty(-17);
motorController[1]->updateduty(-17);
servo.setDutyCycle(1520000);
}
else if (ch == 'd' || ch == 'D')
{
pressedKeys.insert('D');
servo.setDutyCycle(1680000);
}
// 显示当前按下的键
std::cout << "Pressed Keys: ";
for (char key : pressedKeys)
{
std::cout << key << " ";
}
std::cout << std::endl;
// 清空按下的键集合
pressedKeys.clear();
usleep(10000); // 10ms
}
setNonBlockingInput(false);
return 0;
}
+302
View File
@@ -0,0 +1,302 @@
# SmartCar 决策总架构:从视频帧到执行器
## 坐标系统一览
```
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
**⚠ 回退模式:** 当 CONVERT_RGB=0 未生效时,raw_frame = 640×480,模型框坐标也在 640×480 空间。
此时 LCD 渲染仍硬编码除以 160/120(会出错),但正常运行时不会进入此模式。
---
## 主循环 (main.cpp → CameraHandler)
```
main()
├── cfg_load_all() # 从工作目录文件读取全部配置
├── CameraInit(0, dest_fps, 320, 240) # 打开摄像头, LCD, 模型, I2C
├── ControlInit() # 初始化双电机 GPIO + PWM
└── while(running):
└── CameraHandler() # ★ 以下逐帧执行
```
---
## 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)
```
raw_frame (160×120 或 640×480 BGR)
├── cv::resize → (line_tracking_w × line_tracking_h) 典型 80×60
├── image_binerize():
│ BGR→HSV → H通道Otsu(THRESH_BINARY_INV) → S通道Otsu → bitwise_or
│ 赛道=255(白), 背景=0(黑)
│ 原理:蓝底赛道H偏聚集(100~130)、S高;灰路面S低
│ bitwise_or 取并集,H或S任一判为赛道即保留
├── find_road():
│ MORPH_OPEN(2×2 CROSS) → 种子点(lt_w/2, lt_h-10)
│ → 种子点处画实心圆(半径10, 255) 防种子落在黑色区域
│ → floodFill(loDiff=20, upDiff=20, 8邻域, newVal=128)
│ → mask 提取 ROI → track (赛道内部=128, 外部=0)
├── 逐行最长连续段搜索:
│ uchar(*IMG)[lt_w] = track.data
│ 每行扫描,找最长连续非零段
│ 有多个白段时取最后一个(≥取等,偏右)
│ → left_line[row], right_line[row]
│ → 全零行: left=right=-1
└── 中线 + 丢线补全 (row = lt_h-1 → 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 (偏左)
```
**边界线区间有效性:** row 10~59 有可靠数据;row 0~9 的 mid_line 保持初始值 -1(不可靠,不要写入 box 过滤逻辑)。
---
## 舵机控制数学
```
foresee = g_cfg.foresee ← 前瞻行索引(像素,显示空间),默认 40
check_row = foresee / calc_scale ← 转为巡线空间行号(calc_scale=2, int 整除)
mid_val = mid_line[check_row] ← 该行中线列坐标 (0~79)
如果 mid_val == 255 → 跳过(丢线补全也写不到 255,仅作防御)
否则:
deviation = mid_val × 2 - newWidth / 2 ← 转为显示空间偏差(px)
deviation -= g_cfg.center_bias ← 中心偏置修正
g_steer_deviation = deviation / (newWidth/2) ← 归一化到 [-1, 1]
if |deviation| < deadband:
servo = 1500000 ns (直行)
else:
norm = deviation / (newWidth/2)
offset = norm × steer_gain × 300000
duty = clamp(1500000 + offset, 1200000, 1800000)
servo.setDutyCycle(duty)
```
**舵机量程:** 1,200,000 ~ 1,800,000 ns,中位 1,500,000 ns,±300,000 ns 对应满偏。
**deadband 单位:** 显示空间像素(deviation 的单位)。
---
## 电机控制数学
```
ControlUpdate(speed, zebra_block):
if zebra_block || !g_cfg.start:
motor[0].updateduty(0) → 两电机停转
motor[1].updateduty(0)
if !g_cfg.start: mortorEN.setValue(0) → GPIO73 拉低
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 方向)
motor[1].updateduty(spd) → 右电机 (pwmchip8/pwm1, gpio13 方向)
mortorEN.setValue(1) → GPIO73 拉高使能
```
**motor[0] vs motor[1]** 左/右区别仅在于 gpioNum12 vs 13)和 pwm通道(2 vs 1),函数调用完全相同。
**updateduty(duty) 内部:**
- `pwm_period * |duty| / 100` → 转占空比 ns 写入 sysfs
- duty > 0 → GPIO 方向 = 1(正转),duty ≤ 0 → GPIO 方向 = 0(反转)
**速度控制是开环的:** `MotorController` 仅包含 `updateduty()` 方法,无编码器反馈。
`PIDController` 类存在但从未被实例化或调用。
MotorController.h 中也无 `updateSpeed()` 方法(之前的文档中误记了此函数)。
---
## 斑马线状态机
```
检测到 cls=3 → 取第一个斑马线框 → 去抖计数+追踪最远cy
ZNORMAL 期间:
zebra_seen: g_zc_frames++, g_zc_min_cy = min(g_zc_min_cy, zebra_cy)
未 seen: g_zc_frames = max(0, g_zc_frames - 2)
g_zc_frames==0 → g_zc_min_cy=120 (重置)
触发条件 (仅 ZNORMAL):
zebra_near (cy > g_cfg.zebrasee)
&& g_zc_frames >= ZEBRA_MIN_FRAMES (5)
&& g_zc_min_cy <= ZEBRA_FAR_CY (50)
→ play_zebra_audio() → ZSTOP
┌──────┐ 条件: 连续5帧 + 起源于远(cy_min≤50) + 当前近(cy>zebrasee)
│NORMAL│ ─────────────────────────────────────► ┌──────┐
│ │ ◄───────────────────────────────────── │ STOP │
└──────┘ 冷却5秒结束 │ │
▲ └──┬───┘
│ 4秒后 │
│ ┌──────────┐ ◄─────────────────────┘
└───────────│ COOLDOWN │
└──────────┘
NORMAL: 允许通行,检测斑马线
STOP: 刹停 4 秒,g_zstate==Z_STOP 传给 ControlUpdate 第二个参数
COOLDOWN: 恢复行驶但斑马线状态机停止检测 5 秒(防止重复触发)
仅跳过检测,不影响其他功能(舵机/巡线正常)
```
**关键变量:**
- `g_zc_min_cy`:NORMAL 期间追踪斑马线出现时的最小 cy(越远值越小)。未检测到斑马线时衰减减2/帧,归零后重置为 120。
- `ZEBRA_FAR_CY = 50`:斑马线的 cy 必须曾在 ≤50 处出现过(即"从远处来")
- `zebrasee`(默认 60):当前斑马线 cy > 此值视为"足够近",触发停车
- 去抖衰减速度:未检测到时每次 `-2`(比锥桶设计中的 `-1` 更快下降)
- Box 遍历在找到第一个 cls=3 后 `break`,忽略同帧其他斑马线框
---
## 配置系统
所有配置通过工作目录下的纯文本文件读写。文件不存在时 readDoubleFromFile 返回 0
| 文件 | 类型 | 代码默认 | ctl.sh 写入 | 含义 |
|---|---|---|---|---|
| `./speed` | double | 60 | 11 | 目标速度 (% 占空比) |
| `./start` | int(0/1) | 0 | 0 | 使能开关,1=电机运行 |
| `./debug` | int(0/1) | 0 | (不写入) | 每7帧重载配置+允许存图 |
| `./showImg` | int(0/1) | 0 | (不写入) | LCD 显示(每10帧轮询→g_lcd_on |
| `./foresee` | double | 80 | 40 | 舵机前瞻行 (显示空间像素) |
| `./deadband` | double | 5 | 8 | 舵机死区 (显示空间像素) |
| `./steer_gain` | double | 1.0 | 1.5 | 舵机增益 |
| `./center_bias` | double | 0 | (不写入) | 中线偏置修正 |
| `./kp`,`./ki`,`./kd` | double | - | 3.5/0.3/2.0 | PID 参数 (当前未使用) |
| `./mortor_kp/ki/kd` | double | - | 0.6/0.2/0 | 电机编码器 PID (当前未使用) |
| `./zebrasee` | double | 60 | (不写入) | 斑马线近界阈值 (模型空间cy) |
| `./destfps` | double | 30* | (不写入) | 目标帧率 (*代码兜底值) |
| `./saveImg` | int(0/1) | - | (不写入) | 保存帧图像 (需 debug=1) |
**debug 模式:** `./debug` = 1 时,主循环每7帧调用一次 `cfg_load_all()` 重读全部配置,支持热更新调参。同时允许 `saveImg` 保存图像。
**注意**`global.h` 中的 `CfgCache` 默认值和 `ctl.sh` 写入的值不一致(如 speed: 60 vs 11)。`ctl.sh` 写入的值覆盖代码默认值,是实际运行参数。
---
## 代码中未使用的模块
以下 `.cpp` 文件已编译进 `common_lib` 但主循环中从未调用:
| 文件 | 功能 | 状态 |
|---|---|---|
| `PIDController.cpp` | 位置式/增量式 PID | 类存在但无实例化,无调用 |
| `serial.cpp` | VOFA 串口可视化 (vofa_justfloat/vofa_image) | 已实现但无调用 |
| `Timer.cpp` | 定时器线程 | 已实现但无调用(Video 依赖它但 Video 也未用) |
| `video.cpp` | 视频文件流读取 | 已实现但无调用 |
---
## 当前已知缺陷 / 未利用能力
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~59row 0~9 的 mid_line 保持 -1 不可靠
6. **mid_val == 255 防御** — 代码中写死但 255 永远不会出现在 mid_line 中(当前实现下)
7. **LCD 坐标缩放硬编码** — 检测框绘制用 `newWidth/160.0f`,假设 raw_frame 宽=160。回退模式下 raw_frame=640 时出错
+283
View File
@@ -0,0 +1,283 @@
# 锥桶避障功能 — 中线变形方案
## 1. 核心思路
**不改舵机代码,直接修改 `mid_line[]` 数组。** 在锥桶附近把中线"推开"一个凸起,后续巡线逻辑(deviation → deadband → servo)原样工作,车自然跟着变形后的中线绕开锥桶。
不使用任何推入计时器、归还逻辑、EMA 平滑——锥桶消失后,下一帧 `image_main()` 重新计算 `mid_line`,自动恢复。
---
## 2. 坐标与边界过滤
```
模型空间 (raw_frame):
正常模式: 160×120 (MJPEG 1/4解码)
回退模式: 640×480
巡线空间映射:
cone_col = box.cx * line_tracking_width / raw_frame.cols (典型 0~79)
cone_row = box.cy * line_tracking_height / raw_frame.rows (典型 0~59)
换算速查 (正常模式 160×120 → 典型 80×60):
cone_col = cx / 2
cone_row = cy / 2
```
边界过滤 (cls=0, conf ≥ cone_thresh):
```
row_lt < 10 → 丢弃 (row 0~9 无可靠巡线数据)
row_lt >= line_tracking_height → 丢弃
left_line[row_lt] == -1 → 丢弃 (该行丢线)
right_line[row_lt] == -1 → 丢弃
col_lt < left_line[row_lt] → 丢弃 (赛道外)
col_lt > right_line[row_lt] → 丢弃 (赛道外)
```
---
## 3. 中线变形
### 3.1 方向
```
mid_at_cone = mid_line[cone_row]
cone_col < mid_at_cone → 锥在左 → push_mid 向右 (+)
cone_col > mid_at_cone → 锥在右 → push_mid 向左 (-)
direction = (cone_col < mid_at_cone) ? +1 : -1
```
### 3.2 变形区域
`start_row = max(10, cone_row - range)``cone_row`,共 (cone_row - start_row) 行:
```
row 0 ───────────────────────────────── 远
│ (不变形)
row 10 ─────────────────────────────────
│ start_row ────────────── t=0
│ 线性斜坡
│ foresee 行在这里看到部分偏移
│ cone_row ────────────── t=1
row 59 ───────────────────────────────── 近 (车前方)
```
### 3.3 数学
```
for row in [start_row, cone_row]:
t = (row - start_row) / (cone_row - start_row) // [0, 1]
push = t * cone_avoid_gain * (line_tracking_width / 2) * direction
mid_line[row] = original_mid_line[row] + push
mid_line[row] = clamp(mid_line[row],
left_line[row] + 2,
right_line[row] - 2)
```
**参数:**
- `cone_avoid_gain` (double, 0.3):归一化推离量。1.0 = 推到半幅赛道宽。默认 0.3 表示推到赛道 30% 宽度处。
- `cone_avoid_range` (int, 30):斜坡覆盖行数。必须足够大使 foresee 行感受到偏移。
---
## 4. 数字算例
**场景:** 直道,锥桶在赛道左侧。`lt_w=80, lt_h=60, cone_avoid_gain=0.3, cone_avoid_range=30`
```
原始数据:
cone_col=25, cone_row=50, mid_at_cone=40 (锥在左侧15列)
direction = +1 (推中线向右)
start_row = max(10, 50-30) = 20
half_w = 40
变形计算:
push_max = 0.3 * 40 * 1 = 12 (行50处的最大位移, 巡线空间列)
row=20 (start, t=0): mid_line = 40 + 0 = 40 → deviation = 40*2-80 = 0 → 舵机中位
row=25 (t=5/30=0.17): mid_line = 40 + 2.04 = 42 → deviation = 42*2-80 = +4 → 略右
row=35 (t=15/30=0.5): mid_line = 40 + 6.0 = 46 → deviation = 46*2-80 = +12 → 明显右转
row=50 (t=1.0): mid_line = 40 + 12 = 52 → deviation = 52*2-80 = +24 → 大幅右转
foresee=40 (显示空间) → check_row=40/2=20 (巡线空间):
mid_line[20] 未被变形 (start_row=20, t=0)
→ 舵机看不到! 需要减小 range 或增大 foresee
```
**问题暴露:** `foresee=40``check_row=20`,但 `start_row=20` 时 t=0 无偏移。
**修正:** 确保变形起点在 `check_row` 以上。两种方式:
1. 增大 `cone_avoid_range`(如 40),让 start_row 更低
2. 让斜坡向上延伸超过 start_row(即从 row=10 就开始)
**采用方案 2** 变形始终从 `row=10` 开始,`range` 参数控制斜坡的"陡峭度"而非范围。
---
## 5. 修正后的变形公式
```
range = cone_avoid_range // 陡峭度参数 (默认 30)
total_rows = cone_row - 10 // 实际跨度
for row in [10, cone_row]:
t_raw = (row - 10.0) / total_rows // 线性 [0, 1]
t = clamp(t_raw * (total_rows / range), // 用 range 缩放斜率
0.0, 1.0)
push = t * cone_avoid_gain * (line_tracking_width / 2) * direction
mid_line[row] = clamp(original_mid + push,
left_line[row] + 2,
right_line[row] - 2)
```
**效果(cone_avoid_range=30, cone_row=50, total_rows=40):**
```
total_rows/range = 40/30 = 1.33 ← 斜率因子 >1, 斜坡更陡
row=10: t_raw=0, t=0*1.33=0 → push=0
row=20: t_raw=0.25, t=0.25*1.33=0.33 → push=0.33*12=4.0 → deviation=+8 (刚好过死区)
row=35: t_raw=0.625,t=0.83 → push=10 → deviation=+20
row=50: t_raw=1.0, t=1.0 → push=12 → deviation=+24
```
foresee 行 (row=20) 收到 33% 的偏移,产生 +8px 偏差 → 刚好越过 deadband(8px) → 舵机开始右转。
**调参规则:**
- 增大 `cone_avoid_range` → 斜坡更缓 → foresee 行偏移更小 → 转向更柔和
- 减小 `cone_avoid_range` → 斜坡更陡 → foresee 行偏移更大 → 转向更猛
- 增大 `cone_avoid_gain` → 整体偏移量增大 → 转向更猛
---
## 6. 去抖机制
与之前一致,不改变:
```
#define CONE_MIN_FRAMES 3
#define CONE_COUNT_DECAY 1
g_cone_frames: 连续检测计数
g_cone_last_row / g_cone_last_col: 位置跟踪 (容差 ±5行/±10列)
g_cone_confirmed = (g_cone_frames >= CONE_MIN_FRAMES)
```
**关键差异:** 去抖只决定"是否触发变形",变形本身没有持续时间概念。
- 锥桶在 → 变形在(每帧重新计算 mid_line 变形)
- 锥桶消失 → 下帧 image_main() 重新计算 mid_line → 变形自动消失
- 不去抖时的抖动:锥桶在边界附近闪烁会使 mid_line 来回跳,用去抖平滑
---
## 7. 舵机与电机
**舵机:** 零改动。Step 6 读取 `mid_line[foresee/2]`,看到变形后的中线值,自然输出偏转方向。
**电机:** 推入期间减速:
```
if (g_cone_confirmed && g_zstate != Z_STOP) {
effective_speed = target_speed * cone_speed_factor;
} else {
effective_speed = target_speed;
}
ControlUpdate(effective_speed, g_zstate == Z_STOP);
```
---
## 8. 参数配置
### 8.1 启动时一次性读取(运行中不重新读文件)
| 文件 | 类型 | 默认 | 含义 |
|---|---|---|---|
| `./cone_avoid_gain` | double | 0.3 | 归一化推离量 (0=不推, 1=推到赛道边缘) |
| `./cone_avoid_range` | int | 30 | 斜坡陡峭度 (越小越陡,foresee 行感受越强) |
### 8.2 热更新参数
| 文件 | 类型 | 默认 | 含义 |
|---|---|---|---|
| `./cone_speed` | double | 0.5 | 锥桶触发时速度倍率 |
| `./cone_min_frames` | int | 3 | 连续确认帧数 |
| `./cone_margin` | int | 0 | 0=中心点过滤, 1=框边缘过滤 |
| `./cone_thresh` | double | 0.80 | 锥桶置信度阈值 (业务层, 不改 NMS) |
### 8.3 读取实现
**main.cpp 启动时(一次性):**
```cpp
double avoid_gain_val = readDoubleFromFile("./cone_avoid_gain");
int avoid_range_val = (int)readDoubleFromFile("./cone_avoid_range");
if (avoid_gain_val <= 0) avoid_gain_val = 0.3;
if (avoid_range_val <= 0) avoid_range_val = 30;
g_cfg.cone_avoid_gain = avoid_gain_val;
g_cfg.cone_avoid_range = avoid_range_val;
```
**global.cpp (cfg_load_all,热更新)**
```cpp
g_cfg.cone_speed = readDoubleFromFile(cone_speed_file);
g_cfg.cone_min_frames = (int)readDoubleFromFile(cone_min_frames_file);
g_cfg.cone_margin = (int)readDoubleFromFile(cone_margin_file);
g_cfg.cone_thresh = readDoubleFromFile(cone_thresh_file);
```
**ctl.sh**
```sh
echo 0.3 > "$DIR/cone_avoid_gain" 2>/dev/null
echo 30 > "$DIR/cone_avoid_range" 2>/dev/null
echo 0.5 > "$DIR/cone_speed" 2>/dev/null
echo 3 > "$DIR/cone_min_frames" 2>/dev/null
echo 0 > "$DIR/cone_margin" 2>/dev/null
echo 0.80 > "$DIR/cone_thresh" 2>/dev/null
```
---
## 9. 代码修改清单
```
lib/global.h:
├── CfgCache 新增: cone_avoid_gain, cone_avoid_range,
│ cone_speed, cone_min_frames, cone_margin, cone_thresh
└── 新增文件名常量
src/global.cpp:
└── cfg_load_all() 追加热更新参数 (不含 avoid_gain/avoid_range)
main/main.cpp:
└── CameraInit 之前: 读取 cone_avoid_gain, cone_avoid_range 写入 g_cfg
src/camera.cpp:
├── #define CONE_CLASS 0
├── 新增静态变量: g_cone_frames, g_cone_last_row/col, g_cone_confirmed
├── Step 5 之后: 插入锥桶检测+边界过滤+去抖
├── Step 5.5: 中线变形 (修改 mid_line[row] for row in [10, cone_row])
├── Step 7 电机: g_cone_confirmed → effective_speed *= cone_speed_factor
└── Step 8 LCD: 锥桶框改橙色
ctl.sh:
└── init_pins() 追加 6 个新文件默认值
```
---
## 10. 方案对比
| | 旧方案(舵机推入) | 新方案(中线变形) |
|---|---|---|
| 舵机代码 | 需改动 | 零改动 |
| deadband | 需绕过 | 自 然生效 |
| steer_gain | 需单独 cone_gain | 共用 |
| 归还机制 | 需要计时器+衰减 | 自动 (image_main 重算) |
| 平滑性 | 需 EMA | 斜坡自带平滑 |
| 参数数量 | 7 | 6 |
| 锥桶消失恢复 | 需衰减逻辑 | 下一帧自动 |
+95
View File
@@ -0,0 +1,95 @@
# 编码器刹车 — 设计与实现
## 1. 硬件
编码器2,引脚:
| 信号 | 引脚 | 模式 |
|---|---|---|
| LSB 脉冲 | GPIO 67 | 输入 |
| DIR 方向 | GPIO 72 | 输入 |
两引脚均属 gpiochip64GPA64base=6416 pins)。
## 2. 触发条件
`ControlUpdate(speed, zebra_block)``zebra_block == true` 时进入刹车流程。`zebra_block``CameraHandler` Step 5 斑马线状态机传入——当 `g_zstate == Z_STOP` 时为 true。
## 3. 刹车状态机
```
┌──────────┐
正常行驶 ───→ │ IDLE │
└────┬─────┘
│ zebra_block==true
┌──────────┐
│ BRAKING │ ← 施加 -30% 占空比
│ │ 每帧读 GPIO 67
└────┬─────┘
┌───────────┴───────────┐
│ 脉冲变化 │ 脉冲不变
▼ ▼
轮子在转 → 继续刹车 g_brake_still++
重置计数器 │
连续 5 帧无变化?
或超时 60 帧?
┌──────────┐
│ HOLDING │ ← duty=0,保持静止
└──────────┘
zebra_block==false
┌──────────┐
│ IDLE │
└──────────┘
```
## 4. 参数
定义在 `src/control.cpp`
| 常量 | 值 | 含义 |
|---|---|---|
| `BRAKE_STILL_THRESH` | 5 | 连续无脉冲帧数阈值,超过即判停 |
| `BRAKE_TIMEOUT` | 60 | 最长刹车帧数(~2s @30fps),强制释放 |
| `BRAKE_DUTY` | -30 | 刹车占空比(-100~100,负值=反转) |
## 5. 读取方式
GPIO 67 通过 sysfs 文件描述符轮询(`GPIO::readValue()`),每帧 `lseek` + `read` 一次。不依赖中断。
**为什么轮询够用:** 30fps 帧率 = 33ms 间隔。编码器假设 100+ 脉冲/转,即使 1 转/秒的极慢速度也有 3+ 脉冲/帧间隔,不会漏判。
## 6. 刹车强度
```
motor[i]->updateduty(-30)
```
`MotorController::updateduty()` 内:
- 占空比:`period * |duty| / 100 = 50000 * 30 / 100 = 15000 ns`
- 方向:`duty < 0``directionGPIO.setValue(false)` → 反转
即 30% 占空比的反向驱动,足够刹停但不会让车向后猛冲。
## 7. 编码器生效范围
- **仅在斑马线 STOP 时**使用编码器
- 正常行驶时编码器处于"打开但未使用"状态(fd 保持 open,但不 read
- 锥桶避障不使用编码器
- `g_cfg.start == 0` 时刹车状态机重置
## 8. 关键代码路径
```
main.cpp
└── while(running)
└── CameraHandler()
└── ControlUpdate(target_speed, g_zstate == Z_STOP)
└── if (zebra_block) → 刹车状态机
└── encoderLSB.readValue() ← GPIO 67
```
+103
View File
@@ -0,0 +1,103 @@
# 红绿灯识别 — 设计与实现
## 1. 模型检测
NanoDet V10 模型 4 类输出中:
- cls=1: 红灯
- cls=2: 绿灯
模型置信度阈值由 `g_thresh[1]``g_thresh[2]` 控制(`src/camera.cpp:25`),当前分别为 0.80。
## 2. 状态机
```
┌──────────────────────────────────────────┐
│ │
▼ │
┌──────────┐ 红灯连续3帧 ┌──────────┐ │
正常行驶 │ TL_NORMAL │ ─────────────→ │ TL_STOP │ │
│ │ │ 刹车ON │ │
└──────────┘ └────┬─────┘ │
▲ │ 红灯消失 │
│ ▼ │
│ ┌──────────────┐ │
│ 绿灯连续3帧 │ TL_WAIT_GREEN │ │
└──────────────────│ 刹车ON │ │
└──────────────┘ │
```
| 状态 | 刹车 | 锥桶 | 触发条件 |
|---|---|---|---|
| TL_NORMAL | 无 | 正常 | — |
| TL_STOP | ON | 抑制 | 红灯连续 3 帧确认 |
| TL_WAIT_GREEN | ON | 抑制 | 红灯从视野消失 |
**去抖:** 红灯 3 帧确认触发,衰减 `-1`/帧;绿灯 3 帧确认触发,衰减 `-1`/帧。
**为什么红灯消失后还要等绿灯:** 防止红灯误检消失后误触发通行。确保绿灯确实出现在视野中才放行。
## 3. 刹车机制
和斑马线完全一样,复用编码器比例刹车:
```
ControlUpdate(spd, g_zstate == Z_STOP || tl_block)
刹车信号 = 斑马线STOP || 红灯STOP || 等待绿灯
```
刹车参数(热更新):
| 文件 | 默认 | 含义 |
|---|---|---|
| `./brake_scale` | 10 | 速度(pps) → 刹车占空比(ns) |
| `./brake_max` | 10000 | 最大刹车占空比(ns) |
## 4. 与其他模块的交互
```
决策优先级:
Z_STOP (斑马线) > TL_STOP/WAIT_GREEN (红绿灯) > cone (锥桶) > 正常巡线
锥桶抑制: g_zstate != Z_STOP && g_tl_state == TL_NORMAL 时才运行
电机刹车: zebra_STOP || tl_block 任一为 true 即刹车
LCD 状态: "R"=红灯停车 "G"=等绿灯, 拼接斑马线状态
```
## 5. 关键代码路径
```
CameraHandler (src/camera.cpp)
Step 4: 模型推理 → g_boxes[], g_box_count
Step 5: 斑马线状态机
Step 5.5: 红绿灯状态机
├─ 遍历 g_boxes, 统计 red_seen/green_seen
├─ 去抖计数 (3帧确认, 衰减-1/帧)
└─ 状态跃迁
Step 5.6: 锥桶检测 (g_zstate!=Z_STOP && g_tl_state==TL_NORMAL 时才运行)
Step 7: 电机控制
ControlUpdate(spd, g_zstate==Z_STOP || tl_block)
control.cpp:
ControlUpdate(speed, zebra_block)
└─ zebra_block → 编码器线程读速度 → 比例刹车
```
## 6. 状态变量
定义在 `src/camera.cpp`
```cpp
enum TLState { TL_NORMAL, TL_STOP, TL_WAIT_GREEN };
static TLState g_tl_state = TL_NORMAL;
static int g_tl_red_frames = 0; // 红灯连续计数
static int g_tl_green_frames = 0; // 绿灯连续计数
```
## 7. LCD 显示
状态字拼接:`[红绿灯][斑马线]`
- `RN` — 红灯停车 + 正常巡线
- `GN` — 等绿灯 + 正常巡线
- `RS` — 红灯 + 斑马线同时 (极少见)
- `NN` — 全部正常
+122
View File
@@ -0,0 +1,122 @@
/*
* encoder_brake_demo — 编码器刹车测试 v2
*
* 编码器2 (gpio67脉冲/gpio72方向) → 控制后面两个轮子同时反转
* 左后轮: pwmchip8/pwm2, gpio12
* 右后轮: pwmchip8/pwm1, gpio13
* 使能: gpio73
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <fcntl.h>
#include <unistd.h>
#include <ctime>
#include <csignal>
static volatile bool running = true;
void sig_handler(int) { running = false; }
static void write_file(const char *path, const char *val) {
int fd = open(path, O_WRONLY);
if (fd >= 0) { write(fd, val, strlen(val)); close(fd); }
}
static int read_pin(const char *path) {
int fd = open(path, O_RDONLY);
char v = '0';
if (fd >= 0) { read(fd, &v, 1); close(fd); }
return (v == '1') ? 1 : 0;
}
static long now_ns() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000000000L + ts.tv_nsec;
}
int main() {
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
printf("=== Encoder Brake Demo v2 ===\n");
printf("编码器: gpio67(脉冲) gpio72(方向)\n");
printf("左后轮: gpio12 pwm2 | 右后轮: gpio13 pwm1\n");
printf("掰任意后轮 → 两轮同时反向制动\n\n");
write_file("/sys/class/gpio/gpio73/value", "1");
write_file("/sys/class/gpio/gpio12/direction", "out");
write_file("/sys/class/gpio/gpio13/direction", "out");
write_file("/sys/class/gpio/gpio67/direction", "in");
write_file("/sys/class/gpio/gpio72/direction", "in");
write_file("/sys/class/pwm/pwmchip8/pwm1/duty_cycle", "0");
write_file("/sys/class/pwm/pwmchip8/pwm2/duty_cycle", "0");
const long WINDOW_NS = 100000000L; // 100ms 测速窗口
const int MAX_DUTY = 10000; // 最大占空比 20%
const double SCALE = 10.0; // 速度 → 占空比 缩放系数
int prev_lsb = read_pin("/sys/class/gpio/gpio67/value");
int pulse_cnt = 0;
long t0 = now_ns();
int brake_duty = 0;
long last_print = 0;
int last_dir = read_pin("/sys/class/gpio/gpio72/value");
printf("%-8s %8s %8s %6s %8s %s\n",
"raw_pulse", "speed", "duty_ns", "enc_dir", "mot_dir", "");
while (running) {
int cur_lsb = read_pin("/sys/class/gpio/gpio67/value");
int cur_dir = read_pin("/sys/class/gpio/gpio72/value");
if (cur_lsb != prev_lsb) {
pulse_cnt++;
prev_lsb = cur_lsb;
}
if (cur_dir != last_dir) last_dir = cur_dir;
long t1 = now_ns();
long dt = t1 - t0;
if (dt >= WINDOW_NS) {
double speed = pulse_cnt / (dt / 1e9);
if (speed > 0.5) {
brake_duty = (int)(speed * SCALE);
if (brake_duty > MAX_DUTY) brake_duty = MAX_DUTY;
const char *mdir = cur_dir ? "0" : "1";
write_file("/sys/class/gpio/gpio12/value", mdir);
write_file("/sys/class/gpio/gpio13/value", mdir);
} else {
brake_duty = 0;
}
char buf[16];
snprintf(buf, sizeof(buf), "%d", brake_duty);
write_file("/sys/class/pwm/pwmchip8/pwm1/duty_cycle", buf);
write_file("/sys/class/pwm/pwmchip8/pwm2/duty_cycle", buf);
if (t1 - last_print > 300000000L) {
printf("%-8d %8.1f %8d %6d %8s\r",
pulse_cnt, speed, brake_duty, cur_dir,
cur_dir ? "rev" : "fwd");
fflush(stdout);
last_print = t1;
}
pulse_cnt = 0;
t0 = t1;
}
}
write_file("/sys/class/pwm/pwmchip8/pwm1/duty_cycle", "0");
write_file("/sys/class/pwm/pwmchip8/pwm2/duty_cycle", "0");
write_file("/sys/class/gpio/gpio73/value", "0");
printf("\n=== Demo stopped ===\n");
return 0;
}
-5
View File
@@ -1,5 +0,0 @@
# demo1
add_executable(encoder_demo encoder_demo.cpp)
# 链接 OpenCV 库
target_link_libraries(encoder_demo common_lib ${OpenCV_LIBS})
-22
View File
@@ -1,22 +0,0 @@
/*
* @Author: ilikara 3435193369@qq.com
* @Date: 2024-11-30 09:06:41
* @LastEditors: ilikara 3435193369@qq.com
* @LastEditTime: 2025-01-08 07:09:44
* @FilePath: /smartcar/encoder_demo/encoder_demo.cpp
* @Description: 编码器测试用
*
* Copyright (c) 2024 by ilikara 3435193369@qq.com, All Rights Reserved.
*/
#include "encoder.h"
#include <iostream>
int main()
{
ENCODER encoder(0, 73);
while (1)
{
std::cout << encoder.pulse_counter_update() << std::endl;
usleep(5000);
}
return 0;
}
-5
View File
@@ -1,5 +0,0 @@
# demo1
add_executable(framebuffer_demo framebuffer_demo.cpp)
# 链接 OpenCV 库
target_link_libraries(framebuffer_demo common_lib ${OpenCV_LIBS})
-207
View File
@@ -1,207 +0,0 @@
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <cstring>
#include <cmath>
#include <iostream>
using namespace std;
// Framebuffer 设备
const char *fb_device = "/dev/fb0";
// 屏幕分辨率
const int screen_width = 160;
const int screen_height = 128;
// RGB565 颜色宏
#define RGB565(r, g, b) ((((r) >> 3) << 11) | (((g) >> 2) << 5) | ((b) >> 3))
// 8x8 位图字体(26 个字母 + 10 个数字)
const unsigned char font[36][8] = {
// A-Z
{0b00111000, 0b01000100, 0b01000100, 0b01111100, 0b01000100, 0b01000100, 0b01000100, 0b00000000}, // A
{0b01111000, 0b01000100, 0b01000100, 0b01111000, 0b01000100, 0b01000100, 0b01111000, 0b00000000}, // B
{0b00111100, 0b01000010, 0b01000000, 0b01000000, 0b01000000, 0b01000010, 0b00111100, 0b00000000}, // C
{0b01111000, 0b01000100, 0b01000100, 0b01000100, 0b01000100, 0b01000100, 0b01111000, 0b00000000}, // D
{0b01111110, 0b01000000, 0b01000000, 0b01111000, 0b01000000, 0b01000000, 0b01111110, 0b00000000}, // E
{0b01111110, 0b01000000, 0b01000000, 0b01111000, 0b01000000, 0b01000000, 0b01000000, 0b00000000}, // F
{0b00111100, 0b01000010, 0b01000000, 0b01001110, 0b01000010, 0b01000010, 0b00111100, 0b00000000}, // G
{0b01000100, 0b01000100, 0b01000100, 0b01111100, 0b01000100, 0b01000100, 0b01000100, 0b00000000}, // H
{0b00111000, 0b00010000, 0b00010000, 0b00010000, 0b00010000, 0b00010000, 0b00111000, 0b00000000}, // I
{0b00011110, 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b01001000, 0b00110000, 0b00000000}, // J
{0b01000100, 0b01001000, 0b01010000, 0b01100000, 0b01010000, 0b01001000, 0b01000100, 0b00000000}, // K
{0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b01111110, 0b00000000}, // L
{0b01000100, 0b01101100, 0b01010100, 0b01010100, 0b01000100, 0b01000100, 0b01000100, 0b00000000}, // M
{0b01000100, 0b01100100, 0b01010100, 0b01001100, 0b01000100, 0b01000100, 0b01000100, 0b00000000}, // N
{0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b01000100, 0b01000100, 0b00111000, 0b00000000}, // O
{0b01111000, 0b01000100, 0b01000100, 0b01111000, 0b01000000, 0b01000000, 0b01000000, 0b00000000}, // P
{0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b01010100, 0b01001000, 0b00110100, 0b00000000}, // Q
{0b01111000, 0b01000100, 0b01000100, 0b01111000, 0b01010000, 0b01001000, 0b01000100, 0b00000000}, // R
{0b00111100, 0b01000010, 0b01000000, 0b00111000, 0b00000100, 0b01000010, 0b00111100, 0b00000000}, // S
{0b01111100, 0b00010000, 0b00010000, 0b00010000, 0b00010000, 0b00010000, 0b00010000, 0b00000000}, // T
{0b01000100, 0b01000100, 0b01000100, 0b01000100, 0b01000100, 0b01000100, 0b00111000, 0b00000000}, // U
{0b01000100, 0b01000100, 0b01000100, 0b01000100, 0b01000100, 0b00101000, 0b00010000, 0b00000000}, // V
{0b01000100, 0b01000100, 0b01000100, 0b01010100, 0b01010100, 0b01101100, 0b01000100, 0b00000000}, // W
{0b01000100, 0b01000100, 0b00101000, 0b00010000, 0b00101000, 0b01000100, 0b01000100, 0b00000000}, // X
{0b01000100, 0b01000100, 0b00101000, 0b00010000, 0b00010000, 0b00010000, 0b00010000, 0b00000000}, // Y
{0b01111100, 0b00000100, 0b00001000, 0b00010000, 0b00100000, 0b01000000, 0b01111100, 0b00000000}, // Z
// 0-9
{0b00111000, 0b01000100, 0b01001100, 0b01010100, 0b01100100, 0b01000100, 0b00111000, 0b00000000}, // 0
{0b00010000, 0b00110000, 0b00010000, 0b00010000, 0b00010000, 0b00010000, 0b00111000, 0b00000000}, // 1
{0b00111000, 0b01000100, 0b00000100, 0b00001000, 0b00010000, 0b00100000, 0b01111100, 0b00000000}, // 2
{0b00111000, 0b01000100, 0b00000100, 0b00011000, 0b00000100, 0b01000100, 0b00111000, 0b00000000}, // 3
{0b00001000, 0b00011000, 0b00101000, 0b01001000, 0b01111100, 0b00001000, 0b00001000, 0b00000000}, // 4
{0b01111100, 0b01000000, 0b01111000, 0b00000100, 0b00000100, 0b01000100, 0b00111000, 0b00000000}, // 5
{0b00111000, 0b01000100, 0b01000000, 0b01111000, 0b01000100, 0b01000100, 0b00111000, 0b00000000}, // 6
{0b01111100, 0b00000100, 0b00001000, 0b00010000, 0b00100000, 0b00100000, 0b00100000, 0b00000000}, // 7
{0b00111000, 0b01000100, 0b01000100, 0b00111000, 0b01000100, 0b01000100, 0b00111000, 0b00000000}, // 8
{0b00111000, 0b01000100, 0b01000100, 0b00111100, 0b00000100, 0b01000100, 0b00111000, 0b00000000} // 9
};
// 绘制一个像素
void draw_pixel(ushort *fb_data, int x, int y, ushort color)
{
if (x >= 0 && x < screen_width && y >= 0 && y < screen_height)
{
fb_data[y * screen_width + x] = color;
}
}
// 绘制一个矩形
void draw_rect(ushort *fb_data, int x, int y, int width, int height, ushort color)
{
for (int i = x; i < x + width; i++)
{
for (int j = y; j < y + height; j++)
{
draw_pixel(fb_data, i, j, color);
}
}
}
// 绘制一个圆形
void draw_circle(ushort *fb_data, int center_x, int center_y, int radius, ushort color)
{
for (int y = -radius; y <= radius; y++)
{
for (int x = -radius; x <= radius; x++)
{
if (x * x + y * y <= radius * radius)
{
draw_pixel(fb_data, center_x + x, center_y + y, color);
}
}
}
}
// 绘制一个字符
void draw_char(ushort *fb_data, int x, int y, char c, ushort color)
{
int index = -1;
if (c >= 'A' && c <= 'Z')
{
index = c - 'A'; // A-Z 对应 0-25
}
if (c >= 'a' && c <= 'z')
{
index = c - 'a'; // A-Z 对应 0-25
}
else if (c >= '0' && c <= '9')
{
index = c - '0' + 26; // 0-9 对应 26-35
}
if (index >= 0 && index < 36)
{
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
if (font[index][i] & (1 << (7 - j)))
{
draw_pixel(fb_data, x + j, y + i, color);
}
}
}
}
}
// 绘制字符串
void draw_string(ushort *fb_data, int x, int y, const char *str, ushort color)
{
while (*str)
{
draw_char(fb_data, x, y, *str, color);
x += 8; // 每个字符占 8 像素宽度
str++;
}
}
int main()
{
// 打开 Framebuffer 设备
int fb_fd = open(fb_device, O_RDWR);
if (fb_fd == -1)
{
cerr << "Error: Cannot open framebuffer device" << endl;
return -1;
}
// 获取 Framebuffer 信息
struct fb_var_screeninfo vinfo;
if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo))
{
cerr << "Error: Cannot get framebuffer information" << endl;
close(fb_fd);
return -1;
}
// 检查 Framebuffer 是否支持 RGB565
if (vinfo.bits_per_pixel != 16)
{
cerr << "Error: Framebuffer is not RGB565 format" << endl;
close(fb_fd);
return -1;
}
// 映射 Framebuffer 到内存
size_t fb_size = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
ushort *fb_data = (ushort *)mmap(0, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);
if (fb_data == MAP_FAILED)
{
cerr << "Error: Failed to map framebuffer to memory" << endl;
close(fb_fd);
return -1;
}
// 清空屏幕(填充黑色)
memset(fb_data, 0, fb_size);
// 绘制渐变背景
for (int y = 0; y < screen_height; y++)
{
for (int x = 0; x < screen_width; x++)
{
ushort color = RGB565(x, y, (x + y) / 2);
draw_pixel(fb_data, x, y, color);
}
}
// 绘制一个矩形
draw_rect(fb_data, 20, 20, 50, 30, RGB565(255, 0, 0)); // 红色矩形
// 绘制一个圆形
draw_circle(fb_data, 100, 80, 20, RGB565(0, 255, 0)); // 绿色圆形
// 绘制一段文字
draw_string(fb_data, 10, 100, "Hello World", RGB565(255, 255, 255)); // 白色文字
// 释放资源
munmap(fb_data, fb_size);
close(fb_fd);
return 0;
}
-2
View File
@@ -1,2 +0,0 @@
add_executable(gd13_demo gd13_demo.cpp)
target_link_libraries(gd13_demo common_lib ${OpenCV_LIBS})
-103
View File
@@ -1,103 +0,0 @@
/*
* gd13_demo — 斑马线检测演示 (调用 zebra_detect.h)
*/
#include <opencv2/opencv.hpp>
#include <iostream>
#include <cstring>
#include <csignal>
#include <atomic>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include "zebra_detect.h"
using namespace cv;
using namespace std;
static atomic<bool> g_running{true};
static void signal_handler(int) { g_running.store(false); }
static uint16_t rgb565(uint8_t r, uint8_t g, uint8_t b)
{
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
static void display_on_fb(const Mat &bgr, uint16_t *fb_buf, int scr_w, int scr_h)
{
Mat display;
resize(bgr, display, Size(scr_w, scr_h));
for (int y = 0; y < scr_h; ++y)
for (int x = 0; x < scr_w; ++x)
{
Vec3b px = display.at<Vec3b>(y, x);
fb_buf[y * scr_w + x] = rgb565(px[2], px[1], px[0]);
}
}
int main(int argc, char *argv[])
{
bool no_display = false;
for (int i = 1; i < argc; ++i)
if (strcmp(argv[i], "--no-display") == 0) no_display = true;
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
VideoCapture cap(0);
cap.open(0, CAP_V4L2);
if (!cap.isOpened()) cap.open(0);
if (!cap.isOpened()) { cerr << "摄像头打开失败" << endl; return 1; }
printf("摄像头已打开\n");
int fb_fd = -1;
uint16_t *fb_buf = nullptr;
int scr_w = 0, scr_h = 0;
if (!no_display)
{
fb_fd = open("/dev/fb0", O_RDWR);
if (fb_fd >= 0)
{
struct fb_var_screeninfo vinfo;
ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo);
scr_w = vinfo.xres; scr_h = vinfo.yres;
size_t fb_size = vinfo.yres_virtual * vinfo.xres_virtual * 2;
fb_buf = (uint16_t *)mmap(nullptr, fb_size, PROT_READ|PROT_WRITE,
MAP_SHARED, fb_fd, 0);
}
}
Mat frame, result;
printf("开始斑马线检测...\n");
while (g_running)
{
if (!cap.read(frame) || frame.empty()) { usleep(5000); continue; }
ZebraResult zr = detect_zebra_crossing(frame);
result = frame.clone();
if (zr.detected)
{
putText(result, "ZEBRA! dist=" + to_string(zr.distance_px) + "px",
Point(10, 30), FONT_HERSHEY_SIMPLEX, 0.8,
Scalar(0, 0, 255), 2);
printf("ZEBRA detected | distance=%d px\n", zr.distance_px);
}
else
{
printf("not detected | distance=%d\n", zr.distance_px);
}
if (!no_display && fb_buf)
display_on_fb(result, fb_buf, scr_w, scr_h);
}
cap.release();
if (fb_buf) munmap(fb_buf, scr_w * scr_h * 2);
if (fb_fd >= 0) close(fb_fd);
return 0;
}
-5
View File
@@ -1,5 +0,0 @@
# demo2
add_executable(image_test image_test.cpp)
# 链接 OpenCV 库
target_link_libraries(image_test common_lib ${OpenCV_LIBS})
-420
View File
@@ -1,420 +0,0 @@
/*
* @Author: ilikara 3435193369@qq.com
* @Date: 2025-01-07 06:26:01
* @LastEditors: ilikara 3435193369@qq.com
* @LastEditTime: 2025-03-26 12:26:22
* @FilePath: /smartcar/opencv_demo2/opencv_demo2.cpp
* @Description:
*/
#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
#include <map>
#include <dirent.h>
#include <termios.h>
#include <unistd.h>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <sstream>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include "image_cv.h"
using namespace std;
using ImageCallback = void (*)(const string &fullpath);
const int cameraWidth = 320, cameraHeight = 240;
int screenWidth, screenHeight;
const int calc_scale = 2;
int newWidth, newHeight;
ImageCallback image_callback = nullptr;
string base_path;
struct termios orig_termios;
map<int, string> file_map;
int current_num = -1;
const char *fb_device = "/dev/fb0";
int fb_fd;
ushort *fb_data;
struct fb_var_screeninfo vinfo;
std::chrono::_V2::system_clock::time_point frame_begin, frame_end;
std::chrono::duration<double> frame_duration;
int displayMode = 0;
const int displayModeCount = 7;
void imgInit()
{
// 动态设置屏幕分辨率
screenWidth = vinfo.xres;
screenHeight = vinfo.yres;
// 计算 newWidth 和 newHeight,确保图像适应屏幕
double widthRatio = static_cast<double>(screenWidth) / cameraWidth;
double heightRatio = static_cast<double>(screenHeight) / cameraHeight;
double scale = std::min(widthRatio, heightRatio); // 选择较小的比例,确保图像不超出屏幕
newWidth = static_cast<int>(cameraWidth * scale);
newHeight = static_cast<int>(cameraHeight * scale);
line_tracking_width = newWidth / calc_scale;
line_tracking_height = newHeight / calc_scale;
}
// 将 RGB888 转换为 RGB565
ushort rgb888_to_rgb565(const cv::Vec3b &color)
{
return ((color[2] >> 3) << 11) | ((color[1] >> 2) << 5) | (color[0] >> 3);
}
cv::Mat img;
void display()
{
cv::Mat fbMat(screenHeight, screenWidth, CV_8UC3, cv::Scalar(0, 0, 0));
cv::Mat resizedFrame;
cv::Mat coloredResizedFrame;
// 将图像从 BGR 转换为 HSV
cv::Mat hsvImage;
cv::cvtColor(img, hsvImage, cv::COLOR_BGR2HSV);
// 分离 HSV 通道
std::vector<cv::Mat> hsvChannels;
cv::split(hsvImage, hsvChannels);
cv::Mat bframe;
switch (displayMode)
{
case 0:
// 缩放视频到新尺寸
cv::resize(binarizedFrame, resizedFrame, cv::Size(newWidth, newHeight));
// 将单通道的二值化图像转换为三通道的彩色图像
cv::cvtColor(resizedFrame, coloredResizedFrame, cv::COLOR_GRAY2BGR); // 转换为彩色图像
break;
case 1:
cv::resize(raw_frame, coloredResizedFrame, cv::Size(newWidth, newHeight));
break;
case 2:
// 缩放视频到新尺寸
cv::resize(morphologyExFrame, resizedFrame, cv::Size(newWidth, newHeight));
// 将单通道的二值化图像转换为三通道的彩色图像
cv::cvtColor(resizedFrame, coloredResizedFrame, cv::COLOR_GRAY2BGR); // 转换为彩色图像
break;
case 3:
case 4:
case 5:
// 缩放视频到新尺寸
cv::resize(hsvChannels[displayMode - 3], resizedFrame, cv::Size(newWidth, newHeight));
cv::threshold(resizedFrame, bframe, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
// 将单通道的二值化图像转换为三通道的彩色图像
cv::cvtColor(bframe, coloredResizedFrame, cv::COLOR_GRAY2BGR); // 转换为彩色图像
break;
case 6:
// 缩放视频到新尺寸
cv::resize(hsvImage, resizedFrame, cv::Size(newWidth, newHeight));
cv::Mat yellowMask;
inRange(resizedFrame, cv::Scalar(10, 100, 100), cv::Scalar(40, 255, 255), yellowMask);
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));
morphologyEx(yellowMask, yellowMask, cv::MORPH_OPEN, kernel);
morphologyEx(yellowMask, yellowMask, cv::MORPH_CLOSE, kernel);
bframe = yellowMask;
// 将单通道的二值化图像转换为三通道的彩色图像
cv::cvtColor(bframe, coloredResizedFrame, cv::COLOR_GRAY2BGR); // 转换为彩色图像
break;
}
// 将缩放后的图像居中放置在帧缓冲区图像中,填充黑色边框
fbMat.setTo(cv::Scalar(0, 0, 0)); // 清空缓冲区(填充黑色)
cv::Rect roi((screenWidth - newWidth) / 2, (screenHeight - newHeight) / 2, newWidth, newHeight);
coloredResizedFrame.copyTo(fbMat(roi));
// 绘制左右边界线和中线
int scaledLeftX, scaledRightX, scaledMidX, scaledLeftFilteredX, scaledRightFilteredX, scaledMidFilteredX, scaledY;
cv::line(fbMat(roi), cv::Point(newWidth / 2, 0), cv::Point(newWidth / 2, newHeight), cv::Scalar(0, 0, 0), 1);
cv::line(fbMat(roi), cv::Point(0, 40), cv::Point(newWidth, 40), cv::Scalar(0, 0, 0), 1);
for (int y = 0; y < line_tracking_height; y++)
{
// 根据缩放比例调整X坐标
scaledLeftX = static_cast<int>(left_line[y] * calc_scale);
scaledRightX = static_cast<int>(right_line[y] * calc_scale);
scaledMidX = static_cast<int>(mid_line[y] * calc_scale);
scaledLeftFilteredX = static_cast<int>(left_line_filtered[y] * calc_scale);
scaledRightFilteredX = static_cast<int>(right_line_filtered[y] * calc_scale);
scaledMidFilteredX = static_cast<int>(mid_line_filtered[y] * calc_scale);
scaledY = static_cast<int>(y * calc_scale);
cv::line(fbMat(roi), cv::Point(scaledLeftX, scaledY), cv::Point(scaledLeftX, scaledY), cv::Scalar(0, 0, 255), calc_scale);
cv::line(fbMat(roi), cv::Point(scaledLeftFilteredX, scaledY), cv::Point(scaledLeftFilteredX, scaledY), cv::Scalar(255, 255, 0), calc_scale);
cv::line(fbMat(roi), cv::Point(scaledRightX, scaledY), cv::Point(scaledRightX, scaledY), cv::Scalar(0, 255, 0), calc_scale);
cv::line(fbMat(roi), cv::Point(scaledRightFilteredX, scaledY), cv::Point(scaledRightFilteredX, scaledY), cv::Scalar(255, 0, 255), calc_scale);
cv::line(fbMat(roi), cv::Point(scaledMidX, scaledY), cv::Point(scaledMidX, scaledY), cv::Scalar(255, 0, 0), calc_scale);
cv::line(fbMat(roi), cv::Point(scaledMidFilteredX, scaledY), cv::Point(scaledMidFilteredX, scaledY), cv::Scalar(0, 255, 255), calc_scale);
}
for (int y = 0; y < fbMat.rows; y++)
{
for (int x = 0; x < fbMat.cols; x++)
{
cv::Vec3b color = fbMat.at<cv::Vec3b>(y, x);
fb_data[y * vinfo.xres + x] = rgb888_to_rgb565(color);
}
}
}
void defaultImageCallback(const string &fullpath)
{
img = cv::imread(fullpath);
if (img.empty())
{
cerr << "Error: Cannot grab frame" << endl;
return;
}
raw_frame = img;
frame_begin = std::chrono::high_resolution_clock::now();
image_main();
frame_end = std::chrono::high_resolution_clock::now();
frame_duration = frame_end - frame_begin;
display();
}
void triggerCallback(int num)
{
if (!image_callback)
return;
auto it = file_map.find(num);
if (it != file_map.end())
{
string fullpath = base_path + "/" + it->second;
image_callback(fullpath);
}
}
int fbInit()
{
// Framebuffer 设备
fb_fd = open(fb_device, O_RDWR);
if (fb_fd == -1)
{
cerr << "Error: Cannot open framebuffer device" << endl;
return -1;
}
// 获取 Framebuffer 信息
if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo))
{
cerr << "Error: Cannot get framebuffer information" << endl;
close(fb_fd);
return -1;
}
// 检查 Framebuffer 是否支持 RGB565
if (vinfo.bits_per_pixel != 16)
{
cerr << "Error: Framebuffer is not RGB565 format" << endl;
close(fb_fd);
return -1;
}
// 映射 Framebuffer 到内存
size_t fb_size = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
fb_data = (ushort *)mmap(0, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);
if (fb_data == MAP_FAILED)
{
cerr << "Error: Failed to map framebuffer to memory" << endl;
close(fb_fd);
return -1;
}
return 0;
}
void disableRawMode()
{
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}
void enableRawMode()
{
tcgetattr(STDIN_FILENO, &orig_termios);
atexit(disableRawMode);
struct termios raw = orig_termios;
raw.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
bool collectImages(const char *path)
{
base_path = path;
DIR *dir = opendir(path);
if (!dir)
{
cerr << "无法打开目录: " << path << endl;
return false;
}
struct dirent *entry;
while ((entry = readdir(dir)) != nullptr)
{
string filename = entry->d_name;
if (filename.length() != 15)
continue;
if (filename.substr(0, 6) != "image_")
continue;
if (filename.substr(11, 4) != ".jpg")
continue;
string num_str = filename.substr(6, 5);
if (num_str.find_first_not_of("0123456789") != string::npos)
continue;
int num = stoi(num_str);
file_map[num] = filename;
}
closedir(dir);
return !file_map.empty();
}
void showHelp()
{
cout << "控制命令:\n"
<< " 左方向键 - 上一张图片\n"
<< " 右方向键 - 下一张图片\n"
<< " g - 跳转到指定序号\n"
<< " q - 退出程序\n";
}
void jumpToNumber()
{
disableRawMode();
cout << "\n输入要跳转的图片序号: ";
string input;
getline(cin, input);
tcflush(STDIN_FILENO, TCIFLUSH);
try
{
int target = stoi(input);
auto it = file_map.find(target);
if (it != file_map.end())
{
current_num = target;
triggerCallback(current_num);
}
else
{
cout << "未找到序号: " << target << endl;
}
}
catch (...)
{
cout << "无效输入" << endl;
}
enableRawMode();
}
int main(int argc, char *argv[])
{
if (fbInit())
{
cerr << "Framebuffer初始化失败" << endl;
return 1;
}
imgInit();
const char *path = argc > 1 ? argv[1] : ".";
if (!collectImages(path))
{
cerr << "未找到符合要求的图片文件" << endl;
return 1;
}
enableRawMode();
// 设置回调函数
image_callback = defaultImageCallback;
// 初始化时触发第一次回调
current_num = file_map.begin()->first;
triggerCallback(current_num);
showHelp();
while (true)
{
auto it = file_map.find(current_num);
if (it == file_map.end())
{
cerr << "当前文件不可用" << endl;
break;
}
cout << "\r当前图片: " << it->second << " (序号: " << current_num << ")" << "计算耗时:" << frame_duration.count() << "" << " 当前模式: " << displayMode << flush;
char c;
if (read(STDIN_FILENO, &c, 1) == 1)
{
if (c == '\033')
{ // ESC序列
char seq[2];
if (read(STDIN_FILENO, seq, 2) == 2)
{
if (seq[0] == '[')
{
auto next_it = file_map.upper_bound(current_num);
auto prev_it = file_map.lower_bound(current_num);
switch (seq[1])
{
case 'D': // 左方向键
if (prev_it != file_map.begin())
{
current_num = (--prev_it)->first;
triggerCallback(current_num);
}
break;
case 'C': // 右方向键
if (next_it != file_map.end())
{
current_num = next_it->first;
triggerCallback(current_num);
}
break;
}
}
}
}
else if (c == 'q')
{
break;
}
else if (c == 'g')
{
jumpToNumber();
}
else if (c == 't')
{
displayMode = (displayMode + 1) % displayModeCount;
display();
}
}
}
disableRawMode();
cout << "\n程序已退出" << endl;
return 0;
}
-5
View File
@@ -1,5 +0,0 @@
# demo1
add_executable(jy62_demo jy62_demo.cpp)
# 链接 OpenCV 库
target_link_libraries(jy62_demo)
-182
View File
@@ -1,182 +0,0 @@
/*
* @Author: Ilikara 3435193369@qq.com
* @Date: 2025-01-26 17:00:24
* @LastEditors: ilikara 3435193369@qq.com
* @LastEditTime: 2025-02-28 08:42:56
* @FilePath: /2k300_smartcar/jy62_demo/jy62_demo.cpp
* @Description:
*
* Copyright (c) 2025 by ${git_name_email}, All Rights Reserved.
*/
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <cstdint>
#include <termios.h>
#include <atomic>
const size_t PACKET_LENGTH = 11;
const uint8_t HEADER = 0x55;
std::atomic<int16_t> accel[3], gyro[3], angle[3];
uint8_t calculateChecksum(const std::vector<uint8_t> &data)
{
uint8_t checksum = 0;
for (uint8_t byte : data)
{
checksum += byte;
}
return checksum;
}
bool parsePacket(const std::vector<uint8_t> &packet)
{
if (packet[0] != HEADER)
{
return false;
}
if (packet[1] > 0x53 || packet[1] < 0x51)
{
return false;
}
uint8_t receivedChecksum = packet.back();
uint8_t calculatedChecksum = calculateChecksum(std::vector<uint8_t>(packet.begin(), packet.end() - 1));
return receivedChecksum == calculatedChecksum;
}
bool setupSerialPort(int fd, int baudRate)
{
struct termios tty;
memset(&tty, 0, sizeof tty);
// 获取当前串口配置
if (tcgetattr(fd, &tty) != 0)
{
std::cerr << "Error getting terminal attributes" << std::endl;
return false;
}
// 设置输入输出波特率
cfsetospeed(&tty, baudRate);
cfsetispeed(&tty, baudRate);
// 设置数据位为 8 位
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;
// 禁用奇偶校验
tty.c_cflag &= ~PARENB;
// 设置停止位为 1 位
tty.c_cflag &= ~CSTOPB;
// 禁用硬件流控制
tty.c_cflag &= ~CRTSCTS;
// 启用接收和本地模式
tty.c_cflag |= CREAD | CLOCAL;
// 禁用软件流控制
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
// 设置原始模式(禁用规范模式)
tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// 禁用输出处理
tty.c_oflag &= ~OPOST;
// 设置读取超时和最小读取字符数
tty.c_cc[VMIN] = 1; // 至少读取 1 个字符
tty.c_cc[VTIME] = 5; // 等待 0.5 秒
// 应用配置
if (tcsetattr(fd, TCSANOW, &tty) != 0)
{
std::cerr << "Error setting terminal attributes" << std::endl;
return false;
}
return true;
}
void data_handler(const std::vector<uint8_t> &packet)
{
switch (packet[1])
{
case 0x51:
accel[0].store((int16_t)packet[3] << 8 | packet[2]);
accel[1].store((int16_t)packet[5] << 8 | packet[4]);
accel[2].store((int16_t)packet[7] << 8 | packet[6]);
break;
case 0x52:
gyro[0].store((int16_t)packet[3] << 8 | packet[2]);
gyro[1].store((int16_t)packet[5] << 8 | packet[4]);
gyro[2].store((int16_t)packet[7] << 8 | packet[6]);
break;
case 0x53:
angle[0].store((int16_t)packet[3] << 8 | packet[2]);
angle[1].store((int16_t)packet[5] << 8 | packet[4]);
angle[2].store((int16_t)packet[7] << 8 | packet[6]);
break;
}
}
int main()
{
const char *device = "/dev/ttyS2";
int fd = open(device, O_RDWR | O_NOCTTY);
if (fd < 0)
{
std::cerr << "Failed to open " << device << std::endl;
return 1;
}
if (!setupSerialPort(fd, B115200))
{
std::cerr << "Failed to setup serial port" << std::endl;
close(fd);
return 1;
}
std::vector<uint8_t> buffer;
while (true)
{
uint8_t byte;
ssize_t n = read(fd, &byte, 1);
if (n > 0)
{
buffer.push_back(byte);
if (buffer.size() >= PACKET_LENGTH)
{
if (parsePacket(buffer))
{
data_handler(buffer);
buffer.erase(buffer.begin(), buffer.begin() + PACKET_LENGTH);
std::cout << "accel:" << accel[0] << " " << accel[1] << " " << accel[2] << std::endl;
std::cout << "gyro:" << gyro[0] << " " << gyro[1] << " " << gyro[2] << std::endl;
std::cout << "angle:" << angle[0] << " " << angle[1] << " " << angle[2] << std::endl;
}
else
{
std::cerr << "Invalid packet, discarding the first byte." << std::endl;
buffer.erase(buffer.begin());
}
}
}
else if (n < 0)
{
std::cerr << "Error reading from UART." << std::endl;
break;
}
}
close(fd);
return 0;
}
-5
View File
@@ -1,5 +0,0 @@
# demo1
add_executable(key_demo key_demo.cpp)
# 链接库
target_link_libraries(key_demo common_lib)
-68
View File
@@ -1,68 +0,0 @@
/*
* @Author: Ilikara 3435193369@qq.com
* @Date: 2025-01-26 17:00:24
* @LastEditors: ilikara 3435193369@qq.com
* @LastEditTime: 2025-03-10 14:23:16
* @FilePath: /2k300_smartcar/jy62_demo/jy62_demo.cpp
* @Description:
*
* Copyright (c) 2025 by ${git_name_email}, All Rights Reserved.
*/
#include <iostream>
#include <fstream>
#include <vector>
#include <unistd.h>
#include "GPIO.h"
using namespace std;
GPIO keys[9] = {
GPIO(0),
GPIO(3),
GPIO(1),
GPIO(2),
GPIO(27),
GPIO(17),
GPIO(16),
GPIO(15),
GPIO(14)};
string keysName[9] = {
"Down",
"Left",
"Up",
"OK",
"Right",
"SW1",
"SW2",
"SW3",
"SW4"};
void draw_interface()
{
cout << "\033[H\033[J";
for (size_t i = 0; i < 9; ++i)
{
string status = keys[i].readValue() ? "HIGH" : "LOW";
printf("│ GPIO %-7s │ %-9s │\n", keysName[i].c_str(), status.c_str());
}
}
int main()
{
for (size_t i = 0; i < 9; ++i)
{
keys[i].setDirection("in");
}
while (true)
{
draw_interface();
usleep(100000); // 100ms刷新间隔
cout << "\033[13A"; // 将光标移动到界面起始位置
}
return 0;
}
+22
View File
@@ -20,6 +20,17 @@ const std::string steer_gain_file = "./steer_gain";
const std::string center_bias_file = "./center_bias";
const std::string debug_file = "./debug";
const std::string cone_avoid_gain_file = "./cone_avoid_gain";
const std::string cone_avoid_range_file = "./cone_avoid_range";
const std::string cone_speed_file = "./cone_speed";
const std::string cone_min_frames_file = "./cone_min_frames";
const std::string cone_margin_file = "./cone_margin";
const std::string cone_thresh_file = "./cone_thresh";
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";
double readDoubleFromFile(const std::string &filename);
bool readFlag(const std::string &filename);
void cfg_load_all();
@@ -35,6 +46,17 @@ struct CfgCache {
int start = 0; // 1=启动 0=停止
int showImg = 0; // LCD 预览开关
int debug = 0; // debug 模式
double cone_avoid_gain = 0.3; // 锥桶中线变形推离量 (归一化)
int cone_avoid_range = 30; // 变形斜坡陡峭度
double cone_speed = 0.5; // 锥桶触发时速度倍率
int cone_min_frames = 3; // 连续确认帧数
int cone_margin = 0; // 0=中心点 1=框边缘
double cone_thresh = 0.80; // 锥桶置信度阈值
int cone_hold_frames = 30; // 锥桶消失后保持变形的帧数
double brake_scale = 10; // 速度(pps) → 刹车占空比(ns) 缩放系数
int brake_max = 10000; // 最大刹车占空比 (ns)
};
extern CfgCache g_cfg;
+7
View File
@@ -26,6 +26,13 @@ int main(void)
cfg_load_all();
double avoid_gain_val = readDoubleFromFile(cone_avoid_gain_file);
int avoid_range_val = (int)readDoubleFromFile(cone_avoid_range_file);
int hold_frames_val = (int)readDoubleFromFile(cone_hold_frames_file);
if (avoid_gain_val > 0) g_cfg.cone_avoid_gain = avoid_gain_val;
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;
if (CameraInit(0, dest_fps, 320, 240) < 0) {
std::cerr << "CameraInit failed" << std::endl;
return -1;
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
-5
View File
@@ -1,5 +0,0 @@
# demo3
add_executable(opencv_demo1 opencv_demo1.cpp)
# 链接 OpenCV 库
target_link_libraries(opencv_demo1 ${OpenCV_LIBS})
-158
View File
@@ -1,158 +0,0 @@
/*
* @Author: ilikara 3435193369@qq.com
* @Date: 2025-01-07 06:55:37
* @LastEditors: ilikara 3435193369@qq.com
* @LastEditTime: 2025-01-07 08:10:54
* @FilePath: /smartcar/opencv_demo1/opencv_demo1.cpp
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
#include <opencv2/opencv.hpp>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <chrono>
#include <iomanip>
#include <sstream>
using namespace cv;
using namespace std;
// 将 RGB888 转换为 RGB565
ushort rgb888_to_rgb565(const Vec3b &color)
{
return ((color[2] >> 3) << 11) | ((color[1] >> 2) << 5) | (color[0] >> 3);
}
// 获取当前时间字符串
string getCurrentTime()
{
auto now = chrono::system_clock::now();
auto now_time_t = chrono::system_clock::to_time_t(now);
auto now_tm = *localtime(&now_time_t);
stringstream ss;
ss << put_time(&now_tm, "%Y-%m-%d %H:%M:%S");
return ss.str();
}
int main()
{
// 打开 USB 摄像头
VideoCapture cap(0); // 0 表示默认摄像头
if (!cap.isOpened())
{
cerr << "Error: Cannot open camera" << endl;
return -1;
}
// 设置摄像头分辨率(可选)
cap.set(CAP_PROP_FRAME_WIDTH, 320);
cap.set(CAP_PROP_FRAME_HEIGHT, 240);
// Framebuffer 设备
const char *fb_device = "/dev/fb0";
int fb_fd = open(fb_device, O_RDWR);
if (fb_fd == -1)
{
cerr << "Error: Cannot open framebuffer device" << endl;
return -1;
}
// 获取 Framebuffer 信息
struct fb_var_screeninfo vinfo;
if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo))
{
cerr << "Error: Cannot get framebuffer information" << endl;
close(fb_fd);
return -1;
}
// 检查 Framebuffer 是否支持 RGB565
if (vinfo.bits_per_pixel != 16)
{
cerr << "Error: Framebuffer is not RGB565 format" << endl;
close(fb_fd);
return -1;
}
// 映射 Framebuffer 到内存
size_t fb_size = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
ushort *fb_data = (ushort *)mmap(0, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);
if (fb_data == MAP_FAILED)
{
cerr << "Error: Failed to map framebuffer to memory" << endl;
close(fb_fd);
return -1;
}
// 主循环
Mat frame, resizedFrame;
int frameCount = 0;
while (true)
{
// 从摄像头捕获一帧图像
cap >> frame;
if (frame.empty())
{
cerr << "Error: Captured frame is empty" << endl;
break;
}
// 调整图像大小为 160x128
resize(frame, resizedFrame, Size(160, 128));
// 获取当前时间和帧数
string timeStr = getCurrentTime();
string frameStr = "Frame: " + to_string(frameCount);
// 在图像左上角绘制黑色边框的白色文字
int fontFace = FONT_HERSHEY_SIMPLEX;
double fontScale = 0.4;
int thickness = 1;
int baseline = 0;
// 计算文字位置
Point timePos(5, 15); // 时间文字位置
Point framePos(5, 30); // 帧数文字位置
// 绘制黑色边框
putText(resizedFrame, timeStr, timePos + Point(-1, -1), fontFace, fontScale, Scalar(0, 0, 0), thickness + 1);
putText(resizedFrame, timeStr, timePos + Point(1, -1), fontFace, fontScale, Scalar(0, 0, 0), thickness + 1);
putText(resizedFrame, timeStr, timePos + Point(-1, 1), fontFace, fontScale, Scalar(0, 0, 0), thickness + 1);
putText(resizedFrame, timeStr, timePos + Point(1, 1), fontFace, fontScale, Scalar(0, 0, 0), thickness + 1);
putText(resizedFrame, frameStr, framePos + Point(-1, -1), fontFace, fontScale, Scalar(0, 0, 0), thickness + 1);
putText(resizedFrame, frameStr, framePos + Point(1, -1), fontFace, fontScale, Scalar(0, 0, 0), thickness + 1);
putText(resizedFrame, frameStr, framePos + Point(-1, 1), fontFace, fontScale, Scalar(0, 0, 0), thickness + 1);
putText(resizedFrame, frameStr, framePos + Point(1, 1), fontFace, fontScale, Scalar(0, 0, 0), thickness + 1);
// 绘制白色文字
putText(resizedFrame, timeStr, timePos, fontFace, fontScale, Scalar(255, 255, 255), thickness);
putText(resizedFrame, frameStr, framePos, fontFace, fontScale, Scalar(255, 255, 255), thickness);
// 将图像数据写入 Framebuffer
for (int y = 0; y < resizedFrame.rows; y++)
{
for (int x = 0; x < resizedFrame.cols; x++)
{
Vec3b color = resizedFrame.at<Vec3b>(y, x);
fb_data[y * vinfo.xres + x] = rgb888_to_rgb565(color);
}
}
// 更新帧数
frameCount++;
// 等待一段时间(例如 30ms
usleep(30000);
}
// 释放资源
munmap(fb_data, fb_size);
close(fb_fd);
cap.release();
return 0;
}
-5
View File
@@ -1,5 +0,0 @@
# demo2
add_executable(opencv_demo2 opencv_demo2.cpp)
# 链接 OpenCV 库
target_link_libraries(opencv_demo2 ${OpenCV_LIBS})
-222
View File
@@ -1,222 +0,0 @@
/*
* @Author: ilikara 3435193369@qq.com
* @Date: 2025-01-07 06:26:01
* @LastEditors: Ilikara 3435193369@qq.com
* @LastEditTime: 2025-01-17 15:41:43
* @FilePath: /2k300_smartcar/opencv_demo2/opencv_demo2.cpp
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
#include <opencv2/opencv.hpp>
#include <cmath>
#include <vector>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <algorithm> // for sort
using namespace cv;
using namespace std;
// 定义立方体的顶点
vector<Point3f> vertices = {
{-1, -1, -1},
{1, -1, -1},
{1, 1, -1},
{-1, 1, -1},
{-1, -1, 1},
{1, -1, 1},
{1, 1, 1},
{-1, 1, 1}};
// 定义立方体的面
vector<vector<int>> faces = {
{0, 1, 2, 3}, // 前面
{4, 5, 6, 7}, // 后面
{0, 1, 5, 4}, // 底面
{2, 3, 7, 6}, // 顶面
{0, 3, 7, 4}, // 左面
{1, 2, 6, 5} // 右面
};
// 定义每个面的颜色
vector<Scalar> faceColors = {
Scalar(255, 0, 0), // 红色
Scalar(0, 255, 0), // 绿色
Scalar(0, 0, 255), // 蓝色
Scalar(255, 255, 0), // 黄色
Scalar(255, 0, 255), // 紫色
Scalar(0, 255, 255) // 青色
};
// 将3D点投影到2D平面(透视投影)
Point2f projectPoint(Point3f point, Mat rotationMatrix, float scale, Point2f offset, float fov)
{
Mat pointMat = (Mat_<float>(3, 1) << point.x, point.y, point.z);
Mat rotatedPoint = rotationMatrix * pointMat;
// 透视投影
float z = rotatedPoint.at<float>(2, 0);
float x = rotatedPoint.at<float>(0, 0) / (z / fov + 1) * scale + offset.x;
float y = rotatedPoint.at<float>(1, 0) / (z / fov + 1) * scale + offset.y;
return Point2f(x, y);
}
// 将 RGB888 转换为 RGB565
ushort rgb888_to_rgb565(const Vec3b &color)
{
return ((color[2] >> 3) << 11) | ((color[1] >> 2) << 5) | (color[0] >> 3);
}
// 计算面的中心深度
float calculateFaceDepth(const vector<Point3f> &faceVertices, Mat rotationMatrix)
{
float depth = 0;
for (const auto &vertex : faceVertices)
{
Mat pointMat = (Mat_<float>(3, 1) << vertex.x, vertex.y, vertex.z);
Mat rotatedPoint = rotationMatrix * pointMat;
depth += rotatedPoint.at<float>(2, 0); // Z 轴深度
}
return depth / faceVertices.size(); // 返回平均深度
}
int main()
{
// Framebuffer 设备
const char *fb_device = "/dev/fb0";
int fb_fd = open(fb_device, O_RDWR);
if (fb_fd == -1)
{
cerr << "Error: Cannot open framebuffer device" << endl;
return -1;
}
// 获取 Framebuffer 信息
struct fb_var_screeninfo vinfo;
if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo))
{
cerr << "Error: Cannot get framebuffer information" << endl;
close(fb_fd);
return -1;
}
// 检查 Framebuffer 是否支持 RGB565
if (vinfo.bits_per_pixel != 16)
{
cerr << "Error: Framebuffer is not RGB565 format" << endl;
close(fb_fd);
return -1;
}
// 映射 Framebuffer 到内存
size_t fb_size = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
ushort *fb_data = (ushort *)mmap(0, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);
if (fb_data == MAP_FAILED)
{
cerr << "Error: Failed to map framebuffer to memory" << endl;
close(fb_fd);
return -1;
}
// 创建 OpenCV 图像(160x128RGB888
Mat image(128, 160, CV_8UC3, Scalar(0, 0, 0));
float angle = 0;
float scale = 50; // 缩放因子
Point2f offset(80, 64); // 图像中心点
float fov = 500; // 透视投影的视野
while (true)
{
// 清空图像
image.setTo(Scalar(0, 0, 0));
// 计算旋转矩阵
Mat rotationMatrixX = (Mat_<float>(3, 3) << 1, 0, 0,
0, cos(angle), -sin(angle),
0, sin(angle), cos(angle));
Mat rotationMatrixY = (Mat_<float>(3, 3) << cos(angle), 0, sin(angle),
0, 1, 0,
-sin(angle), 0, cos(angle));
Mat rotationMatrixZ = (Mat_<float>(3, 3) << cos(angle), -sin(angle), 0,
sin(angle), cos(angle), 0,
0, 0, 1);
Mat rotationMatrix = rotationMatrixZ * rotationMatrixY * rotationMatrixX;
// 计算每个面的深度并排序
vector<pair<float, int>> faceDepths; // {深度, 面索引}
for (size_t i = 0; i < faces.size(); i++)
{
vector<Point3f> faceVertices;
for (int vertexIndex : faces[i])
{
faceVertices.push_back(vertices[vertexIndex]);
}
float depth = calculateFaceDepth(faceVertices, rotationMatrix);
faceDepths.push_back({depth, i});
}
// 按深度从远到近排序
sort(faceDepths.begin(), faceDepths.end(), [](const pair<float, int> &a, const pair<float, int> &b)
{
return a.first > b.first; // 深度大的先绘制
});
// 按排序后的顺序绘制面
for (const auto &faceDepth : faceDepths)
{
int faceIndex = faceDepth.second;
vector<Point2f> facePointsFloat;
for (int vertexIndex : faces[faceIndex])
{
Point3f vertex = vertices[vertexIndex];
Point2f projectedPoint = projectPoint(vertex, rotationMatrix, scale, offset, fov);
facePointsFloat.push_back(projectedPoint);
}
// 将 Point2f 转换为 PointCV_32S 类型)
vector<Point> facePoints;
for (const auto &pt : facePointsFloat)
{
facePoints.push_back(Point(cvRound(pt.x), cvRound(pt.y)));
}
// 填充面
fillConvexPoly(image, facePoints, faceColors[faceIndex]);
// 绘制边
for (size_t j = 0; j < facePoints.size(); j++)
{
line(image, facePoints[j], facePoints[(j + 1) % facePoints.size()], Scalar(0, 0, 0), 2);
}
}
// 将 OpenCV 图像(RGB888)转换为 RGB565 并写入 Framebuffer
for (int y = 0; y < image.rows; y++)
{
for (int x = 0; x < image.cols; x++)
{
Vec3b color = image.at<Vec3b>(y, x);
fb_data[y * vinfo.xres + x] = rgb888_to_rgb565(color);
}
}
// 更新角度
angle += 0.02;
// 等待一段时间
usleep(1000000 / 50);
}
// 释放资源
munmap(fb_data, fb_size);
close(fb_fd);
return 0;
}
-5
View File
@@ -1,5 +0,0 @@
# demo2
add_executable(opencv_demo3 opencv_demo3.cpp)
# 链接 OpenCV 库
target_link_libraries(opencv_demo3 ${OpenCV_LIBS})
-141
View File
@@ -1,141 +0,0 @@
/*
* @Author: ilikara 3435193369@qq.com
* @Date: 2025-01-07 06:26:01
* @LastEditors: ilikara 3435193369@qq.com
* @LastEditTime: 2025-01-07 08:10:45
* @FilePath: /smartcar/opencv_demo2/opencv_demo2.cpp
* @Description: 使用Haar级联分类器从USB摄像头获取图像,并将图像居中显示到160x128的RGB565 framebuffer设备
*/
#include <opencv2/opencv.hpp>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
using namespace cv;
using namespace std;
// 将 RGB888 转换为 RGB565
ushort rgb888_to_rgb565(const Vec3b &color)
{
return ((color[2] >> 3) << 11) | ((color[1] >> 2) << 5) | (color[0] >> 3);
}
int main()
{
// Framebuffer 设备
const char *fb_device = "/dev/fb0";
int fb_fd = open(fb_device, O_RDWR);
if (fb_fd == -1)
{
cerr << "Error: Cannot open framebuffer device" << endl;
return -1;
}
// 获取 Framebuffer 信息
struct fb_var_screeninfo vinfo;
if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo))
{
cerr << "Error: Cannot get framebuffer information" << endl;
close(fb_fd);
return -1;
}
// 检查 Framebuffer 是否支持 RGB565
if (vinfo.bits_per_pixel != 16)
{
cerr << "Error: Framebuffer is not RGB565 format" << endl;
close(fb_fd);
return -1;
}
// 映射 Framebuffer 到内存
size_t fb_size = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
ushort *fb_data = (ushort *)mmap(0, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);
if (fb_data == MAP_FAILED)
{
cerr << "Error: Failed to map framebuffer to memory" << endl;
close(fb_fd);
return -1;
}
// 打开USB摄像头
VideoCapture cap(0); // 0表示第一个摄像头
if (!cap.isOpened())
{
cerr << "Error: Cannot open camera" << endl;
return -1;
}
// 设置摄像头参数
cap.set(CAP_PROP_FRAME_WIDTH, 640);
cap.set(CAP_PROP_FRAME_HEIGHT, 360);
cap.set(CAP_PROP_FOURCC, VideoWriter::fourcc('M', 'J', 'P', 'G'));
cap.set(CAP_PROP_AUTO_EXPOSURE, -1); // 启用自动曝光
// 加载Haar级联分类器
CascadeClassifier face_cascade;
if (!face_cascade.load("opencv/share/opencv4/haarcascades/haarcascade_frontalface_default.xml"))
{
cerr << "Error: Cannot load Haar cascade classifier" << endl;
return -1;
}
// 创建 OpenCV 图像(160x128RGB888
Mat image(128, 160, CV_8UC3, Scalar(0, 0, 0));
while (true)
{
Mat frame;
cap >> frame; // 从摄像头获取一帧图像
if (frame.empty())
{
cerr << "Error: Cannot grab frame from camera" << endl;
break;
}
// 缩放图像到160x90
Mat resized_frame;
resize(frame, resized_frame, Size(160, 90));
// 转换为灰度图像
Mat gray;
cvtColor(resized_frame, gray, COLOR_BGR2GRAY);
// 使用Haar级联分类器检测人脸
vector<Rect> faces;
face_cascade.detectMultiScale(gray, faces, 1.1, 2, 0 | CASCADE_SCALE_IMAGE, Size(30, 30));
// 在图像上绘制检测到的人脸
for (const auto &face : faces)
{
rectangle(resized_frame, face, Scalar(255, 0, 0), 2);
}
// 将图像居中显示到160x128的framebuffer
image.setTo(Scalar(0, 0, 0)); // 清空图像
int y_offset = (128 - 90) / 2;
resized_frame.copyTo(image(Rect(0, y_offset, 160, 90)));
// 将 OpenCV 图像(RGB888)转换为 RGB565 并写入 Framebuffer
for (int y = 0; y < image.rows; y++)
{
for (int x = 0; x < image.cols; x++)
{
Vec3b color = image.at<Vec3b>(y, x);
fb_data[y * vinfo.xres + x] = rgb888_to_rgb565(color);
}
}
// 等待一段时间
usleep(33333);
}
// 释放资源
munmap(fb_data, fb_size);
close(fb_fd);
cap.release();
return 0;
}
-2
View File
@@ -1,2 +0,0 @@
add_executable(screenshot_demo screenshot_demo.cpp)
target_link_libraries(screenshot_demo common_lib ${OpenCV_LIBS})
-111
View File
@@ -1,111 +0,0 @@
/*
* screenshot_demo — 摄像头截图保存
* ======================================
* 每次运行截一张图, 保存到 ./screenshot/image_N.jpg
* 用法:
* ./screenshot_demo 截一张图 + LCD 显示
* ./screenshot_demo --no-display 无 LCD
*/
#include <opencv2/opencv.hpp>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
using namespace cv;
using namespace std;
static uint16_t rgb565(uint8_t r, uint8_t g, uint8_t b)
{
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
int main(int argc, char *argv[])
{
bool no_display = false;
for (int i = 1; i < argc; ++i)
if (strcmp(argv[i], "--no-display") == 0)
no_display = true;
// ---- 创建目录 ----
mkdir("./screenshot", 0755);
// ---- 摄像头 ----
VideoCapture cap;
cap.open(0, CAP_V4L2);
if (!cap.isOpened()) cap.open(0);
if (!cap.isOpened()) { cerr << "无法打开摄像头" << endl; return 1; }
cap.set(CAP_PROP_FRAME_WIDTH, 640);
cap.set(CAP_PROP_FRAME_HEIGHT, 480);
cap.set(CAP_PROP_FOURCC, VideoWriter::fourcc('M', 'J', 'P', 'G'));
printf("摄像头: %dx%d\n",
(int)cap.get(CAP_PROP_FRAME_WIDTH),
(int)cap.get(CAP_PROP_FRAME_HEIGHT));
// ---- 丢弃前几帧 (曝光稳定) ----
Mat frame;
for (int i = 0; i < 10; ++i)
cap.read(frame);
usleep(100000);
// ---- 截图 ----
if (!cap.read(frame) || frame.empty())
{
cerr << "截图失败" << endl;
cap.release();
return 1;
}
// 自增文件名
int idx = 0;
char fname[64];
struct stat st;
do {
snprintf(fname, sizeof(fname),
"./screenshot/image_%05d.jpg", ++idx);
} while (stat(fname, &st) == 0);
imwrite(fname, frame);
printf("截图: %s (%dx%d)\n", fname, frame.cols, frame.rows);
// ---- LCD 显示 ----
if (!no_display)
{
int fb_fd = open("/dev/fb0", O_RDWR);
if (fb_fd >= 0)
{
struct fb_var_screeninfo vinfo;
ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo);
int scr_w = vinfo.xres, scr_h = vinfo.yres;
size_t fb_size = vinfo.yres_virtual * vinfo.xres_virtual * 2;
uint16_t *fb_buf = (uint16_t *)mmap(nullptr, fb_size,
PROT_READ|PROT_WRITE,
MAP_SHARED, fb_fd, 0);
Mat disp;
resize(frame, disp, Size(scr_w, scr_h));
rectangle(disp, Point(0, 0), Point(disp.cols, disp.rows),
Scalar(0, 255, 255), 3);
for (int y = 0; y < scr_h; ++y)
for (int x = 0; x < scr_w; ++x)
{
Vec3b px = disp.at<Vec3b>(y, x);
fb_buf[y * scr_w + x] = rgb565(px[2], px[1], px[0]);
}
sleep(1);
munmap(fb_buf, fb_size);
close(fb_fd);
}
}
cap.release();
return 0;
}
+171 -7
View File
@@ -22,7 +22,8 @@ PwmController servo(1, 0);
// ── 模型检测参数 ──
#define ZEBRA_CLASS 3
static float g_thresh[4] = {0.80f, 0.80f, 0.80f, 0.75f};
#define CONE_CLASS 0
static float g_thresh[4] = {0.90f, 0.75f, 0.80f, 0.90f};
// ── 斑马线去抖 ──
#define ZEBRA_MIN_FRAMES 5
@@ -36,6 +37,23 @@ static ZState g_zstate = Z_NORMAL;
static time_t g_ztime = 0;
static bool g_zebra_ever = false;
// ── 红绿灯状态机 ──
#define TL_RED_CLASS 1
#define TL_GREEN_CLASS 2
enum TLState { TL_NORMAL, TL_STOP, TL_WAIT_GREEN };
static TLState g_tl_state = TL_NORMAL;
static int g_tl_red_frames = 0;
static int g_tl_green_frames = 0;
// ── 锥桶检测 ──
static int g_cone_frames = 0;
static int g_cone_last_row = -1;
static int g_cone_last_col = -1;
static bool g_cone_confirmed = false;
static int g_cone_hold_ctr = 0;
static int g_cone_hold_src_row = -1;
static int g_cone_hold_src_col = -1;
// ── 模型检测结果缓存 ──
static DetectBoxV10 g_boxes[16];
static int g_box_count = 0;
@@ -110,10 +128,10 @@ int CameraInit(uint8_t camera_id, double dest_fps, int width, int height)
line_tracking_width = newWidth / calc_scale;
line_tracking_height = newHeight / calc_scale;
if (!model_v10_init("./nanodetv10.bin")) {
if (!model_v10_init("./mild_v12.bin")) {
printf("[MODEL] 警告: 模型加载失败\n");
} else {
printf("[MODEL] v10 模型已加载\n");
printf("[MODEL] Mild v12 模型已加载\n");
}
i2c_audio_open();
@@ -283,6 +301,138 @@ int CameraHandler(void)
break;
}
// ── 5.5 红绿灯状态机 ──
bool red_seen = false;
bool green_seen = false;
for (int i = 0; i < g_box_count; ++i) {
if (g_boxes[i].cls == TL_RED_CLASS) red_seen = true;
if (g_boxes[i].cls == TL_GREEN_CLASS) green_seen = true;
}
if (red_seen) g_tl_red_frames++;
else g_tl_red_frames = std::max(0, g_tl_red_frames - 1);
if (green_seen) g_tl_green_frames++;
else g_tl_green_frames = std::max(0, g_tl_green_frames - 1);
switch (g_tl_state) {
case TL_NORMAL:
if (g_tl_red_frames >= 3) {
g_tl_state = TL_STOP;
if (g_cfg.debug) printf("[TL] 红灯停车\n");
}
break;
case TL_STOP:
if (!red_seen) {
g_tl_state = TL_WAIT_GREEN;
if (g_cfg.debug) printf("[TL] 等待绿灯\n");
}
break;
case TL_WAIT_GREEN:
if (g_tl_green_frames >= 3) {
g_tl_state = TL_NORMAL;
g_tl_red_frames = 0;
g_tl_green_frames = 0;
if (g_cfg.debug) printf("[TL] 绿灯通行\n");
}
break;
}
bool tl_block = (g_tl_state != TL_NORMAL);
// ── 5.6 锥桶检测 + 中线变形 ──
bool cone_seen = false;
int cone_row_keep = -1;
int cone_col_keep = -1;
if (g_zstate != Z_STOP && g_tl_state == TL_NORMAL) {
for (int i = 0; i < g_box_count; ++i) {
if (g_boxes[i].cls != CONE_CLASS) continue;
if (g_boxes[i].conf < g_cfg.cone_thresh) continue;
int col_lt = g_boxes[i].cx * line_tracking_width / raw_frame.cols;
int row_lt = g_boxes[i].cy * line_tracking_height / raw_frame.rows;
if (row_lt < 10 || row_lt >= line_tracking_height) continue;
if (left_line[row_lt] == -1 || right_line[row_lt] == -1) continue;
if (g_cfg.cone_margin) {
int bl = (g_boxes[i].cx - g_boxes[i].w/2) * line_tracking_width / raw_frame.cols;
int br = (g_boxes[i].cx + g_boxes[i].w/2) * line_tracking_width / raw_frame.cols;
if (br < left_line[row_lt] || bl > right_line[row_lt]) continue;
} else {
if (col_lt < left_line[row_lt] || col_lt > right_line[row_lt]) continue;
}
if (row_lt > cone_row_keep) { cone_row_keep = row_lt; cone_col_keep = col_lt; }
cone_seen = true;
}
}
{
int row_tol = std::max(2, line_tracking_height / 12);
int col_tol = std::max(3, line_tracking_width / 8);
if (cone_seen) {
if (g_cone_frames == 0) {
g_cone_last_row = cone_row_keep;
g_cone_last_col = cone_col_keep;
g_cone_frames = 1;
} else {
if (std::abs(cone_row_keep - g_cone_last_row) <= row_tol &&
std::abs(cone_col_keep - g_cone_last_col) <= col_tol) {
g_cone_frames++;
g_cone_last_row = cone_row_keep;
g_cone_last_col = cone_col_keep;
} else {
g_cone_frames = 1;
g_cone_last_row = cone_row_keep;
g_cone_last_col = cone_col_keep;
}
}
} else {
if (g_cone_frames > 0) g_cone_frames = std::max(0, g_cone_frames - 1);
}
}
g_cone_confirmed = (g_cone_frames >= g_cfg.cone_min_frames);
if (g_cone_confirmed) {
g_cone_hold_ctr = 0;
g_cone_hold_src_row = cone_row_keep;
g_cone_hold_src_col = cone_col_keep;
} else if (g_cone_hold_src_row > 0 && g_cone_hold_ctr <= g_cfg.cone_hold_frames) {
g_cone_hold_ctr++;
}
bool cone_active = g_cone_confirmed ||
(g_cone_hold_ctr > 0 && g_cone_hold_ctr <= g_cfg.cone_hold_frames);
if (cone_active && g_zstate != Z_STOP && g_tl_state == TL_NORMAL) {
int src_row = g_cone_confirmed ? cone_row_keep : g_cone_hold_src_row;
int src_col = g_cone_confirmed ? cone_col_keep : g_cone_hold_src_col;
int start_row = 10;
if (src_row > start_row) {
double total_rows = src_row - start_row;
double rng = (double)g_cfg.cone_avoid_range;
double half_w = line_tracking_width / 2.0;
double mid_at_cone = mid_line[src_row];
double dir = (src_col < mid_at_cone) ? 1.0 : -1.0;
double decay = g_cone_confirmed
? 1.0
: (1.0 - (double)g_cone_hold_ctr / g_cfg.cone_hold_frames);
for (int row = start_row; row <= src_row; row++) {
double t_raw = (row - start_row) / total_rows;
double t = std::clamp(t_raw * (total_rows / rng), 0.0, 1.0);
double push = t * g_cfg.cone_avoid_gain * half_w * dir * decay;
double new_mid = mid_line[row] + push;
new_mid = std::clamp(new_mid,
(double)left_line[row] + 2.0,
(double)right_line[row] - 2.0);
mid_line[row] = (int)(new_mid + 0.5);
}
}
}
// ── 6. 舵机 ──
if (g_cfg.start) {
int foresee = (int)g_cfg.foresee;
@@ -304,7 +454,15 @@ int CameraHandler(void)
}
// ── 7. 电机控制 ──
ControlUpdate(target_speed, g_zstate == Z_STOP);
{
double spd = target_speed;
bool cone_slow = g_cone_confirmed ||
(g_cone_hold_ctr > 0 && g_cone_hold_ctr <= g_cfg.cone_hold_frames);
if (cone_slow && g_zstate != Z_STOP && g_tl_state == TL_NORMAL) {
spd *= g_cfg.cone_speed;
}
ControlUpdate(spd, g_zstate == Z_STOP || tl_block);
}
clock_gettime(CLOCK_MONOTONIC,&t4);
// ── 8. LCD ──
@@ -340,14 +498,20 @@ int CameraHandler(void)
x2=std::max(0,std::min(newWidth-1,x2)); y2=std::max(0,std::min(newHeight-1,y2));
cv::Scalar color(0,255,0);
if (g_boxes[i].cls == ZEBRA_CLASS) color=cv::Scalar(255,0,255);
else if (g_boxes[i].cls == CONE_CLASS) color=cv::Scalar(0,165,255);
cv::rectangle(lcd_fbImage(roi), cv::Point(x1,y1), cv::Point(x2,y2), color, 2);
char lab[16]; std::snprintf(lab,16,"%d %.0f",g_boxes[i].cls,g_boxes[i].conf*100);
cv::putText(lcd_fbImage(roi), lab, cv::Point(x1+2,y1+10), cv::FONT_HERSHEY_SIMPLEX,0.3,color,1);
}
const char* ztxt="N";
if (g_zstate==Z_STOP) ztxt="S";
else if (g_zstate==Z_COOLDOWN) ztxt="C";
char ztxt[8];
int zidx = 0;
if (g_tl_state == TL_STOP) ztxt[zidx++] = 'R';
else if (g_tl_state == TL_WAIT_GREEN) ztxt[zidx++] = 'G';
if (g_zstate == Z_STOP) ztxt[zidx++] = 'S';
else if (g_zstate == Z_COOLDOWN) ztxt[zidx++] = 'C';
else ztxt[zidx++] = 'N';
ztxt[zidx] = '\0';
cv::putText(lcd_fbImage(roi), ztxt, cv::Point(2,newHeight-4), cv::FONT_HERSHEY_SIMPLEX,0.4,cv::Scalar(0,255,255),1);
convertMatToRGB565(lcd_fbImage, fb_buffer, screenWidth, screenHeight);
+79 -6
View File
@@ -1,23 +1,70 @@
/*
* control — 电机控制层
*
* 开环占空比控制:根据目标速度和偏差计算占空比,直接输出到 PWM
* 编码器反馈,无 PID 闭环
* 开环占空比控制 + 斑马线编码器比例刹车
* 编码器线程: 高速轮询 gpio67 → 算速度(脉冲/秒) → 原子变量共享
*/
#include "control.h"
#include "global.h"
#include <thread>
#include <atomic>
#include <cmath>
extern double g_steer_deviation;
MotorController *motorController[2] = {nullptr, nullptr};
GPIO mortorEN(73);
static GPIO encoderLSB(67);
static GPIO encoderDIR(72);
static std::thread g_enc_thread;
static std::atomic<bool> g_enc_running{false};
static std::atomic<double> g_enc_speed{0.0};
static std::atomic<int> g_enc_dir{0};
static long enc_now_ns() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000000000L + ts.tv_nsec;
}
static void encoder_thread() {
int prev_lsb = encoderLSB.readValue() ? 1 : 0;
int pulse_cnt = 0;
long t0 = enc_now_ns();
while (g_enc_running.load()) {
int cur_lsb = encoderLSB.readValue() ? 1 : 0;
int cur_dir = encoderDIR.readValue() ? 1 : 0;
if (cur_lsb != prev_lsb) {
pulse_cnt++;
prev_lsb = cur_lsb;
}
g_enc_dir.store(cur_dir);
long t1 = enc_now_ns();
long dt = t1 - t0;
if (dt >= 100000000L) {
g_enc_speed.store(pulse_cnt / (dt / 1e9));
pulse_cnt = 0;
t0 = t1;
}
}
}
void ControlInit()
{
mortorEN.setDirection("out");
mortorEN.setValue(1);
// 左电机方向: GPIO12 (右电机方向由 MotorController 自管, 但左In2也一样要设)
encoderLSB.setDirection("in");
encoderDIR.setDirection("in");
g_enc_running.store(true);
g_enc_thread = std::thread(encoder_thread);
GPIO leftIn2(13);
leftIn2.setDirection("out");
leftIn2.setValue(1);
@@ -37,15 +84,38 @@ void ControlInit()
void ControlUpdate(double speed, bool zebra_block)
{
if (zebra_block || !g_cfg.start)
if (!g_cfg.start)
{
for (int i = 0; i < 2; ++i)
if (motorController[i]) motorController[i]->updateduty(0);
if (!g_cfg.start) mortorEN.setValue(0);
mortorEN.setValue(0);
return;
}
if (zebra_block)
{
double cur_speed = g_enc_speed.load();
int cur_dir = g_enc_dir.load();
if (cur_speed > 0.5)
{
int brake_ns = (int)(cur_speed * g_cfg.brake_scale);
if (brake_ns > g_cfg.brake_max) brake_ns = g_cfg.brake_max;
double brake_pct = brake_ns / 500.0; // ns → % (period=50000ns)
int dir = cur_dir ? 1 : 0;
for (int i = 0; i < 2; ++i)
if (motorController[i])
motorController[i]->updateduty(dir ? -brake_pct : brake_pct);
}
else
{
for (int i = 0; i < 2; ++i)
if (motorController[i]) motorController[i]->updateduty(0);
}
return;
}
// 弯道降速: 偏差越大,速度越低 (最低 60%)
double curve = 1.0 - std::abs(g_steer_deviation) * 0.4;
if (curve < 0.6) curve = 0.6;
double spd = speed * curve;
@@ -58,6 +128,9 @@ void ControlUpdate(double speed, bool zebra_block)
void ControlExit()
{
g_enc_running.store(false);
if (g_enc_thread.joinable()) g_enc_thread.join();
for (int i = 0; i < 2; ++i)
{
delete motorController[i];
+8
View File
@@ -31,4 +31,12 @@ void cfg_load_all()
g_cfg.start = readFlag(start_file);
g_cfg.showImg = readFlag(showImg_file);
g_cfg.debug = readFlag(debug_file);
g_cfg.cone_speed = readDoubleFromFile(cone_speed_file);
g_cfg.cone_min_frames = (int)readDoubleFromFile(cone_min_frames_file);
g_cfg.cone_margin = (int)readDoubleFromFile(cone_margin_file);
g_cfg.cone_thresh = readDoubleFromFile(cone_thresh_file);
g_cfg.brake_scale = readDoubleFromFile(brake_scale_file);
g_cfg.brake_max = readDoubleFromFile(brake_max_file);
}
+396 -496
View File
@@ -1,602 +1,502 @@
/*
* NanoDetHeat v10: 3.2M MACs, 4-class
* 优化: BN折叠 + conv_bn_relu融合 + 快速exp + 边界修正
* YOLO_Mega_Mild_r2 C++ inference engine v3
* Architecture: 3-9-9-13-13-19-19-19-44-44-64-64 -> head(64-64,dw) -> out(64-9)
*/
#include "model_v10.hpp"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <ctime>
#include <new>
#include <algorithm>
#include <unordered_map>
#include <string>
#include <vector>
// ============================================================
// 快速 exp — Schraudolph 方法 (IEEE 754 整数近似)
// ============================================================
static inline float fast_exp(float x) {
// clamp to avoid overflow: exp(88) fits in float
x = std::min(x, 88.0f);
x = std::max(x, -87.0f);
// exp(x) ~ 2^(x * log2(e))
// float 按位: 2^23 * (127 + x*log2(e))
union { float f; int i; } u;
u.i = (int)(12102203.0f * x) + 1064866805; // 2^23 * log2(e) = 12102203
return u.f;
}
static inline float fast_sigmoid(float x) {
return 1.0f / (1.0f + fast_exp(-x));
static inline float clamp_f(float v, float lo, float hi) { return v < lo ? lo : (v > hi ? hi : v); }
static inline float sigmoid_f(float x) {
x = clamp_f(x, -30.0f, 30.0f);
return 1.0f / (1.0f + std::exp(-x));
}
static inline float relu_maybe(float s, bool relu) { return (relu && s < 0.0f) ? 0.0f : s; }
// ============================================================
// 权重 (新增 BN alpha/beta 预计算)
// ============================================================
struct WT { char n[128]; int nd; int s[4]; float* d; };
static int gn=0; static WT* gw=nullptr;
static int gn = 0;
static WT* gw = nullptr;
static float* wf(const char* k) {
for(int i=0;i<gn;++i) if(!std::strcmp(gw[i].n,k)) return gw[i].d;
static WT* wt(const char* k) {
for (int i = 0; i < gn; ++i)
if (!std::strcmp(gw[i].n, k)) return &gw[i];
return nullptr;
}
static float* wf(const char* k) { WT* t = wt(k); return t ? t->d : nullptr; }
static void init_lut();
// 加载后扫描 BN 参数,预计算 alpha/beta 存入权重表
static void bn_precompute() {
// 扫描所有权重,找 BN 三元组 (weight/bias/running_mean/running_var)
std::unordered_map<std::string, int> base_idx;
for (int i=0; i<gn; ++i) {
std::unordered_map<std::string, int> bn_weight_idx;
for (int i = 0; i < gn; ++i) {
const char* name = gw[i].n;
// 匹配 xxx.weight (BN) 或 xxx.bias (BN) → base = "xxx"
const char* dot = std::strrchr(name, '.');
if (!dot) continue;
std::string base(name, dot - name);
int sz = gw[i].s[0];
if (std::strcmp(dot+1, "weight")==0 && sz>0 && sz <= 256) {
base_idx[base] = i;
}
if (!dot || std::strcmp(dot + 1, "weight") != 0) continue;
if (gw[i].nd != 1) continue;
bn_weight_idx[std::string(name, dot - name)] = i;
}
std::vector<WT> new_wts;
for (auto& [base, wi] : base_idx) {
// 找对应的 bias / running_mean / running_var
std::string w_key = base + ".weight";
std::string b_key = base + ".bias";
std::string m_key = base + ".running_mean";
std::string v_key = base + ".running_var";
float* w = wf(w_key.c_str());
float* b = wf(b_key.c_str());
float* m = wf(m_key.c_str());
float* v = wf(v_key.c_str());
if (!w || !m || !v) continue; // cls_head/size_head 没有 BN
int C = gw[wi].s[0];
for (auto& kv : bn_weight_idx) {
const std::string& base = kv.first;
float* w = gw[kv.second].d;
float* b = wf((base + ".bias").c_str());
float* m = wf((base + ".running_mean").c_str());
float* v = wf((base + ".running_var").c_str());
if (!m || !v) continue;
const int C = gw[kv.second].s[0];
float* alpha = new float[C];
float* beta = new float[C];
const float eps = 1e-5f;
for (int c=0; c<C; ++c) {
for (int c = 0; c < C; ++c) {
alpha[c] = w[c] / std::sqrt(v[c] + eps);
beta[c] = (b ? b[c] : 0.0f) - m[c] * alpha[c];
}
// 存为新的权重条目
new_wts.push_back({});
std::snprintf(new_wts.back().n, 128, "%s._alpha", base.c_str());
new_wts.back().nd=1; new_wts.back().s[0]=C; new_wts.back().s[1]=1; new_wts.back().s[2]=1; new_wts.back().s[3]=1;
new_wts.back().d = alpha;
new_wts.push_back({});
std::snprintf(new_wts.back().n, 128, "%s._beta", base.c_str());
new_wts.back().nd=1; new_wts.back().s[0]=C; new_wts.back().s[1]=1; new_wts.back().s[2]=1; new_wts.back().s[3]=1;
new_wts.back().d = beta;
WT ta = {}; std::snprintf(ta.n, 128, "%s._alpha", base.c_str());
ta.nd = 1; ta.s[0] = C; ta.s[1] = ta.s[2] = ta.s[3] = 1; ta.d = alpha;
WT tb = {}; std::snprintf(tb.n, 128, "%s._beta", base.c_str());
tb.nd = 1; tb.s[0] = C; tb.s[1] = tb.s[2] = tb.s[3] = 1; tb.d = beta;
new_wts.push_back(ta); new_wts.push_back(tb);
}
// 重新分配权重数组 (旧数组 + 新条目)
WT* old_gw = gw;
int old_gn = gn;
int new_gn = old_gn + (int)new_wts.size();
gw = new WT[new_gn];
std::memcpy(gw, old_gw, old_gn * sizeof(WT));
for (size_t i=0; i<new_wts.size(); ++i) {
gw[old_gn + i] = new_wts[i];
}
gn = new_gn;
delete[] old_gw;
init_lut();
WT* old = gw;
const int old_n = gn, new_n = old_n + (int)new_wts.size();
gw = new WT[new_n];
std::memcpy(gw, old, old_n * sizeof(WT));
for (size_t i = 0; i < new_wts.size(); ++i) gw[old_n + i] = new_wts[i];
gn = new_n;
delete[] old;
}
// ============================================================
// 内存
// ============================================================
struct M10 {
float preproc[3*120*160]; // 预处理 buffer (静态, 避免每帧 new/delete)
float stem[6*60*80];
float b1[8*60*80];
float b2[12*30*40];
float b3[16*15*20];
float b4[32*15*20];
float b5[48*15*20];
float b6[48*15*20];
float sh[24*15*20];
float cl[5*15*20];
float sz[2*15*20];
float dw_out[8*60*80];
float se_buf[80];
uint8 peak_mask[15*20];
};
static M10* m=nullptr;
static constexpr int IN_W = 160, IN_H = 120;
static constexpr int BUF_FLOATS = 9 * 60 * 80;
static float* g_preproc = nullptr;
static float* g_A = nullptr;
static float* g_B = nullptr;
static float* g_T = nullptr;
static float* g_out = nullptr;
static uint8_t g_resized[IN_W * IN_H * 3];
static float g_se[160];
static float g_prob[5 * 15 * 20];
static float g_lut[256];
static char g_dump_dir[256] = {0};
// ============================================================
// Relu
// ============================================================
static void relu_f(float* x, int N) {
for (int i=0; i<N; ++i) if (x[i]<0) x[i]=0;
static void dump_tensor(const char* name, const float* d, int count) {
if (!g_dump_dir[0]) return;
char path[320];
std::snprintf(path, sizeof(path), "%s/%s.f32", g_dump_dir, name);
FILE* f = std::fopen(path, "wb");
if (f) { std::fwrite(d, sizeof(float), count, f); std::fclose(f); }
}
// ============================================================
// 融合: conv + BN + ReLU (使用预计算的 alpha/beta)
// ============================================================
static void conv_bn_relu(float* o, const float* in,
const float* conv_w, const float* conv_b,
const float* bn_alpha, const float* bn_beta,
int H, int W, int iC, int oC, int K, int str, int grp, bool use_relu)
static void conv1x1(float* o, const float* in, const float* cw, const float* cb,
const float* ba, const float* bb,
int H, int W, int iC, int oC, bool relu)
{
if (!conv_w || !in || !o) return;
int Ho=H/str, Wo=W/str, pad=K/2;
int icpg=iC/grp, ocpg=oC/grp;
const int N = H * W;
for (int oc = 0; oc < oC; ++oc) {
float* oo = o + oc * N;
const float* ww = cw + oc * iC;
const float bias = cb ? cb[oc] : 0.0f;
for (int i = 0; i < N; ++i) oo[i] = bias;
for (int ic = 0; ic < iC; ++ic) {
const float w = ww[ic];
const float* ii = in + ic * N;
for (int i = 0; i < N; ++i) oo[i] += w * ii[i];
}
const float a = ba ? ba[oc] : 1.0f;
const float b = bb ? bb[oc] : 0.0f;
for (int i = 0; i < N; ++i)
oo[i] = relu_maybe(oo[i] * a + b, relu);
}
}
if (K==1 && str==1) {
// 1×1 fused: 指针递增避免 y*W+x
for (int g=0; g<grp; ++g) {
for (int oc=0; oc<ocpg; ++oc) {
int occ = g*ocpg + oc;
float* oo = o + occ*H*W;
float cb = conv_b ? conv_b[occ] : 0.0f;
const float* ww = conv_w + occ*icpg;
float ba=bn_alpha?bn_alpha[occ]:1.0f, bb=bn_beta?bn_beta[occ]:0.0f;
static void dwconv3x3(float* o, const float* in, const float* cw,
const float* ba, const float* bb,
int H, int W, int C, bool relu)
{
for (int c = 0; c < C; ++c) {
const float* ii = in + c * H * W;
float* oo = o + c * H * W;
const float* ww = cw + c * 9;
const float w0=ww[0],w1=ww[1],w2=ww[2],w3=ww[3],w4=ww[4],
w5=ww[5],w6=ww[6],w7=ww[7],w8=ww[8];
const float a = ba ? ba[c] : 1.0f;
const float b = bb ? bb[c] : 0.0f;
for (int y=0; y<H; ++y) {
float* oy = oo + y*W;
const float* iy = in + y*W;
for (int x=0; x<W; ++x) {
float s = cb;
const float* px = iy + x;
for (int ic=0; ic<icpg; ++ic) {
int icc = g*icpg + ic;
s += ww[ic] * px[icc*H*W];
}
s = s*ba + bb;
if (use_relu && s<0) s = 0;
oy[x] = s;
}
}
for (int y = 1; y < H - 1; ++y) {
const float *i0 = ii + (y - 1) * W, *i1 = i0 + W, *i2 = i1 + W;
float* oy = oo + y * W;
for (int x = 1; x < W - 1; ++x) {
float s = w0*i0[x-1] + w1*i0[x] + w2*i0[x+1]
+ w3*i1[x-1] + w4*i1[x] + w5*i1[x+1]
+ w6*i2[x-1] + w7*i2[x] + w8*i2[x+1];
oy[x] = relu_maybe(s * a + b, relu);
}
}
return;
}
if (grp==iC && K==3 && str==1) {
// 3×3 dw fused + interior unrolled / border with checks
for (int c=0; c<iC; ++c) {
float* const oo = o + c*H*W;
const float* const ii = in + c*H*W;
const float* const ww = conv_w + c*9;
float cb = conv_b ? conv_b[c] : 0.0f;
float ba=bn_alpha?bn_alpha[c]:1.0f, bb=bn_beta?bn_beta[c]:0.0f;
float w0=ww[0], w1=ww[1], w2=ww[2], w3=ww[3], w4=ww[4], w5=ww[5], w6=ww[6], w7=ww[7], w8=ww[8];
if (H>=3 && W>=3) {
// interior: 1≤y<H-1, 1≤x<W-1 (no boundary checks)
for (int y=1; y<H-1; ++y) {
float* const oy = oo + y*W + 1;
const float* const i0 = ii + (y-1)*W;
const float* const i1 = i0 + W;
const float* const i2 = i1 + W;
for (int x=1; x<W-1; ++x) {
float s = cb
+ w0*i0[x-1] + w1*i0[x] + w2*i0[x+1]
+ w3*i1[x-1] + w4*i1[x] + w5*i1[x+1]
+ w6*i2[x-1] + w7*i2[x] + w8*i2[x+1];
s = s*ba + bb;
if (use_relu && s<0) s = 0;
oy[x-1] = s;
}
}
// top row (y=0): boundary check — ky=-1 clamps to y=0
{
float* const oy = oo;
const float* const i0 = ii; // y=0 (ky=-1 and ky=0)
const float* const i1 = ii + W; // y=1 (ky=1)
for (int x=0; x<W; ++x) {
float s = cb;
// ky=-1: use i0 (clamped), ky=0: use i0, ky=1: use i1
int xl=(x>0)?x-1:x, xr=(x<W-1)?x+1:x;
s += w0*i0[xl] + w1*i0[x] + w2*i0[xr] // ky=-1
+ w3*i0[xl] + w4*i0[x] + w5*i0[xr] // ky=0
+ w6*i1[xl] + w7*i1[x] + w8*i1[xr]; // ky=1
s = s*ba + bb;
if (use_relu && s<0) s = 0;
oy[x] = s;
}
}
// bottom row (y=H-1): boundary check — ky=1 clamps to y=H-1
{
float* const oy = oo + (H-1)*W;
const float* const i0 = ii + (H-2)*W; // y=H-2 (ky=-1)
const float* const i1 = ii + (H-1)*W; // y=H-1 (ky=0 and ky=1)
for (int x=0; x<W; ++x) {
float s = cb;
int xl=(x>0)?x-1:x, xr=(x<W-1)?x+1:x;
s += w0*i0[xl] + w1*i0[x] + w2*i0[xr] // ky=-1
+ w3*i1[xl] + w4*i1[x] + w5*i1[xr] // ky=0
+ w6*i1[xl] + w7*i1[x] + w8*i1[xr]; // ky=1
s = s*ba + bb;
if (use_relu && s<0) s = 0;
oy[x] = s;
}
}
// middle rows left border (1≤y<H-1, x=0)
for (int y=1; y<H-1; ++y) {
const float* const i0 = ii + (y-1)*W;
const float* const i1 = i0 + W;
const float* const i2 = i1 + W;
float s = cb
+ w1*i0[0] + w2*i0[1]
+ w4*i1[0] + w5*i1[1]
+ w7*i2[0] + w8*i2[1];
s = s*ba + bb;
if (use_relu && s<0) s = 0;
oo[y*W] = s;
}
// middle rows right border (1≤y<H-1, x=W-1)
for (int y=1; y<H-1; ++y) {
const float* const i0 = ii + (y-1)*W + (W-2);
const float* const i1 = i0 + W;
const float* const i2 = i1 + W;
float s = cb
+ w0*i0[0] + w1*i0[1]
+ w3*i1[0] + w4*i1[1]
+ w6*i2[0] + w7*i2[1];
s = s*ba + bb;
if (use_relu && s<0) s = 0;
oo[y*W+W-1] = s;
}
} else {
// degenerate (small H/W): fallback
for (int y=0; y<H; ++y) {
for (int x=0; x<W; ++x) {
float s = cb;
for (int ky=0; ky<3; ++ky) {
int iy = y + ky - 1;
if (iy<0||iy>=H) continue;
for (int kx=0; kx<3; ++kx) {
int ix = x + kx - 1;
if (ix<0||ix>=W) continue;
s += ww[ky*3+kx] * ii[iy*W+ix];
}
}
s = s*ba + bb;
if (use_relu && s<0) s = 0;
oo[y*W+x] = s;
}
auto edge = [&](int y, int x) {
float s = 0.0f;
for (int ky = 0; ky < 3; ++ky) {
const int iy = y + ky - 1;
if (iy < 0 || iy >= H) continue;
for (int kx = 0; kx < 3; ++kx) {
const int ix = x + kx - 1;
if (ix < 0 || ix >= W) continue;
s += ww[ky * 3 + kx] * ii[iy * W + ix];
}
}
}
return;
oo[y * W + x] = relu_maybe(s * a + b, relu);
};
for (int x = 0; x < W; ++x) { edge(0, x); edge(H - 1, x); }
for (int y = 1; y < H - 1; ++y) { edge(y, 0); edge(y, W - 1); }
}
}
// 通用路径 — 常数外提 + iy*W 预计算
for (int g=0; g<grp; ++g) {
int ic_base = g*icpg;
for (int oc=0; oc<ocpg; ++oc) {
int occ = g*ocpg + oc;
float* oo = o + occ*Ho*Wo;
float cb = conv_b ? conv_b[occ] : 0.0f;
float ba=bn_alpha?bn_alpha[occ]:1.0f, bb=bn_beta?bn_beta[occ]:0.0f;
for (int y=0; y<Ho; ++y) {
int yi0 = y*str;
for (int x=0; x<Wo; ++x) {
float s = cb;
int xi0 = x*str;
for (int ic=0; ic<icpg; ++ic) {
int icc = ic_base + ic;
const float* ww = conv_w + ((occ*icpg+ic)*K*K);
const float* ii = in + icc*H*W;
for (int ky=0; ky<K; ++ky) {
int iy = yi0 + ky - pad;
if (iy<0||iy>=H) continue;
int iy_off = iy*W;
for (int kx=0; kx<K; ++kx) {
int ix = xi0 + kx - pad;
if (ix<0||ix>=W) continue;
s += ww[ky*K+kx] * ii[iy_off + ix];
}
static void conv_generic(float* o, const float* in, const float* cw, const float* cb,
const float* ba, const float* bb,
int H, int W, int iC, int oC, int K, int str, int grp, bool relu)
{
const int Ho = H / str, Wo = W / str, pad = K / 2;
const int icpg = iC / grp, ocpg = oC / grp;
for (int g = 0; g < grp; ++g) {
const int ic_base = g * icpg;
for (int oc = 0; oc < ocpg; ++oc) {
const int occ = g * ocpg + oc;
float* oo = o + occ * Ho * Wo;
const float bias = cb ? cb[occ] : 0.0f;
const float a = ba ? ba[occ] : 1.0f;
const float b = bb ? bb[occ] : 0.0f;
for (int y = 0; y < Ho; ++y) {
const int yi0 = y * str - pad;
const int ky0 = std::max(0, -yi0);
const int ky1 = std::min(K, H - yi0);
for (int x = 0; x < Wo; ++x) {
const int xi0 = x * str - pad;
const int kx0 = std::max(0, -xi0);
const int kx1 = std::min(K, W - xi0);
float s = bias;
for (int ic = 0; ic < icpg; ++ic) {
const float* ww = cw + ((occ * icpg + ic) * K * K);
const float* ii = in + (ic_base + ic) * H * W;
for (int ky = ky0; ky < ky1; ++ky) {
const float* irow = ii + (yi0 + ky) * W + xi0;
const float* wrow = ww + ky * K;
for (int kx = kx0; kx < kx1; ++kx)
s += wrow[kx] * irow[kx];
}
}
s = s*ba + bb;
if (use_relu && s<0) s = 0;
oo[y*Wo+x] = s;
oo[y * Wo + x] = relu_maybe(s * a + b, relu);
}
}
}
}
}
// ============================================================
// 纯 conv (无 BN/ReLU, cls_head / size_head / skip 的 conv 部分)
// ============================================================
static void conv2d(float* o, const float* in, const float* w, const float* b,
int H, int W, int iC, int oC, int K, int str, int grp) {
conv_bn_relu(o, in, w, b, nullptr, nullptr, H, W, iC, oC, K, str, grp, false);
static void conv_bn_relu(float* o, const float* in,
const float* cw, const float* cb,
const float* ba, const float* bb,
int H, int W, int iC, int oC, int K, int str, int grp, bool relu)
{
if (!cw || !in || !o) return;
if (K == 1 && str == 1 && grp == 1)
conv1x1(o, in, cw, cb, ba, bb, H, W, iC, oC, relu);
else if (K == 3 && str == 1 && grp == iC && iC == oC)
dwconv3x3(o, in, cw, ba, bb, H, W, iC, relu);
else
conv_generic(o, in, cw, cb, ba, bb, H, W, iC, oC, K, str, grp, relu);
}
// ============================================================
// SE 通道注意力 (使用快速 sigmoid)
// ============================================================
static void se_module(float* x, int C, int N,
const float* w1, const float* b1,
const float* w2, const float* b2,
float* buf) {
if (!w1||!w2) return;
int mid=C/4;
float* pool=buf;
const WT* w1t, const float* b1,
const WT* w2t, const float* b2)
{
if (!w1t || !w2t) return;
const float* w1 = w1t->d;
const float* w2 = w2t->d;
const int mid = w1t->s[0];
// AdaptiveAvgPool
for (int c=0; c<C; ++c) {
float s=0;
float* px = x+c*N, *end = px+N;
while (px < end) { s += *px++; }
pool[c]=s/(float)N;
float* pool = g_se;
float* fc1 = g_se + C;
float* fc2 = g_se + C + mid;
const float inv_n = 1.0f / (float)N;
for (int c = 0; c < C; ++c) {
float s = 0.0f;
const float* px = x + c * N;
for (int i = 0; i < N; ++i) s += px[i];
pool[c] = s * inv_n;
}
// fc1: C→mid + ReLU
float* fc1=buf+C;
for (int o=0; o<mid; ++o) {
float s=b1?b1[o]:0;
const float* ww=w1+o*C;
for (int ic=0; ic<C; ++ic) s+=pool[ic]*ww[ic];
fc1[o]=s>0?s:0;
for (int o = 0; o < mid; ++o) {
float s = b1 ? b1[o] : 0.0f;
const float* ww = w1 + o * C;
for (int ic = 0; ic < C; ++ic) s += pool[ic] * ww[ic];
fc1[o] = s > 0.0f ? s : 0.0f;
}
// fc2: mid→C + Sigmoid (fast)
float* fc2=buf+C+mid;
for (int o=0; o<C; ++o) {
float s=b2?b2[o]:0;
const float* ww=w2+o*mid;
for (int ic=0; ic<mid; ++ic) s+=fc1[ic]*ww[ic];
fc2[o]=fast_sigmoid(s);
for (int o = 0; o < C; ++o) {
float s = b2 ? b2[o] : 0.0f;
const float* ww = w2 + o * mid;
for (int ic = 0; ic < mid; ++ic) s += fc1[ic] * ww[ic];
fc2[o] = sigmoid_f(s);
}
for (int c=0; c<C; ++c) {
float* xc=x+c*N; float sc=fc2[c];
for (int i=0; i<N; ++i) xc[i]*=sc;
for (int c = 0; c < C; ++c) {
float* xc = x + c * N;
const float sc = fc2[c];
for (int i = 0; i < N; ++i) xc[i] *= sc;
}
}
// ============================================================
// SE-ResDW Block v10
// ============================================================
static void se_block_v10(float* o, const float* in, int iC, int oC, int H, int W, int str, const char* pfx) {
int Ho=H/str, Wo=W/str, No=Ho*Wo;
char na[128], wr[128], bi[128];
static constexpr int STRIDE = 8, OH = 15, OW = 20, NC = 4;
// dw conv + BN + ReLU (fused)
std::snprintf(na,128,"%s.dw.0.weight",pfx);
std::snprintf(wr,128,"%s.dw.1._alpha",pfx);
std::snprintf(bi,128,"%s.dw.1._beta",pfx);
conv_bn_relu(m->dw_out, in, wf(na), nullptr,
wf(wr), wf(bi), H, W, iC, iC, 3, str, iC, true);
static void model_compute() {
struct Lay { int iC, oC, H, W, s; const char* bn; const char* sn; };
static const Lay lays[] = {
{3, 9, 120, 160, 2, "b1", "se_0"},
{9, 9, 60, 80, 1, "b2", "se_1"},
{9, 13, 60, 80, 2, "b3", "se_2"},
{13, 13, 30, 40, 1, "b4", "se_3"},
{13, 19, 30, 40, 2, "b5", "se_4"},
{19, 19, 15, 20, 1, "b6", "se_5"},
{19, 19, 15, 20, 1, "b7", "se_6"},
{19, 44, 15, 20, 1, "b8", "se_7"},
{44, 44, 15, 20, 1, "b9", "se_8"},
{44, 64, 15, 20, 1, "b10", "se_9"},
{64, 64, 15, 20, 1, "b11", "se_10"},
};
// pw conv + BN + ReLU (fused)
std::snprintf(na,128,"%s.pw.0.weight",pfx);
std::snprintf(wr,128,"%s.pw.1._alpha",pfx);
std::snprintf(bi,128,"%s.pw.1._beta",pfx);
conv_bn_relu(o, m->dw_out, wf(na), nullptr,
wf(wr), wf(bi), Ho, Wo, iC, oC, 1, 1, 1, true);
const float* in = g_preproc;
float* bufs[2] = { g_A, g_B };
int cur = 0;
// skip (BN fused, no ReLU)
bool iden=(str==1 && iC==oC);
float* skip=m->dw_out;
if (iden) {
std::memcpy(skip, in, iC*H*W*4);
} else {
std::snprintf(na,128,"%s.sk.0.weight",pfx);
std::snprintf(wr,128,"%s.sk.1._alpha",pfx);
std::snprintf(bi,128,"%s.sk.1._beta",pfx);
conv_bn_relu(skip, in, wf(na), nullptr,
wf(wr), wf(bi), H, W, iC, oC, 1, str, 1, false);
for (const Lay& l : lays) {
int K, grp;
if (l.s == 2) { K = 3; grp = 1; }
else if (l.iC == l.oC) { K = 3; grp = l.iC; }
else { K = 1; grp = 1; }
const int Ho = l.H / l.s, Wo = l.W / l.s;
float* out = bufs[cur];
char wkey[128], akey[128], bkey[128];
std::snprintf(wkey, 128, "%s.0.weight", l.bn);
std::snprintf(akey, 128, "%s.1._alpha", l.bn);
std::snprintf(bkey, 128, "%s.1._beta", l.bn);
conv_bn_relu(out, in, wf(wkey), nullptr, wf(akey), wf(bkey),
l.H, l.W, l.iC, l.oC, K, l.s, grp, true);
char s1[128], s1b[128], s3[128], s3b[128];
std::snprintf(s1, 128, "%s.1.weight", l.sn);
std::snprintf(s1b, 128, "%s.1.bias", l.sn);
std::snprintf(s3, 128, "%s.3.weight", l.sn);
std::snprintf(s3b, 128, "%s.3.bias", l.sn);
se_module(out, l.oC, Ho * Wo, wt(s1), wf(s1b), wt(s3), wf(s3b));
in = out;
cur ^= 1;
}
for (int i=0; i<oC*No; ++i) o[i]+=skip[i];
// SE
std::snprintf(na,128,"%s.se.1.weight",pfx);
std::snprintf(bi,128,"%s.se.1.bias",pfx);
char se2[128], se2b[128];
std::snprintf(se2,128,"%s.se.3.weight",pfx);
std::snprintf(se2b,128,"%s.se.3.bias",pfx);
se_module(o, oC, No, wf(na), wf(bi), wf(se2), wf(se2b), m->se_buf);
conv_bn_relu(g_T, in, wf("head.0.weight"), nullptr,
wf("head.1._alpha"), wf("head.1._beta"),
15, 20, 64, 64, 3, 1, 64, true);
relu_f(o, oC*No);
conv_bn_relu(g_out, g_T, wf("out.weight"), wf("out.bias"),
nullptr, nullptr, 15, 20, 64, 9, 1, 1, 1, false);
}
// ============================================================
// 解码 — softmax + NMS (预计算 peak mask)
// ============================================================
static constexpr int OH=15, OW=20, STRIDE=8, NC=4;
static constexpr float RS[NC][2] = {
{56.85f,45.39f}, {101.44f,66.53f}, {96.75f,62.86f}, {66.01f,35.49f},
};
static int decode(DetectBoxV10* boxes, int max_boxes, float thr) {
const int N = OH * OW;
int cnt = 0;
static int decode(DetectBoxV10* boxes, int max, const float* th) {
int N=OH*OW, cnt=0;
// 预计算 peak mask: 每个类别做一次 maxpool
uint8* mask = m->peak_mask;
for (int c=0; c<NC; ++c) {
const float* cp = m->cl + c*N;
uint8* mp = mask + c*N;
for (int gy=0; gy<OH; ++gy) {
for (int gx=0; gx<OW; ++gx) {
if (cp[gy*OW+gx] < 0) { mp[gy*OW+gx]=0; continue; }
bool peak=true;
float val=cp[gy*OW+gx];
for (int dy=-1; dy<=1&&peak; ++dy)
for (int dx=-1; dx<=1&&peak; ++dx) {
int ny=gy+dy, nx=gx+dx;
if (ny<0||ny>=OH||nx<0||nx>=OW) continue;
if (cp[ny*OW+nx] > val) peak=false;
}
mp[gy*OW+gx] = peak ? 1 : 0;
}
for (int idx = 0; idx < N; ++idx) {
float mx = -1e30f;
for (int c = 0; c <= NC; ++c) mx = std::max(mx, g_out[c * N + idx]);
float sum = 0.0f;
for (int c = 0; c <= NC; ++c) {
const float e = std::exp(g_out[c * N + idx] - mx);
g_prob[c * N + idx] = e;
sum += e;
}
const float inv = 1.0f / sum;
for (int c = 0; c <= NC; ++c) g_prob[c * N + idx] *= inv;
}
// softmax + check per cell
for (int gy=0; gy<OH && cnt<max; ++gy) {
for (int gx=0; gx<OW && cnt<max; ++gx) {
int idx=gy*OW+gx;
for (int c = 0; c < NC && cnt < max_boxes; ++c) {
for (int gy = 0; gy < OH; ++gy) {
for (int gx = 0; gx < OW; ++gx) {
const int idx = gy * OW + gx;
const float pc = g_prob[c * N + idx];
if (pc <= thr || pc <= g_prob[NC * N + idx]) continue;
// softmax over NC+1 classes (fast exp)
float sm[5], mx=-1e9f, sum=0;
for (int c=0; c<=NC; ++c) {
float v=m->cl[c*N+idx];
sm[c]=v; if(v>mx) mx=v;
bool peak = true;
for (int dy = -1; dy <= 1 && peak; ++dy)
for (int dx = -1; dx <= 1 && peak; ++dx) {
const int ny = gy + dy, nx = gx + dx;
if (ny < 0 || ny >= OH || nx < 0 || nx >= OW) continue;
if (g_prob[c * N + ny * OW + nx] > pc) peak = false;
}
if (!peak) continue;
const float tx = clamp_f(g_out[(NC + 1) * N + idx], 0.0f, 1.0f);
const float ty = clamp_f(g_out[(NC + 2) * N + idx], 0.0f, 1.0f);
const float tw = std::max(g_out[(NC + 3) * N + idx], 0.5f);
const float th = std::max(g_out[(NC + 4) * N + idx], 0.5f);
const float cx = (gx + tx) * STRIDE, cy = (gy + ty) * STRIDE;
const float w = tw * STRIDE, h = th * STRIDE;
boxes[cnt].cls = c;
boxes[cnt].conf = pc;
boxes[cnt].cx = cx;
boxes[cnt].cy = cy;
boxes[cnt].w = w;
boxes[cnt].h = h;
if (++cnt >= max_boxes) return cnt;
}
for (int c=0; c<=NC; ++c) { sm[c]=fast_exp(sm[c]-mx); sum+=sm[c]; }
// best class (excluding bg=NC), per-class threshold
int bc=-1; float bs=0;
for (int c=0; c<NC; ++c) {
if (!mask[c*N+idx]) continue;
float p=sm[c]/sum;
if (p>bs && p>=th[c]) { bs=p; bc=c; }
}
if (bc<0) continue;
float pw=std::max(1.0f, m->sz[0*N+idx]*RS[bc][0]);
float ph=std::max(1.0f, m->sz[1*N+idx]*RS[bc][1]);
boxes[cnt].cls=bc; boxes[cnt].conf=bs;
boxes[cnt].cx=((float)gx+0.5f)*STRIDE;
boxes[cnt].cy=((float)gy+0.5f)*STRIDE;
boxes[cnt].w=pw; boxes[cnt].h=ph;
cnt++;
}
}
return cnt;
}
// ============================================================
// 预处理 LUT: uint8→float normalized
// ============================================================
static float g_lut[256];
static void init_lut() {
for (int i=0; i<256; ++i) g_lut[i] = (float)i / 255.0f;
}
// ============================================================
// 前向推理 — 模型计算体 (preproc 已由调用方完成)
// ============================================================
static void model_compute() {
float* in = m->preproc;
// stem: 3→6, s2 (fused)
conv_bn_relu(m->stem, in, wf("s.0.weight"), nullptr,
wf("s.1._alpha"), wf("s.1._beta"), 120, 160, 3, 6, 3, 2, 1, true);
// blocks
se_block_v10(m->b1, m->stem, 6, 8, 60, 80, 1, "b1");
se_block_v10(m->b2, m->b1, 8, 12, 60, 80, 2, "b2");
se_block_v10(m->b3, m->b2, 12, 16, 30, 40, 2, "b3");
se_block_v10(m->b4, m->b3, 16, 32, 15, 20, 1, "b4");
se_block_v10(m->b5, m->b4, 32, 48, 15, 20, 1, "b5");
se_block_v10(m->b6, m->b5, 48, 48, 15, 20, 1, "b6");
// shared: 48→24, 1×1 (fused)
conv_bn_relu(m->sh, m->b6, wf("sh.0.weight"), nullptr,
wf("sh.1._alpha"), wf("sh.1._beta"), 15, 20, 48, 24, 1, 1, 1, true);
// heads (pure conv, no BN)
conv2d(m->cl, m->sh, wf("ch.weight"), wf("ch.bias"), 15, 20, 24, 5, 1, 1, 1);
conv2d(m->sz, m->sh, wf("sz.weight"), wf("sz.bias"), 15, 20, 24, 2, 1, 1, 1);
}
// ── 自写 640×480 → 3×120×160 float planar (4×4 box avg + BGR→RGB + /255) ──
static void preproc_640(const uint8* bgr) {
float* in = m->preproc;
const int sw=640, sh=480;
for(int c=0;c<3;++c){
int sc=2-c;
float* ch = in + c*120*160;
for(int y=0;y<120;++y){
int iy=y*4;
for(int x=0;x<160;++x){
int ix=x*4, sum=0;
for(int dy=0;dy<4;++dy)
for(int dx=0;dx<4;++dx)
sum += bgr[(iy+dy)*sw*3 + (ix+dx)*3 + sc];
ch[y*160+x] = (float)sum * (1.0f/(16.0f*255.0f));
// cv2.resize INTER_LINEAR compatible
static void resize_bilinear_hwc3(const uint8_t* src, int sw, int sh, uint8_t* dst) {
const float fx = (float)sw / (float)IN_W;
const float fy = (float)sh / (float)IN_H;
static int x0i[IN_W], x1i[IN_W];
static float xw[IN_W];
for (int x = 0; x < IN_W; ++x) {
float sx = (x + 0.5f) * fx - 0.5f;
if (sx < 0) sx = 0;
int x0 = (int)sx;
if (x0 > sw - 1) x0 = sw - 1;
int x1 = std::min(x0 + 1, sw - 1);
x0i[x] = x0 * 3; x1i[x] = x1 * 3; xw[x] = sx - (float)x0;
}
for (int y = 0; y < IN_H; ++y) {
float sy = (y + 0.5f) * fy - 0.5f;
if (sy < 0) sy = 0;
int y0 = (int)sy;
if (y0 > sh - 1) y0 = sh - 1;
const int y1 = std::min(y0 + 1, sh - 1);
const float wy = sy - (float)y0;
const uint8_t* r0 = src + (size_t)y0 * sw * 3;
const uint8_t* r1 = src + (size_t)y1 * sw * 3;
uint8_t* d = dst + y * IN_W * 3;
for (int x = 0; x < IN_W; ++x) {
const float wx = xw[x];
const int a = x0i[x], b = x1i[x];
for (int c = 0; c < 3; ++c) {
const float top = r0[a + c] + (r0[b + c] - r0[a + c]) * wx;
const float bot = r1[a + c] + (r1[b + c] - r1[a + c]) * wx;
const float v = top + (bot - top) * wy;
d[x * 3 + c] = (uint8_t)(v + 0.5f);
}
}
}
}
// ── 原版: 160×120 BGR → 3×120×160 float planar ──
static void preproc_160(const uint8* bgr) {
float* in = m->preproc;
for (int c=0; c<3; ++c) {
int sc=2-c; float* ch=in+c*120*160;
for (int y=0; y<120; ++y) {
const uint8* row=bgr+y*160*3;
for (int x=0; x<160; ++x) ch[y*160+x]=g_lut[row[x*3+sc]];
static void preproc_160(const uint8_t* img, YoloPixFmt fmt) {
for (int c = 0; c < 3; ++c) {
const int sc = (fmt == YOLO_FMT_BGR) ? (2 - c) : c;
float* ch = g_preproc + c * IN_H * IN_W;
for (int y = 0; y < IN_H; ++y) {
const uint8_t* row = img + y * IN_W * 3;
for (int x = 0; x < IN_W; ++x) ch[y * IN_W + x] = g_lut[row[x * 3 + sc]];
}
}
}
// ── 内部: preproc + compute ──
static void forward(const uint8* bgr) { preproc_160(bgr); model_compute(); }
// ============================================================
// 接口
// ============================================================
static bool g_rdy=false;
static bool g_rdy = false;
bool model_v10_init(const char* path) {
FILE* f=fopen(path,"rb");
if(!f) return false;
fread(&gn,sizeof(int),1,f);
gw=new WT[gn];
for (int i=0; i<gn; ++i) {
WT& t=gw[i]; int nl; fread(&nl,4,1,f); fread(t.n,1,nl,f); t.n[nl]=0;
fread(&t.nd,4,1,f); int tot=1;
for (int d=0; d<t.nd; ++d) { fread(&t.s[d],4,1,f); tot*=t.s[d]; }
for (int d=t.nd; d<4; ++d) t.s[d]=1;
t.d=new float[tot]; fread(t.d,4,tot,f);
FILE* f = std::fopen(path, "rb");
if (!f) return false;
if (std::fread(&gn, sizeof(int), 1, f) != 1 || gn <= 0 || gn > 4096) { std::fclose(f); return false; }
gw = new WT[gn];
for (int i = 0; i < gn; ++i) {
WT& t = gw[i];
int nl = 0;
bool ok = std::fread(&nl, 4, 1, f) == 1 && nl > 0 && nl < 127
&& std::fread(t.n, 1, nl, f) == (size_t)nl;
if (ok) {
t.n[nl] = 0;
ok = std::fread(&t.nd, 4, 1, f) == 1 && t.nd >= 0 && t.nd <= 4;
}
if (ok) {
int tot = 1;
for (int d = 0; d < t.nd && ok; ++d) {
ok = std::fread(&t.s[d], 4, 1, f) == 1 && t.s[d] > 0;
tot *= t.s[d];
}
for (int d = t.nd; d < 4; ++d) t.s[d] = 1;
if (ok) {
t.d = new float[tot];
ok = std::fread(t.d, 4, tot, f) == (size_t)tot;
}
}
if (!ok) { gn = (t.d ? i + 1 : i); std::fclose(f); model_v10_deinit(); return false; }
}
fclose(f);
std::fclose(f);
bn_precompute();
m=new(std::nothrow) M10();
if(!m) { delete[] gw; gw=nullptr; return false; }
std::memset(m,0,sizeof(M10));
g_rdy=true;
for (int i = 0; i < 256; ++i) g_lut[i] = (float)i / 255.0f;
g_preproc = new(std::nothrow) float[3 * IN_H * IN_W];
g_A = new(std::nothrow) float[BUF_FLOATS];
g_B = new(std::nothrow) float[BUF_FLOATS];
g_T = new(std::nothrow) float[BUF_FLOATS];
g_out = new(std::nothrow) float[9 * 15 * 20];
if (!g_preproc || !g_A || !g_B || !g_T || !g_out) { model_v10_deinit(); return false; }
g_rdy = true;
return true;
}
int model_v10_detect(const uint8* bgr, int w, int h, DetectBoxV10* boxes, int max, const float* thresh) {
if(!g_rdy) return 0;
if(w==640 && h==480) {
preproc_640(bgr);
model_compute();
return decode(boxes,max,thresh);
int model_v10_detect_fmt(const uint8_t* img, int w, int h, YoloPixFmt fmt,
DetectBoxV10* boxes, int max_boxes, float thr) {
if (!g_rdy || !img || !boxes || max_boxes <= 0 || w <= 1 || h <= 1) return 0;
const uint8_t* net_in = img;
if (w != IN_W || h != IN_H) {
resize_bilinear_hwc3(img, w, h, g_resized);
net_in = g_resized;
}
if(w!=160||h!=120) return 0;
forward(bgr);
return decode(boxes,max,thresh);
preproc_160(net_in, fmt);
model_compute();
const int n = decode(boxes, max_boxes, thr);
const float sx = (float)w / (float)IN_W;
const float sy = (float)h / (float)IN_H;
for (int i = 0; i < n; ++i) {
boxes[i].cx *= sx; boxes[i].w *= sx;
boxes[i].cy *= sy; boxes[i].h *= sy;
}
return n;
}
int model_v10_detect(const uint8_t* img, int w, int h, DetectBoxV10* boxes, int max_boxes, const float* thresh) {
float thr = thresh ? thresh[0] : 0.6f;
return model_v10_detect_fmt(img, w, h, YOLO_FMT_BGR, boxes, max_boxes, thr);
}
const float* model_v10_forward_debug(const float* chw) {
if (!g_rdy || !chw) return nullptr;
std::memcpy(g_preproc, chw, 3 * IN_H * IN_W * sizeof(float));
model_compute();
return g_out;
}
void model_v10_set_dump_dir(const char* dir) {
if (dir) { std::strncpy(g_dump_dir, dir, sizeof(g_dump_dir) - 1); g_dump_dir[sizeof(g_dump_dir)-1] = 0; }
else g_dump_dir[0] = 0;
}
void model_v10_deinit() {
if(gw) { for(int i=0; i<gn; ++i) delete[] gw[i].d; delete[] gw; gw=nullptr; }
delete m; m=nullptr; g_rdy=false;
if (gw) { for (int i = 0; i < gn; ++i) delete[] gw[i].d; delete[] gw; gw = nullptr; }
gn = 0;
delete[] g_preproc; delete[] g_A; delete[] g_B; delete[] g_T; delete[] g_out;
g_preproc = g_A = g_B = g_T = g_out = nullptr;
g_rdy = false;
}
bool model_v10_ready() { return g_rdy; }
+3 -1
View File
@@ -2,11 +2,13 @@
#include "types_model.hpp"
struct DetectBoxV10 {
int cls; // 0=锥 1=红灯 2=绿灯 3=行人
int cls;
float conf;
float cx, cy, w, h;
};
typedef enum { YOLO_FMT_BGR = 0, YOLO_FMT_RGB = 1 } YoloPixFmt;
bool model_v10_init(const char* path);
int model_v10_detect(const uint8* bgr, int w, int h, DetectBoxV10* boxes, int max, const float* thresh);
void model_v10_deinit();
-5
View File
@@ -1,5 +0,0 @@
# demo2
add_executable(udp_receive udp_receive.cpp)
# 链接 OpenCV 库
target_link_libraries(udp_receive common_lib ${OpenCV_LIBS})
-104
View File
@@ -1,104 +0,0 @@
'''
Author: ilikara 3435193369@qq.com
Date: 2025-03-11 03:00:45
LastEditors: Ilikara 3435193369@qq.com
LastEditTime: 2025-03-11 17:04:09
FilePath: /2k300_smartcar/udp_receive/slider.py
Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
'''
import tkinter as tk
import socket
import struct
# 配置参数
TARGET_IP = "192.168.43.238" # 目标板卡IP
TARGET_PORT = 8888 # 目标端口
class UdpSliderApp:
def __init__(self, master):
self.master = master
master.title("Dual Slider Control")
# 创建UDP socket
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 滑块1
self.slider1_frame = tk.Frame(master)
self.slider1_frame.pack(pady=10)
self.label1 = tk.Label(self.slider1_frame, text="Angle: 0.00")
self.label1.pack(side=tk.LEFT)
self.slider1 = tk.Scale(
self.slider1_frame,
from_=-7.0, to=7.0,
resolution=0.01,
orient=tk.HORIZONTAL,
length=300,
command=lambda v: self.send_values()
)
self.slider1.pack(side=tk.LEFT)
# 滑块2
self.slider2_frame = tk.Frame(master)
self.slider2_frame.pack(pady=10)
self.label2 = tk.Label(self.slider2_frame, text="Speed: 0.00")
self.label2.pack(side=tk.LEFT)
self.slider2 = tk.Scale(
self.slider2_frame,
from_=-20.0, to=20.0, # 示例范围可调
resolution=0.1,
orient=tk.HORIZONTAL,
length=300,
command=lambda v: self.send_values()
)
self.slider2.pack(side=tk.LEFT)
# 退出按钮
self.exit_btn = tk.Button(
master,
text="Exit",
command=master.quit,
bg="#ff4444",
fg="white"
)
self.exit_btn.pack(pady=20)
self.reset_btn = tk.Button(
self.slider2_frame,
text="Reset to 0",
command=self.reset_slider2,
bg="#ff6666",
fg="white",
width=8
)
self.reset_btn.pack(side=tk.LEFT, padx=10)
def send_values(self):
"""打包并发送两个浮点数"""
try:
val1 = float(self.slider1.get())
val2 = float(self.slider2.get())
# 更新标签
self.label1.config(text=f"Angle: {val1:.2f}")
self.label2.config(text=f"Speed: {val2:.2f}")
# 打包为网络字节序的两个float
data = struct.pack('!ff', val1, val2) # !表示网络字节序
# 发送UDP数据
self.sock.sendto(data, (TARGET_IP, TARGET_PORT))
except Exception as e:
print(f"发送错误: {str(e)}")
def reset_slider2(self):
self.slider2.set(0.0) # 设置滑块物理位置
self.label2.config(text="Slider2: 0.00") # 直接更新显示
self.send_values() # 手动触发发送
if __name__ == "__main__":
root = tk.Tk()
app = UdpSliderApp(root)
root.mainloop()
app.sock.close()
-175
View File
@@ -1,175 +0,0 @@
/*
* @Author: ilikara 3435193369@qq.com
* @Date: 2025-03-11 02:38:15
* @LastEditors: ilikara 3435193369@qq.com
* @LastEditTime: 2025-03-29 01:39:17
* @FilePath: /2k300_smartcar/udp_receive/udp_receive.cpp
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstdint>
#include <string.h>
#include <opencv2/opencv.hpp>
#include <thread>
#include "MotorController.h"
#include "PwmController.h"
#include "GPIO.h"
#include <iomanip>
cv::VideoCapture cap;
std::string directory = "./image";
int saved_frame_count;
void streamCapture(void)
{
cv::Mat frame;
while (1)
{
cap.read(frame);
if (frame.empty())
{
std::cerr << "Save Error: Frame is empty." << std::endl;
continue;
}
// 构建文件名
std::ostringstream filename;
filename << directory << "/image_" << std::setw(5) << std::setfill('0') << saved_frame_count << ".jpg";
saved_frame_count++;
// 保存图像
cv::imwrite(filename.str(), frame);
}
return;
}
GPIO mortorEN(73);
MotorController *motorController[2] = {nullptr, nullptr};
PwmController servo(1, 0);
void Init()
{
mortorEN.setDirection("out");
mortorEN.setValue(1);
const int pwmchip[2] = {8, 8};
const int pwmnum[2] = {2, 1};
const int gpioNum[2] = {12, 13};
const int encoder_pwmchip[2] = {0, 3};
const int encoder_gpioNum[2] = {75, 72};
const int encoder_dir[2] = {1, -1};
const unsigned int period_ns = 50000; // 20 kHz
for (int i = 0; i < 2; ++i)
{
motorController[i] = new MotorController(pwmchip[i], pwmnum[i], gpioNum[i], period_ns,
0, 0, 0, 0,
encoder_pwmchip[i], encoder_gpioNum[i], encoder_dir[i]);
motorController[i]->updateduty(0);
}
servo.setPeriod(3040000);
servo.setDutyCycle(1520000);
servo.enable();
}
int main()
{
// 打开默认摄像头(设备编号 0
cap.open(0);
// 检查摄像头是否成功打开
if (!cap.isOpened())
{
printf("无法打开摄像头\n");
return -1;
}
cap.set(cv::CAP_PROP_FRAME_WIDTH, 320); // 宽度
cap.set(cv::CAP_PROP_FRAME_HEIGHT, 240); // 高度
cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G')); // 视频流格式
cap.set(cv::CAP_PROP_AUTO_EXPOSURE, -1); // 设置自动曝光
std::thread camworker = std::thread(streamCapture);
Init();
const int PORT = 8888;
const int BUFFER_SIZE = 2 * sizeof(float);
// 创建UDP Socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
std::cerr << "Socket creation failed" << std::endl;
return -1;
}
// 绑定地址和端口
sockaddr_in server_addr{};
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
if (bind(sockfd, (sockaddr *)&server_addr, sizeof(server_addr)))
{
std::cerr << "Bind failed" << std::endl;
close(sockfd);
return -1;
}
// 接收数据
sockaddr_in client_addr{};
socklen_t client_len = sizeof(client_addr);
char buffer[BUFFER_SIZE];
while (true)
{
ssize_t bytes_received = recvfrom(
sockfd,
buffer,
BUFFER_SIZE,
0,
(sockaddr *)&client_addr,
&client_len);
if (bytes_received == BUFFER_SIZE)
{
// 解析第一个float
uint32_t temp1;
memcpy(&temp1, buffer, 4);
temp1 = ntohl(temp1);
float val1;
memcpy(&val1, &temp1, 4);
// 解析第二个float
uint32_t temp2;
memcpy(&temp2, buffer + 4, 4);
temp2 = ntohl(temp2);
float val2;
memcpy(&val2, &temp2, 4);
std::cout << "收到数据: "
<< "滑块1=" << val1
<< ", 滑块2=" << val2
<< std::endl;
double servoduty_ns = (val1) / 100 * servo.readPeriod() + 1520000;
servo.setDutyCycle(servoduty_ns);
motorController[0]->updateduty(val2);
motorController[1]->updateduty(val2);
}
else
{
std::cerr << "Invalid data size" << std::endl;
}
}
close(sockfd);
return 0;
}
-10
View File
@@ -1,10 +0,0 @@
cmake_minimum_required(VERSION 3.5.0)
project(vl53l0x_demo VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
include_directories(${CMAKE_SOURCE_DIR}/lib)
include_directories(${CMAKE_SOURCE_DIR}/src)
add_executable(vl53l0x_demo vl53l0x_demo.cpp)
target_link_libraries(vl53l0x_demo common_lib pthread)
-44
View File
@@ -1,44 +0,0 @@
/*
* VL53L0X 激光测距 demo — 持续读取距离并打印
*/
#include <iostream>
#include <csignal>
#include <thread>
#include <atomic>
#include "vl53l0x.h"
std::atomic<bool> running(true);
void onSignal(int)
{
running = false;
}
int main()
{
std::signal(SIGINT, onSignal);
VL53L0X tof;
if (!tof.init())
{
std::cerr << "VL53L0X 初始化失败" << std::endl;
return 1;
}
VL53L0X_RangingMeasurementData_t data;
while (running)
{
if (tof.readRange(data))
{
std::cout << "dist=" << data.RangeMilliMeter << "mm"
<< " status=" << (int)data.RangeStatus
<< " signal=" << data.SignalRateRtnMegaCps
<< std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
tof.stop();
std::cout << "VL53L0X demo exit" << std::endl;
return 0;
}
-32
View File
@@ -1,32 +0,0 @@
#!/bin/sh
# VL53L0X 激光测距初始化脚本
# GPIO 50=SCL, GPIO 51=SDA -> I2C2_SCL[1]/I2C2_SDA[1]
echo "[VL53L0X] === 检查/初始化激光测距 ==="
# 1. 检查设备节点是否已存在
if [ -c /dev/stmvl53l0x_ranging ]; then
echo "[VL53L0X] /dev/stmvl53l0x_ranging 已存在"
else
echo "[VL53L0X] /dev/stmvl53l0x_ranging 不存在,尝试加载驱动..."
modprobe stmvl53l0x 2>/dev/null || modprobe vl53l0x 2>/dev/null
if [ ! -c /dev/stmvl53l0x_ranging ]; then
echo "[VL53L0X] 驱动未加载,需要检查内核配置或设备树"
fi
fi
# 2. 设置 GPIO 50/51 为 I2C 功能(尝试通过 debugfs pinctrl
PINMUX=/sys/kernel/debug/pinctrl/zx296718-pinctrl
if [ -d "$PINMUX" ]; then
echo "[VL53L0X] pinctrl debugfs 可用: $PINMUX"
ls "$PINMUX"/
else
echo "[VL53L0X] pinctrl debugfs 不可用"
fi
# 3. 最终确认
if [ -c /dev/stmvl53l0x_ranging ]; then
echo "[VL53L0X] OK — 设备就绪,可以使用"
else
echo "[VL53L0X] FAIL — 需要先配置内核/设备树中的 I2C 引脚复用"
fi
-36
View File
@@ -1,36 +0,0 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := eng
LOCAL_SRC_FILES:=vl53l0x_test.c
LOCAL_MODULE:=vl53l0x_test
LOCAL_CPPFLAGS += -DANDROID
LOCAL_SHARED_LIBRARIES:=libc
LOCAL_STATIC_LIBRARIES :=
LOCAL_C_INCLUDES += $(LOCAL_PATH) $(LOCAL_PATH)/$(KERNEL_DIR)/include
include $(BUILD_EXECUTABLE)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := eng
LOCAL_SRC_FILES:=vl53l0x_reg.c
LOCAL_MODULE:=vl53l0x_reg
LOCAL_CPPFLAGS += -DANDROID
LOCAL_SHARED_LIBRARIES:=libc
LOCAL_STATIC_LIBRARIES :=
LOCAL_C_INCLUDES += $(LOCAL_PATH) $(LOCAL_PATH)/$(KERNEL_DIR)/include
include $(BUILD_EXECUTABLE)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := eng
LOCAL_SRC_FILES:=vl53l0x_parameter.c
LOCAL_MODULE:=vl53l0x_parameter
LOCAL_CPPFLAGS += -DANDROID
LOCAL_SHARED_LIBRARIES:=libc
LOCAL_STATIC_LIBRARIES :=
LOCAL_C_INCLUDES += $(LOCAL_PATH) $(LOCAL_PATH)/$(KERNEL_DIR)/include
include $(BUILD_EXECUTABLE)
-15
View File
@@ -1,15 +0,0 @@
CFLAGS=-I$(ROOTFS_INC) -I./
CC=/opt/loongson-gnu-toolchain-8.3-x86_64-loongarch64-linux-gnu-rc1.6/bin/loongarch64-linux-gnu-gcc
all: vl53l0x_test vl53l0x_reg vl53l0x_parameter
vl53l0x_test: vl53l0x_test.o
$(CC) -o vl53l0x_test vl53l0x_test.o $(CFLAGS)
vl53l0x_reg: vl53l0x_reg.o
$(CC) -o vl53l0x_reg vl53l0x_reg.o $(CFLAGS)
vl53l0x_parameter: vl53l0x_parameter.o
$(CC) -o vl53l0x_parameter vl53l0x_parameter.o $(CFLAGS)
.PHONY: clean
clean:
rm -f ./*.o *~ core vl53l0x_test vl53l0x_reg vl53l0x_parameter
-640
View File
@@ -1,640 +0,0 @@
/*******************************************************************************
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 1
/** VL53L0X PAL IMPLEMENTATION sub version */
#define VL53L0X_IMPLEMENTATION_VER_REVISION 4606
#define VL53L0X_DEFAULT_MAX_LOOP 200
#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 */
/** @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 */
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 */
/** @brief Structure containing the Dmax computation parameters and data
*/
typedef struct {
int32_t AmbTuningWindowFactor_K;
/*!< internal algo tuning (*1000) */
int32_t RetSignalAt0mm;
/*!< intermediate dmax computation value caching */
} VL53L0X_DMaxData_t;
/**
* @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 */
uint8_t HistogramType; /*!< 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 */
uint8_t ReadDataFromDeviceDone; /* Indicate if read from device has
been done (==1) or not (==0) */
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 */
FixPoint1616_t SignalRateMeasFixed400mm; /*!< Peek Signal rate
at 400 mm*/
} 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 {
VL53L0X_DMaxData_t DMaxData;
/*!< Dmax Data */
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 */
uint16_t DmaxCalRangeMilliMeter;
/*!< Dmax Calibration Range millimeter */
FixPoint1616_t DmaxCalSignalRateRtnMegaCps;
/*!< Dmax Calibration Signal Rate Return MegaCps */
} 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)
/*!<Identifies the pre-range vcsel period. */
#define VL53L0X_VCSEL_PERIOD_FINAL_RANGE ((VL53L0X_VcselPeriod) 1)
/*!<Identifies the final range vcsel period. */
/** @} VL53L0X_define_VcselPeriod_group */
/** @defgroup VL53L0X_define_SchedulerSequence_group Defines the steps
* carried out by the scheduler during a range measurement.
* @{
* Defines the states of all the steps in the scheduler
* i.e. enabled/disabled.
*/
typedef struct {
uint8_t TccOn; /*!<Reports if Target Centre Check On */
uint8_t MsrcOn; /*!<Reports if MSRC On */
uint8_t DssOn; /*!<Reports if DSS On */
uint8_t PreRangeOn; /*!<Reports if Pre-Range On */
uint8_t FinalRangeOn; /*!<Reports if Final-Range On */
} VL53L0X_SchedulerSequenceSteps_t;
/** @} VL53L0X_define_SchedulerSequence_group */
/** @defgroup VL53L0X_define_SequenceStepId_group Defines the Polarity
* of the Interrupt
* Defines the the sequence steps performed during ranging..
* @{
*/
typedef uint8_t VL53L0X_SequenceStepId;
#define VL53L0X_SEQUENCESTEP_TCC ((VL53L0X_VcselPeriod) 0)
/*!<Target CentreCheck identifier. */
#define VL53L0X_SEQUENCESTEP_DSS ((VL53L0X_VcselPeriod) 1)
/*!<Dynamic Spad Selection function Identifier. */
#define VL53L0X_SEQUENCESTEP_MSRC ((VL53L0X_VcselPeriod) 2)
/*!<Minimum Signal Rate Check function Identifier. */
#define VL53L0X_SEQUENCESTEP_PRE_RANGE ((VL53L0X_VcselPeriod) 3)
/*!<Pre-Range check Identifier. */
#define VL53L0X_SEQUENCESTEP_FINAL_RANGE ((VL53L0X_VcselPeriod) 4)
/*!<Final Range Check Identifier. */
#define VL53L0X_SEQUENCESTEP_NUMBER_OF_CHECKS 5
/*!<Number of Sequence Step Managed by the API. */
/** @} VL53L0X_define_SequenceStepId_group */
/* MACRO Definitions */
/** @defgroup VL53L0X_define_GeneralMacro_group General Macro Defines
* General Macro Defines
* @{
*/
/* Defines */
#define VL53L0X_SETPARAMETERFIELD(Dev, field, value) \
PALDevDataSet(Dev, CurrentParameters.field, value)
#define VL53L0X_GETPARAMETERFIELD(Dev, field, variable) \
variable = PALDevDataGet(Dev, CurrentParameters).field
#define VL53L0X_SETARRAYPARAMETERFIELD(Dev, field, index, value) \
PALDevDataSet(Dev, CurrentParameters.field[index], value)
#define VL53L0X_GETARRAYPARAMETERFIELD(Dev, field, index, variable) \
variable = PALDevDataGet(Dev, CurrentParameters).field[index]
#define VL53L0X_SETDEVICESPECIFICPARAMETER(Dev, field, value) \
PALDevDataSet(Dev, DeviceSpecificParameters.field, value)
#define VL53L0X_GETDEVICESPECIFICPARAMETER(Dev, field) \
PALDevDataGet(Dev, DeviceSpecificParameters).field
#define VL53L0X_FIXPOINT1616TOFIXPOINT97(Value) \
(uint16_t)((Value>>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_ */
-257
View File
@@ -1,257 +0,0 @@
/*******************************************************************************
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_ */
-101
View File
@@ -1,101 +0,0 @@
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "vl53l0x_def.h"
//******************************** IOCTL definitions
#define VL53L0X_IOCTL_INIT _IO('p', 0x01)
#define VL53L0X_IOCTL_XTALKCALB _IOW('p', 0x02, unsigned int)
#define VL53L0X_IOCTL_OFFCALB _IOW('p', 0x03, unsigned int)
#define VL53L0X_IOCTL_STOP _IO('p', 0x05)
#define VL53L0X_IOCTL_SETXTALK _IOW('p', 0x06, unsigned int)
#define VL53L0X_IOCTL_SETOFFSET _IOW('p', 0x07, int8_t)
#define VL53L0X_IOCTL_GETDATAS _IOR('p', 0x0b, VL53L0X_RangingMeasurementData_t)
#define VL53L0X_IOCTL_PARAMETER _IOWR('p', 0x0d, struct stmvl53l0x_parameter)
typedef enum {
OFFSET_PAR = 0,
XTALKRATE_PAR = 1,
XTALKENABLE_PAR = 2,
GPIOFUNC_PAR = 3,
LOWTHRESH_PAR = 4,
HIGHTHRESH_PAR = 5,
DEVICEMODE_PAR = 6,
INTERMEASUREMENT_PAR = 7,
} parameter_name_e;
/*
* IOCTL parameter structs
*/
struct stmvl53l0x_parameter {
uint32_t is_read; //1: Get 0: Set
parameter_name_e name;
int32_t value;
int32_t value2;
int32_t status;
};
static void help(void)
{
fprintf(stderr,
"Usage: vl53l0x_parameter Parameter [Value]\n"
" Parameter as <0: OFFSET, 1: XTALKRATE, 2: XTAKEENABLE, 3: GPIOFUNC, \n"
" 4: LOW_THRESHOLD, 5: HIGH_THRESHOLD,\n"
" 6: DEVICE_MODE, 7: INTERMEASUREMENT_MS\n"
" 8: REFERENCESPADS_PAR, 9: REFCALIBRATION_PAR\n"
" Value is optional for writting value to requested Parameter\n"
);
exit(1);
}
int main(int argc, char *argv[])
{
int fd;
struct stmvl53l0x_parameter parameter;
char *end;
if (argc < 2) {
help();
exit(1);
}
fd = open("/dev/stmvl53l0x_ranging",O_RDWR );
if (fd <= 0) {
fprintf(stderr,"Error open stmvl53l0x_ranging device: %s\n", strerror(errno));
return -1;
}
parameter.name = strtoul(argv[1], &end, 10);
if (*end) {
help();
exit(1);
}
if (argc == 2) {
parameter.is_read = 1;
fprintf(stderr, "To get VL53L0 parameter index:0x%x\n",
parameter.name);
} else {
parameter.is_read = 0;
parameter.value = strtoul(argv[2],&end,10);
if (*end) {
help();
exit(1);
}
fprintf(stderr, "To set VL53L0 parameter index:0x%x, as value:%d\n",
parameter.name, parameter.value);
}
// to access requested register
ioctl(fd, VL53L0X_IOCTL_PARAMETER,&parameter);
fprintf(stderr," VL53L0 parameter value:%d, error status:%d\n",
parameter.value, parameter.status);
//close(fd);
return 0;
}
Binary file not shown.
Binary file not shown.
-88
View File
@@ -1,88 +0,0 @@
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "vl53l0x_types.h"
/*
* IOCTL register data structs
*/
struct stmvl53l0x_register {
uint32_t is_read; //1: read 0: write
uint32_t reg_index;
uint32_t reg_bytes;
uint32_t reg_data;
int32_t status;
};
//******************************** IOCTL definitions
#define VL53L0X_IOCTL_REGISTER _IOWR('p', 0x0c, struct stmvl53l0x_register)
static void help(void)
{
fprintf(stderr,
"Usage: vl53l0x_reg REG_ADDR REG_BYTES [REG_DATA]\n"
" REG_ADDR is VL6180's register address using 0xxx format: \n"
" REG_BYTES is the register bytes(1, 2 or 4) \n"
" REG_DATA is optional for writting data to requested REG_ADDR\n"
);
exit(1);
}
int main(int argc, char *argv[])
{
int fd;
struct stmvl53l0x_register reg;
char *end;
if (argc < 3) {
help();
exit(1);
}
fd = open("/dev/stmvl53l0x_ranging",O_RDWR );
if (fd <= 0) {
fprintf(stderr,"Error open stmvl53l0x_ranging device: %s\n", strerror(errno));
return -1;
}
reg.reg_index = strtoul(argv[1], &end, 16);
if (*end) {
help();
exit(1);
}
reg.reg_bytes = strtoul(argv[2],&end,10);
if (*end) {
help();
exit(1);
}
if (argc == 3) {
reg.is_read = 1;
fprintf(stderr, "To read VL53L0 register index:0x%x, bytes:%d\n",
reg.reg_index, reg.reg_bytes);
} else {
reg.is_read = 0;
reg.reg_data = strtoul(argv[3],&end,16);
if (*end) {
help();
exit(1);
}
fprintf(stderr, "To write VL53L0 register index:0x%x, bytes:%d as value:0x%x\n",
reg.reg_index, reg.reg_bytes, reg.reg_data);
}
// to access requested register
ioctl(fd, VL53L0X_IOCTL_REGISTER,&reg);
fprintf(stderr," VL53L0 register data:0x%x, error status:%d\n",
reg.reg_data, reg.status);
//close(fd);
return 0;
}
Binary file not shown.
Binary file not shown.
-389
View File
@@ -1,389 +0,0 @@
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <signal.h>
#include "vl53l0x_def.h"
#define MODE_RANGE 0
#define MODE_XTAKCALIB 1
#define MODE_OFFCALIB 2
#define MODE_HELP 3
#define MODE_PARAMETER 6
//******************************** IOCTL definitions
#define VL53L0X_IOCTL_INIT _IO('p', 0x01)
#define VL53L0X_IOCTL_XTALKCALB _IOW('p', 0x02, unsigned int)
#define VL53L0X_IOCTL_OFFCALB _IOW('p', 0x03, unsigned int)
#define VL53L0X_IOCTL_STOP _IO('p', 0x05)
#define VL53L0X_IOCTL_SETXTALK _IOW('p', 0x06, unsigned int)
#define VL53L0X_IOCTL_SETOFFSET _IOW('p', 0x07, int8_t)
#define VL53L0X_IOCTL_GETDATAS _IOR('p', 0x0b, VL53L0X_RangingMeasurementData_t)
#define VL53L0X_IOCTL_PARAMETER _IOWR('p', 0x0d, struct stmvl53l0x_parameter)
//modify the following macro accoring to testing set up
#define OFFSET_TARGET 100//200
#define XTALK_TARGET 600//400
#define NUM_SAMPLES 20//20
typedef enum {
OFFSET_PAR = 0,
XTALKRATE_PAR = 1,
XTALKENABLE_PAR = 2,
GPIOFUNC_PAR = 3,
LOWTHRESH_PAR = 4,
HIGHTHRESH_PAR = 5,
DEVICEMODE_PAR = 6,
INTERMEASUREMENT_PAR = 7,
REFERENCESPADS_PAR = 8,
REFCALIBRATION_PAR = 9,
} parameter_name_e;
/*
* IOCTL parameter structs
*/
struct stmvl53l0x_parameter {
uint32_t is_read; //1: Get 0: Set
parameter_name_e name;
int32_t value;
int32_t value2;
int32_t status;
};
static void help(void)
{
fprintf(stderr,
"Usage: vl53l0x_test [-c] [-h]\n"
" -h for usage\n"
" -c for crosstalk calibration\n"
" -o for offset calibration\n"
" -O <low> <high> for testing interrupt threshold (out mode)\n"
" -L <low> for testing interrupt threshold (low mode)\n"
" -H <high> for testing interrupt threshold (high mode)\n"
" default for ranging\n"
);
exit(1);
}
static int loop_break=0;
void sig_handler(int signum)
{
if(signum == SIGINT)
{
loop_break=1;
}
}
int main(int argc, char *argv[])
{
int fd;
unsigned long data;
VL53L0X_RangingMeasurementData_t range_datas;
struct stmvl53l0x_parameter parameter;
int flags = 0;
int mode = MODE_RANGE;
unsigned int targetDistance=0;
int i = 0;
int rc = 0;
unsigned int low_threshold = 0, high_threshold = 0;
int configure_int_thresholds = 0;
int gpio_functionnality_threshold = 0;
if (signal(SIGINT, sig_handler) == SIG_ERR)
fprintf(stderr, "Can't catch SIGINT");
/* handle (optional) flags first */
while (1+flags < argc && argv[1+flags][0] == '-') {
switch (argv[1+flags][1]) {
case 'c': mode= MODE_XTAKCALIB; break;
case 'h': mode= MODE_HELP; break;
case 'o': mode = MODE_OFFCALIB; break;
case 'O':
if (argc < 4) {
fprintf(stderr, "USAGE: %s <low_threshold_value> <high_threshold_value>\n", argv[0]);
exit(1);
}
low_threshold = atoi(argv[2]);
high_threshold = atoi(argv[3]);
configure_int_thresholds = 1;
fprintf(stderr, "Configuring Low threshold = %u, High threhold = %u\n", low_threshold, high_threshold);
break;
case 'L':
if (argc < 3) {
fprintf(stderr, "USAGE: %s <low_threshold_value> \n", argv[0]);
exit(1);
}
low_threshold = atoi(argv[2]);
configure_int_thresholds = 2;
fprintf(stderr, "Configuring Low threshold = %u, High threhold = %u\n", low_threshold, high_threshold);
break;
case 'H':
if (argc < 3) {
fprintf(stderr, "USAGE: %s <high_threshold_value>\n", argv[0]);
exit(1);
}
high_threshold = atoi(argv[2]);
configure_int_thresholds = 3;
fprintf(stderr, "Configuring Low threshold = %u, High threhold = %u\n", low_threshold, high_threshold);
break;
default:
fprintf(stderr, "Error: Unsupported option "
"\"%s\"!\n", argv[1+flags]);
help();
exit(1);
}
flags++;
}
if (mode == MODE_HELP)
{
help();
exit(0);
}
fd = open("/dev/stmvl53l0x_ranging",O_RDWR | O_SYNC);
if (fd <= 0)
{
fprintf(stderr,"Error open stmvl53l0x_ranging device: %s\n", strerror(errno));
return -1;
}
//make sure it's not started
if (ioctl(fd, VL53L0X_IOCTL_STOP , NULL) < 0) {
fprintf(stderr, "Error: Could not perform VL53L0X_IOCTL_STOP : %s\n", strerror(errno));
close(fd);
return -1;
}
if (mode == MODE_XTAKCALIB)
{
unsigned int XtalkInt = 0;
uint8_t XtalkEnable = 0;
fprintf(stderr, "xtalk Calibrate place black target at %dmm from glass===\n",XTALK_TARGET);
// to xtalk calibration
targetDistance = XTALK_TARGET;
if (ioctl(fd, VL53L0X_IOCTL_XTALKCALB , &targetDistance) < 0) {
fprintf(stderr, "Error: Could not perform VL53L0X_IOCTL_XTALKCALB : %s\n", strerror(errno));
close(fd);
return -1;
}
// to get xtalk parameter
parameter.is_read = 1;
parameter.name = XTALKRATE_PAR;
if (ioctl(fd, VL53L0X_IOCTL_PARAMETER , &parameter) < 0) {
fprintf(stderr, "Error: Could not perform VL53L0X_IOCTL_PARAMETER : %s\n", strerror(errno));
close(fd);
return -1;
}
XtalkInt = (unsigned int)parameter.value;
parameter.name = XTALKENABLE_PAR;
if (ioctl(fd, VL53L0X_IOCTL_PARAMETER , &parameter) < 0) {
fprintf(stderr, "Error: Could not perform VL53L0X_IOCTL_PARAMETER : %s\n", strerror(errno));
close(fd);
return -1;
}
XtalkEnable = (uint8_t)parameter.value;
fprintf(stderr, "VL53L0 Xtalk Calibration get Xtalk Compensation rate in fixed 16 point as %u, enable:%u\n",XtalkInt,XtalkEnable);
for(i = 0; i <= NUM_SAMPLES; i++)
{
usleep(30 *1000); /*100ms*/
// to get all range data
ioctl(fd, VL53L0X_IOCTL_GETDATAS,&range_datas);
}
fprintf(stderr," VL53L0 DMAX calibration Range Data:%d, signalRate_mcps:%d\n",range_datas.RangeMilliMeter, range_datas.SignalRateRtnMegaCps);
// get rangedata of last measurement to avoid incorrect datum from unempty buffer
//to close
close(fd);
return -1;
}
else if (mode == MODE_OFFCALIB)
{
int offset=0;
uint32_t SpadCount=0;
uint8_t IsApertureSpads=0;
uint8_t VhvSettings=0,PhaseCal=0;
// to xtalk calibration
targetDistance = OFFSET_TARGET;
if (ioctl(fd, VL53L0X_IOCTL_OFFCALB , &targetDistance) < 0) {
fprintf(stderr, "Error: Could not perform VL53L0X_IOCTL_OFFCALB : %s\n", strerror(errno));
close(fd);
return -1;
}
// to get current offset
parameter.is_read = 1;
parameter.name = OFFSET_PAR;
if (ioctl(fd, VL53L0X_IOCTL_PARAMETER, &parameter) < 0) {
fprintf(stderr, "Error: Could not perform VL53L0X_IOCTL_PARAMETER : %s\n", strerror(errno));
close(fd);
return -1;
}
offset = (int)parameter.value;
fprintf(stderr, "get offset %d micrometer===\n",offset);
parameter.name = REFCALIBRATION_PAR;
if (ioctl(fd, VL53L0X_IOCTL_PARAMETER, &parameter) < 0) {
fprintf(stderr, "Error: Could not perform VL53L0X_IOCTL_PARAMETER : %s\n", strerror(errno));
close(fd);
return -1;
}
VhvSettings = (uint8_t) parameter.value;
PhaseCal=(uint8_t) parameter.value2;
fprintf(stderr, "get VhvSettings is %u ===\nget PhaseCas is %u ===\n", VhvSettings,PhaseCal);
parameter.name =REFERENCESPADS_PAR;
if (ioctl(fd, VL53L0X_IOCTL_PARAMETER, &parameter) < 0) {
fprintf(stderr, "Error: Could not perform VL53L0X_IOCTL_PARAMETER : %s\n", strerror(errno));
close(fd);
return -1;
}
SpadCount = (uint32_t)parameter.value;
IsApertureSpads=(uint8_t) parameter.value2;
fprintf(stderr, "get SpadCount is %d ===\nget IsApertureSpads is %u ===\n", SpadCount,IsApertureSpads);
//to close
close(fd);
return -1;
}
else
{
switch(configure_int_thresholds)
{
case 1:
gpio_functionnality_threshold = VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_OUT;
break;
case 2:
gpio_functionnality_threshold = VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_LOW;
break;
case 3:
gpio_functionnality_threshold = VL53L0X_GPIOFUNCTIONALITY_THRESHOLD_CROSSED_HIGH;
break;
default:
gpio_functionnality_threshold = VL53L0X_GPIOFUNCTIONALITY_NEW_MEASURE_READY;
}
if (configure_int_thresholds) {
parameter.is_read = 0;
parameter.name = DEVICEMODE_PAR;
parameter.value = VL53L0X_DEVICEMODE_CONTINUOUS_TIMED_RANGING;
//parameter.value = VL53L0X_DEVICEMODE_CONTINUOUS_RANGING;
if (ioctl(fd, VL53L0X_IOCTL_PARAMETER, &parameter) < 0) {
fprintf(stderr, "Error: Could not perform VL53L0X_IOCTL_PARAMETER(CONTINOUS_TIMED_RANGING) : %s\n",
strerror(errno));
close(fd);
return -1;
}
parameter.name = GPIOFUNC_PAR;
parameter.value = gpio_functionnality_threshold;
if (ioctl(fd, VL53L0X_IOCTL_PARAMETER, &parameter) < 0) {
fprintf(stderr, "Error: Could not perform VL53L0X_IOCTL_PARAMETER : %s, low_threshold = %u\n",
strerror(errno),
low_threshold);
close(fd);
return -1;
}
if (configure_int_thresholds != 3)
{
parameter.name = LOWTHRESH_PAR;
parameter.value = low_threshold;
if (ioctl(fd, VL53L0X_IOCTL_PARAMETER, &parameter) < 0) {
fprintf(stderr, "Error: Could not perform VL53L0X_IOCTL_PARAMETER : %s, low_threshold = %u\n",
strerror(errno),
low_threshold);
close(fd);
return -1;
}
}
if (configure_int_thresholds != 2)
{
parameter.name = HIGHTHRESH_PAR;
parameter.value = high_threshold;
if (ioctl(fd, VL53L0X_IOCTL_PARAMETER, &parameter) < 0) {
fprintf(stderr, "Error: Could not perform VL53L0X_IOCTL_PARAMETER : %s, high_threshold = %u\n",
strerror(errno),
high_threshold);
close(fd);
return -1;
}
}
}
else {
parameter.name = DEVICEMODE_PAR;
parameter.value = VL53L0X_DEVICEMODE_CONTINUOUS_RANGING;
if (ioctl(fd, VL53L0X_IOCTL_PARAMETER, &parameter) < 0) {
fprintf(stderr, "Error: Could not perform VL53L0X_IOCTL_PARAMETER(CONTINUOUS_RANGING) : %s\n",
strerror(errno));
close(fd);
return -1;
}
parameter.name = GPIOFUNC_PAR;
parameter.value = gpio_functionnality_threshold;
if (ioctl(fd, VL53L0X_IOCTL_PARAMETER, &parameter) < 0) {
fprintf(stderr, "Error: Could not perform VL53L0X_IOCTL_PARAMETER : %s, low_threshold = %u\n",
strerror(errno),
low_threshold);
close(fd);
return -1;
}
}
// to init
if (ioctl(fd, VL53L0X_IOCTL_INIT , NULL) < 0) {
fprintf(stderr, "Error: Could not perform VL53L0X_IOCTL_INIT : %s\n", strerror(errno));
close(fd);
return -1;
}
}
// get data testing
while (1)
{
usleep(30 *1000); /*100ms*/
// to get all range data
rc = ioctl(fd, VL53L0X_IOCTL_GETDATAS,&range_datas);
if (rc) {
fprintf(stderr, "VL53L0X_IOCTL_GETDATAS failed = %d\n", rc);
close(fd);
exit(1);
}
fprintf(stderr,"Range:%4d, Error:%u, SigRate_mcps:%7d, AmbRate_mcps:%7d\r",
range_datas.RangeMilliMeter, range_datas.RangeStatus, range_datas.SignalRateRtnMegaCps, range_datas.AmbientRateRtnMegaCps);
if(loop_break)
break;
}
fprintf(stderr, "\n");
fprintf(stderr, "Stop driver\n");
if (ioctl(fd, VL53L0X_IOCTL_STOP , NULL) < 0) {
fprintf(stderr, "Error: Could not perform VL53L0X_IOCTL_STOP : %s\n", strerror(errno));
close(fd);
return -1;
}
close(fd);
return 0;
}
Binary file not shown.
-69
View File
@@ -1,69 +0,0 @@
/*******************************************************************************
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_TYPES_H_
#define VL53L0X_TYPES_H_
#include <linux/types.h>
#ifndef NULL
#error "TODO review NULL definition or add required include "
#define NULL 0
#endif
/** use where fractional values are expected
*
* Given a floating point value f it's .16 bit point is (int)(f*(1<<16))*/
typedef unsigned int FixPoint1616_t;
#if !defined(STDINT_H) && !defined(_GCC_STDINT_H) \
&& !defined(_STDINT_H) && !defined(_LINUX_TYPES_H)
#pragma message("Please review type definition of STDINT define for your \
platform and add to list above ")
/*
* target platform do not provide stdint or use a different #define than above
* to avoid seeing the message below addapt the #define list above or implement
* all type and delete these pragma
*/
typedef unsigned int uint32_t;
typedef int int32_t;
typedef unsigned short uint16_t;
typedef short int16_t;
typedef unsigned char uint8_t;
typedef signed char int8_t;
#else
#include <stdint.h>
#endif /* _STDINT_H */
#endif /* VL53L0X_TYPES_H_ */
-5
View File
@@ -1,5 +0,0 @@
# demo2
add_executable(wonderEcho_demo wonderEcho_demo.cpp)
# 链接 OpenCV 库
target_link_libraries(wonderEcho_demo)
-157
View File
@@ -1,157 +0,0 @@
/*
* @Author: Ilikara 3435193369@qq.com
* @Date: 2025-02-11 15:23:11
* @LastEditors: ilikara 3435193369@qq.com
* @LastEditTime: 2025-03-10 13:06:56
* @FilePath: /2k300_smartcar/wonderEcho_demo/wonderEcho_demo.cpp
* @Description:
*
* Copyright (c) 2025 by ${git_name_email}, All Rights Reserved.
*/
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <thread>
#include <atomic>
#include <mutex>
std::mutex i2c_mutex; // 互斥锁,用于保护I2C设备访问
std::atomic<bool> running{true}; // 控制线程运行状态
int i2c_read_byte_data(int file, __u8 reg)
{
union i2c_smbus_data data;
struct i2c_smbus_ioctl_data args;
args.read_write = I2C_SMBUS_READ;
args.command = reg;
args.size = I2C_SMBUS_BYTE_DATA;
args.data = &data;
if (ioctl(file, I2C_SMBUS, &args) == -1)
{
return -1;
}
return data.byte & 0xFF;
}
int i2c_write_i2c_block_data(int file, __u8 reg, __u8 length, const __u8 *values)
{
union i2c_smbus_data data;
struct i2c_smbus_ioctl_data args;
if (length > I2C_SMBUS_BLOCK_MAX)
{
return -1;
}
for (int i = 0; i < length; i++)
{
data.block[i + 1] = values[i];
}
data.block[0] = length;
args.read_write = I2C_SMBUS_WRITE;
args.command = reg;
args.size = I2C_SMBUS_I2C_BLOCK_DATA;
args.data = &data;
if (ioctl(file, I2C_SMBUS, &args) == -1)
{
return -1;
}
return 0;
}
// 轮询线程函数
void poll_i2c_device(int file)
{
while (running)
{
{
std::lock_guard<std::mutex> lock(i2c_mutex); // 加锁
__u8 reg = 0x64;
__u8 value = i2c_read_byte_data(file, reg);
if (value != 0x00)
{
std::cout << "Value at 0x64 is not 0x00: " << std::hex << (int)value << std::endl;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // 1Hz轮询
}
}
// 用户输入线程函数
void handle_user_input(int file)
{
while (running)
{
__u8 data[2];
std::cout << "Enter two bytes to send to 0x6e (first byte must be 0x00 or 0xFF): ";
std::cin >> std::hex >> (int &)data[0] >> (int &)data[1];
// 检查第一个字节是否为0x00或0xFF
if (data[0] != 0x00 && data[0] != 0xFF)
{
std::cerr << "First byte must be 0x00 or 0xFF!" << std::endl;
continue;
}
{
std::lock_guard<std::mutex> lock(i2c_mutex); // 加锁
__u8 reg = 0x6e;
if (i2c_write_i2c_block_data(file, reg, sizeof(data), data) < 0)
{
std::cerr << "Failed to write to the i2c device" << std::endl;
}
else
{
std::cout << "Data sent successfully: " << std::hex << (int)data[0] << " " << (int)data[1] << std::endl;
}
}
}
}
int main()
{
int file;
const char *filename = "/dev/i2c-2"; // I2C总线设备文件
int addr = 0x34; // I2C设备地址
// 打开I2C设备
if ((file = open(filename, O_RDWR)) < 0)
{
std::cerr << "Failed to open the i2c bus" << std::endl;
return 1;
}
// 设置I2C设备地址
if (ioctl(file, I2C_SLAVE, addr) < 0)
{
std::cerr << "Failed to acquire bus access and/or talk to slave" << std::endl;
close(file);
return 1;
}
// 创建轮询线程和用户输入线程
std::thread poll_thread(poll_i2c_device, file);
std::thread input_thread(handle_user_input, file);
// 等待用户输入线程结束(按Ctrl+C退出)
input_thread.join();
// 停止轮询线程
running = false;
poll_thread.join();
// 关闭I2C设备
close(file);
return 0;
}
-3
View File
@@ -1,3 +0,0 @@
# zebra_demo - 斑马线检测测试
add_executable(zebra_demo zebra_demo.cpp)
target_link_libraries(zebra_demo common_lib ${OpenCV_LIBS})
Binary file not shown.

Before

Width:  |  Height:  |  Size: 894 KiB

-729
View File
@@ -1,729 +0,0 @@
/*
* 斑马线实时检测 — 龙芯2K0300 小车摄像头版 (含逆透视变换 IPM)
* =================================================================
*
* 【设计目的】
* 在车辆行驶过程中实时识别前方斑马线,将检测结果写入标志文件
* 供巡线主控模块 (control.cpp) 读取,做出减速/停车等安全决策。
*
* 【算法来源】
* 移植自 Python 开源项目: https://github.com/TomMao23/ZebraCrossing_Detection
* 利用斑马线的四个视觉特征做传统图像处理检测:
* (1) 梯度一致性 — 黑白交替 → 边缘方向高度集中
* (2) 等间隔 — 线条等距分布 (当前未使用)
* (3) 多根线 — 一个区域内有大量平行边缘
* (4) 宽度大 — 斑马线宽度明显大于车道线
*
* 【处理流水线 (每帧)】
*
* 摄像头 320×240 (MJPG, /dev/video0)
* ↓
* ┌── [IPM] 逆透视变换 ──────────────────────────────┐
* │ 前视图 → 鸟瞰图 (BEV, Bird's Eye View) │
* │ cv::warpPerspective(frame, bev, iM, 640×640) │
* │ 将倾斜的透视角度"拉平",使斑马线恢复矩形形状 │
* │ 矩阵 M 需针对摄像头安装角度/高度单独标定 │
* │ 标定方法:拍摄地面矩形标定板 → 四点透视变换 │
* └──────────────────────────────────────────────────┘
* ↓ if USE_IPM
* 640×640 BEV 图 (已校正透视畸变)
* ↓ cv::resize
* 400×400 正方形 BGR 图
* ↓ extractChannel(0) → 只取 B 通道 (利用蓝色通道衰减黄色减速带)
* medianBlur 5×5 → 去砖缝/椒盐噪声, 保留梯度大小
* ↓
* MORPH_OPEN 3×3 iter=4 → 多次腐蚀去窄车道线, 斑马线保留
* ↓
* MORPH_CLOSE 5×5 iter=3 → 先膨胀填斑马线缺损, 再腐蚀抑制路面箭头碎片
* ↓
* Canny(30, 90) → 双阈值边缘检测, 利用连通性合并弱边缘
* ↓
* Sobel 3×3 → 计算每个边缘像素的梯度模值 Amplitude 和方向 theta(0~90°)
* ↓ 弱边缘过滤 (Amplitude < 30 → 置零)
* 滑动窗口 100×302, 步长 50, 水平范围固定 [39, 341]
* ↓
* 每个窗口内:
* - 统计有效梯度点 (Amplitude > 0, theta ∈ [0, 70°)) 的方向直方图
* - 14 个 bin, 每 bin 5° (0°~5°, 5°~10°, ..., 65°~70°)
* - 取峰值 bin 的点数
* - 峰值 > 1500 → 判为斑马线
* ↓
* 所有阳性窗口的纵向跨度 = 最终斑马线位置 (紫色框)
* ↓
* LCD 叠加显示 + 写入 ./zebra_detected 文件
*
* 【为何需要逆透视变换 (IPM)】
* 前视摄像头拍到的斑马线是透视畸变的 (近大远小、倾斜变形)
* → 斑马线的平行特征被破坏 → 梯度一致性减弱 → 检测效果下降
* IPM 将图像变换为鸟瞰视角 → 斑马线恢复矩形/平行特征
* → Sobel 梯度方向集中在同一角度 → 直方图峰值更高 → 检测更准
*
* 【注意】IPM 矩阵必须针对实车摄像头标定!
* 当前矩阵是 Python 项目原始标定值,在 2K0300 小车上可能不适用。
* 建议先关闭 IPM 测试 (USE_IPM=0),确认检测逻辑正确后,
* 再用标定板重新标定并开启 IPM。
*
* 【为何方向限制在 0~70°】
* 排除水平停止线干扰:
* 停止线 = 横线 → Sobel dx 大 / dy → 0 → 方向接近 0°
* 斑马线 = 纵线/斜线 → 方向偏大 (30°~90°)
* 实际上 bin 0 也在统计范围但峰值不高, 主要靠阈值过滤
*
* 【为何窗口固定水平范围 [39, 341]】
* 排除道路两侧的行人道/建筑/绿化等干扰
* 400 像素宽, 窗口 302 宽, 居中左偏 39px, 右侧留 59px
*
* 【为何单独用蓝色通道而非灰度图】
* BGR 三通道中:
* B 通道 = 蓝色分量
* 黄色减速带 ≈ R+G (无蓝色) → B 通道响应 ≈ 0 → 完全消除减速带边缘
* 白色斑马线 ≈ R+G+B 均等 → B 通道正常响应 → 斑马线边缘保留
* 如果用灰度 (0.299R + 0.587G + 0.114B), 黄色减速带会产生强边缘
*
* 【为何用中值滤波而非高斯/均值滤波】
* 中值滤波最大程度保留原始梯度大小 (不模糊边缘), 同时完美消灭人行道砖缝纹理
* 高斯/均值会平滑边缘 → 梯度幅度衰减 → 影响判定阈值
*
* 【为何先开运算再闭运算】
* 开运算 (先 Erosion 后 Dilation):
* iter=4 次连续腐蚀 → 窄车道线被彻底腐蚀消失
* 后续膨胀 → 恢复剩余斑马线的尺寸
* 等效: 保留 "宽度 > 某阈值" 的线条 (斑马线),去除 "窄的" 线条 (车道线)
* 闭运算 (先 Dilation 后 Erosion):
* 先膨胀 → 填补斑马线内部的微小缺损/裂缝
* 后腐蚀 → 恢复原始尺寸,同时消除箭头碎片的虚假边缘
*
* 【为何用 Canny 而非直接对 Sobel 结果判定】
* (1) Canny 双阈值 + 连通性分析 → 孤立噪点被抑制, 只有真正连续的边缘通过
* (2) Canny 使一条边缘只保留最细的 1px 脊线 → 斑马线 "多根线" 特征更突出
* (直接 Sobel 一根粗边缘产生 N 个梯度点 → 降低了线数的区分度)
* (3) Canny 两个阈值让调参范围更大, 比单梯度阈值更鲁棒
*
* 【2K0300 算力评估 (400×400 分辨率)】
* IPM warpPerspective: O(640×640×常数) ≈ 2.5M
* 蓝色通道提取: O(160K) ≈ 忽略
* medianBlur 5×5: O(160K × 25) ≈ 4M
* morphOpen ×4: O(160K × 9 × 8) ≈ 11.5M
* morphClose ×3: O(160K × 25 × 6) ≈ 24M
* Canny: O(160K × 常数) ≈ 几M
* Sobel ×2: O(160K × 9 × 2) ≈ 2.9M
* 梯度逐像素: O(160K × 10) ≈ 1.6M
* 滑动窗口 (7窗): O(7 × 30200) ≈ 0.2M
* ---------------------------------------------------
* 总计约 50~55M 操作/帧, 2K0300 0.43 GOPS → 理论 7~10 FPS
* 优化: 关闭 IPM 可节省 ~2.5M/帧; 增大 WIN_STEP 减少窗口数
*
* 用法:
* ./zebra_demo 带 LCD 显示 + 检测 (含 IPM)
* ./zebra_demo --no-display 无头模式 (只写状态文件, 省算力)
* ./zebra_demo --no-ipm 关闭逆透视变换 (使用前视图)
*
* 跨模块通信:
* 写入 ./zebra_detected: "1" = 检测到斑马线, "0" = 未检测到
* 控制模块 (control.cpp) 可用 readFlag("./zebra_detected") 读取
* 仅在状态变化时写入, 减少文件 I/O
*
* 退出: 按 Ctrl+C (SIGINT) 或 kill (SIGTERM) 优雅清理资源
*/
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <cmath>
#include <fstream>
#include <csignal>
#include <atomic>
// Linux 特定: 帧缓冲屏 + mmap + ioctl
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
// ============================================================
// 可调参数 — 与 Python 版算法完全对齐
// ============================================================
// --- 逆透视变换 (IPM) ---
// 是否启用 IPM: 0=关闭(使用前视图), 1=开启(使用鸟瞰图)
// 启用前必须用标定板重新标定矩阵 M, 否则效果可能比关闭更差!
#define USE_IPM 1
// IPM 输出鸟瞰图尺寸 (与 Python 版 gd.py 一致)
const int IPM_SIZE = 640;
// --- 滑动窗口 ---
const int WIN_H = 100;
const int WIN_W = 302;
const int WIN_STEP = 50;
const int WIN_COL_L = 39;
const int WIN_COL_R = 341;
// --- 处理分辨率 ---
const int PROC_SIZE = 400;
// --- 梯度过滤 ---
const float GRAD_THRESHOLD = 30.0f;
// --- 方向直方图 ---
const int HIST_BIN_SIZE = 5;
const int HIST_ANGLE_MAX = 70;
const int HIST_BIN_COUNT = 14;
// --- 判定阈值 ---
const int ZEBRA_THRESHOLD = 1500;
// --- 摄像头 ---
const int CAM_WIDTH = 320;
const int CAM_HEIGHT = 240;
// ============================================================
// IPM 逆透视变换矩阵
//
// Python 源码中的变换逻辑:
// M = 原图 → BEV 的 3×3 透视变换矩阵
// iM = M.inv() = BEV → 原图的逆矩阵
//
// xy = 640×640 BEV 坐标网格 (px, py)
// ixy = perspectiveTransform(xy, iM) → 每个 BEV 像素在原图中的坐标
// remap(frame, mapx, mapy) → 采样原图得到 BEV 图像
//
// C++ 等价实现:
// iM = M.inv()
// warpPerspective(frame, bev, iM, Size(640,640), INTER_LINEAR)
//
// warpPerspective 的矩阵含义是 dst→src 映射, 恰好就是 iM
//
// 【标定方法】
// 1. 在小车前方地面放置一个已知尺寸的矩形标定板 (如 A4 纸或棋盘格)
// 2. 拍摄一张包含整个矩形的前视图
// 3. 获取矩形的 4 个角点在原图中的像素坐标 pts_src
// 4. 在 BEV 中定义矩形应有的 4 个角点坐标 pts_dst
// 5. M = cv::getPerspectiveTransform(pts_src, pts_dst)
// 6. 将 M 填入下方矩阵中
//
// 当前矩阵是 Python 项目的原始标定值,需替换为实车标定值!
// ============================================================
// Python 原始矩阵 M (前视图 → BEV), 来源: gd.py:255-257
// 元素按 (row, col) = (0,0) (0,1) (0,2)
// (1,0) (1,1) (1,2)
// (2,0) (2,1) (2,2)
static const double g_M_data[3][3] = {
{-1.86073726e-01, -5.02678929e-01, 4.72322899e+02},
{-1.39150388e-02, -1.50260445e+00, 1.00507430e+03},
{-1.77785988e-05, -1.65517173e-03, 1.00000000e+00}
};
/**
* 计算逆矩阵 (BEV → 前视图), 用于 warpPerspective
* 在启动时调用一次, 结果全局缓存
*/
static cv::Mat build_ipm_inverse_matrix()
{
cv::Mat M(3, 3, CV_64F);
for (int r = 0; r < 3; ++r)
for (int c = 0; c < 3; ++c)
M.at<double>(r, c) = g_M_data[r][c];
// iM = M.inv(): BEV 坐标 → 原始图像坐标
// warpPerspective 接受 dst→src 映射, 即 iM
cv::Mat iM = M.inv();
return iM;
}
// 全局变量: 在 main() 中计算一次, 之后每帧复用
static cv::Mat g_iM;
// ============================================================
// 全局状态 — 优雅退出机制
// ============================================================
static std::atomic<bool> g_running{true};
static void signal_handler(int)
{
g_running.store(false);
}
// ============================================================
// apply_ipm — 逆透视变换: 前视图 → 鸟瞰图 (BEV)
//
// 输入: 原始摄像头帧 (320×240 或任意分辨率)
// 预计算的逆矩阵 g_iM (BEV→原图)
// 输出: 640×640 鸟瞰图 (消除透视畸变, 斑马线恢复矩形)
//
// 等价于 Python 版:
// remap(frame, mapx, mapy, INTER_LINEAR) // 预计算映射表
//
// 为什么选 640×640 输出?
// 1. BEV 鸟瞰需要比原图更大的空间来容纳"拉平"后的路面
// 2. 640 是 2 的幂次方的近似值, 对 resize 和后续操作友好
// 3. Python 版使用此尺寸, 算法参数基于此调优
//
// 如果不开启 IPM, 此函数会被跳过, 直接用前视图进入处理管线
// ============================================================
static cv::Mat apply_ipm(const cv::Mat &frame)
{
cv::Mat bev;
cv::warpPerspective(frame, bev, g_iM,
cv::Size(IPM_SIZE, IPM_SIZE),
cv::INTER_LINEAR);
// warpPerspective 使用 INTER_LINEAR 双线性插值,
// 比 INTER_NEAREST 最近邻更平滑, 避免产生锯齿状伪边缘
return bev;
}
// ============================================================
// preprocess — 图像预处理 (蓝色通道 + 滤波 + 形态学)
//
// 输入: BGR 彩色图 (400×400)
// 输出: 预处理后的单通道图 (实际是蓝色通道处理结果)
//
// 五步处理:
// [1] extractChannel(bgr, blue, 0)
// 取出 B 通道 — 关键设计!
// 黄色减速带在 B 通道中亮度极低 (黄色≈R+G, 无B)
// 白色斑马线在 B 通道中亮度正常 (白色≈R=G=B)
// → B 通道天然过滤减速带, 保留斑马线
//
// [2] medianBlur(blue, 5)
// 中值滤波核大小=5
// 替代像素值为邻域中值 → 椒盐噪声/砖缝等独立噪点直接抹除
// 与均值滤波不同: 中值保持边缘处的实际梯度值不衰减
//
// [3] morphologyEx(MORPH_OPEN, 3×3, iter=4)
// 开运算 = Erode ×4 → Dilate ×4
// 3×3 矩形核较小, 但 4 次迭代累积效果强:
// - 窄车道线 (宽度 < 8px) → 在连续腐蚀中被完全消除
// - 宽斑马线 (宽度 >> 8px) → 腐蚀后仍有残留 → 膨胀恢复
// 本质: 保留"粗线条", 删除"细线条"
//
// [4] morphologyEx(MORPH_CLOSE, 5×5, iter=3)
// 闭运算 = Dilate ×3 → Erode ×3
// 5×5 较大核, 3 次迭代:
// - 先膨胀填平斑马线内部的小缺口/裂缝 (缺损斑马线恢复)
// - 后腐蚀恢复原始尺寸, 同时消除路面箭头碎片
// 注意: 如果闭运算过强, 会把相邻的细斑马线粘成一片 → iter=3 是经验平衡点
// ============================================================
static cv::Mat preprocess(const cv::Mat &bgr)
{
cv::Mat blue;
// [1] 提取 B 通道 (蓝底色 + 减弱黄减速带)
cv::extractChannel(bgr, blue, 0);
// [2] 中值滤波 5×5 → 去砖缝纹理/椒盐噪声/虚假小梯度点
cv::medianBlur(blue, blue, 5);
// [3] 开运算: 3×3 矩形核, 4 次迭代 → 去除细窄车道线
cv::Mat kernel_open = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
cv::morphologyEx(blue, blue, cv::MORPH_OPEN, kernel_open, cv::Point(-1, -1), 4);
// [4] 闭运算: 5×5 矩形核, 3 次迭代 → 填补缺损 + 抑制箭头碎片
cv::Mat kernel_close = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
cv::morphologyEx(blue, blue, cv::MORPH_CLOSE, kernel_close, cv::Point(-1, -1), 3);
return blue;
}
// ============================================================
// compute_gradient — Sobel 边缘梯度模值和方向
//
// 输入: Canny 边缘检测结果 (单通道, 像素值 0 或 255)
// 输出: Amplitude — 梯度模值 (浮点, 弱边缘 < 30 置零)
// theta — 梯度方向 0~90° (浮点)
//
// 方向定义:
// 对每个像素 (r, c) 计算 Sobel 梯度:
// gx = ∂I/∂c → 水平方向的变化率
// gy = ∂I/∂r → 垂直方向的变化率
//
// Amplitude = sqrt(gx² + gy²) → 边缘强度
// theta = atan2(|gy|, |gx|) → 边缘方向 (0~90°)
//
// 其中 atan2 取 abs 意味着不区分 "从左到右" 和 "从右到左" 的边缘,
// 也不区分 "白→黑" 和 "黑→白" 的方向, 统一归到 0~90°。
// 这对斑马线检测是合适的 — 我们只关心边缘的方向一致性, 不关心极性。
//
// 方向参考:
// 0° = 纯水平边缘 (dy→0, dx 大)
// 45° = 135° 角斜线
// 90° = 纯垂直边缘 (dx→0, dy 大)
// 斑马线纵线 ≈ 70~90°, 斜线 ≈ 30~60°
//
// 弱边缘过滤:
// Canny 之后仍有少量噪声梯度 (<30)
// 直接置零 → 后续直方图统计不参与
// ============================================================
static void compute_gradient(const cv::Mat &canny,
cv::Mat &amplitude, cv::Mat &theta)
{
cv::Mat dx, dy;
// Sobel 算子 3×3:
// dx = [-1 0 1; -2 0 2; -1 0 1] (x方向导数)
// dy = [-1 -2 -1; 0 0 0; 1 2 1] (y方向导数)
// CV_32F = 32 位浮点, 保留梯度方向亚度数精度
cv::Sobel(canny, dx, CV_32F, 1, 0, 3);
cv::Sobel(canny, dy, CV_32F, 0, 1, 3);
// 初始化输出矩阵为全零
amplitude = cv::Mat::zeros(canny.size(), CV_32F);
theta = cv::Mat::zeros(canny.size(), CV_32F);
// 逐像素计算模值和方向
// 逐像素循环对于 400×400 = 160K 像素来说不是瓶颈,
// 瓶颈在 medianBlur 和 morphologyEx 的核运算上
for (int r = 0; r < canny.rows; ++r)
{
for (int c = 0; c < canny.cols; ++c)
{
float gx = dx.at<float>(r, c);
float gy = dy.at<float>(r, c);
float amp = std::sqrt(gx * gx + gy * gy);
float ang = std::atan2(std::abs(gy), std::abs(gx) + 1e-10f)
* 180.0f / static_cast<float>(CV_PI);
// 弱边缘置零 (不参与后续方向统计)
amplitude.at<float>(r, c) = (amp >= GRAD_THRESHOLD) ? amp : 0.0f;
theta.at<float>(r, c) = ang;
}
}
}
// ============================================================
// is_zebra_window — 对单个滑动窗口做斑马线分类
//
// 输入: 窗口内的梯度模值图 (amp_win) 和方向图 (ang_win)
// 输出: true = 斑马线, false = 背景
//
// 判定逻辑:
// [1] 扫描窗口内每个像素
// - 跳过 amp ≤ 0 的像素 (弱边缘/噪声/平滑区域)
// - 跳过 ang ≥ 70° 的像素 (方向在统计范围外)
//
// [2] 统计方向直方图 hist[0..13]
// hist[k] = 方向在 [k*5, k*5+5) 的有效梯度点数量
// e.g. hist[0] = 方向 0~4.999° 的点数
//
// [3] 取峰值 bin 的点数 = peak
// peak 反映了窗口内 "某个特定方向上" 有多少条平行的边缘线
//
// [4] peak > ZEBRA_THRESHOLD (1500) → 斑马线
// 斑马线 = 同一方向上大量平行边缘 → peak 极高
// 普通路面 = 边缘方向杂乱 → 峰值被分散到不同 bin → 各 bin 点数低
//
// 在 IPM 开启时, 鸟瞰图将斑马线恢复为垂直矩形
// → 所有边缘方向集中在 90° 附近 → hist[13] 极高 → 检测更灵敏
// ============================================================
static bool is_zebra_window(const cv::Mat &amp_win, const cv::Mat &ang_win)
{
int hist[HIST_BIN_COUNT] = {0};
for (int r = 0; r < amp_win.rows; ++r)
{
for (int c = 0; c < amp_win.cols; ++c)
{
float amp = amp_win.at<float>(r, c);
if (amp <= 0.0f)
continue;
float ang = ang_win.at<float>(r, c);
if (ang < 0.0f || ang >= HIST_ANGLE_MAX)
continue;
int bin = static_cast<int>(ang) / HIST_BIN_SIZE;
if (bin >= 0 && bin < HIST_BIN_COUNT)
hist[bin]++;
}
}
int peak = 0;
for (int i = 0; i < HIST_BIN_COUNT; ++i)
if (hist[i] > peak)
peak = hist[i];
return peak > ZEBRA_THRESHOLD;
}
// ============================================================
// 帧缓冲显示 — 将 OpenCV BGR Mat 转为 RGB565 格式直写 /dev/fb0
//
// 硬件: SPI LCD 通过 /dev/fb0 (RGB565 格式)
//
// RGB565 编码:
// 16 位: [R4 R3 R2 R1 R0] [G5 G4 G3] [G2 G1 G0] [B4 B3 B2 B1 B0]
// R 取 高 5 位 (>>3, &0xF8)
// G 取 高 6 位 (>>2, &0xFC)
// B 取 高 5 位 (>>3)
// ============================================================
static uint16_t convertRGBToRGB565(uint8_t r, uint8_t g, uint8_t b)
{
return ((r & 0xF8) << 8) |
((g & 0xFC) << 3) |
(b >> 3);
}
static void display_on_fb(const cv::Mat &bgr, int /* fb_fd */,
uint16_t *fb_buf, int scr_w, int scr_h)
{
cv::Mat display;
cv::resize(bgr, display, cv::Size(scr_w, scr_h));
for (int y = 0; y < scr_h; ++y)
{
for (int x = 0; x < scr_w; ++x)
{
cv::Vec3b pixel = display.at<cv::Vec3b>(y, x);
fb_buf[y * scr_w + x] = convertRGBToRGB565(
pixel[2], pixel[1], pixel[0]);
}
}
}
// ============================================================
// main — 初始化 + 主检测循环 + 清理
// ============================================================
int main(int argc, char *argv[])
{
// ---- 命令行参数解析 ----
bool no_display = false;
bool use_ipm = (USE_IPM != 0); // 默认跟随编译宏
for (int i = 1; i < argc; ++i)
{
if (std::strcmp(argv[i], "--no-display") == 0)
no_display = true;
else if (std::strcmp(argv[i], "--no-ipm") == 0)
use_ipm = false;
}
// ---- 注册信号处理 ----
std::signal(SIGINT, signal_handler);
std::signal(SIGTERM, signal_handler);
// ---- 预计算 IPM 逆矩阵 (启用时) ----
if (use_ipm)
{
g_iM = build_ipm_inverse_matrix();
printf("IPM 逆透视变换: 已启用 (%dx%d BEV)\n", IPM_SIZE, IPM_SIZE);
printf(" 注意: 矩阵未针对实车标定, 效果可能不佳\n");
}
else
{
printf("IPM 逆透视变换: 已关闭 (使用前视图)\n");
}
// ========================================================
// 第 1 步: 打开 USB 摄像头
// ========================================================
cv::VideoCapture cap;
cap.open(0, cv::CAP_V4L2);
if (!cap.isOpened())
{
cap.open(0);
if (!cap.isOpened())
{
std::cerr << "无法打开摄像头 /dev/video0" << std::endl;
return 1;
}
}
cap.set(cv::CAP_PROP_FRAME_WIDTH, CAM_WIDTH);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, CAM_HEIGHT);
cap.set(cv::CAP_PROP_FOURCC,
cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));
printf("摄像头已打开 %dx%d\n",
(int)cap.get(cv::CAP_PROP_FRAME_WIDTH),
(int)cap.get(cv::CAP_PROP_FRAME_HEIGHT));
// ========================================================
// 第 2 步: 打开帧缓冲 /dev/fb0 → mmap 映射到用户空间
// ========================================================
int fb_fd = -1;
uint16_t *fb_buf = nullptr;
int scr_w = 0, scr_h = 0;
if (!no_display)
{
fb_fd = open("/dev/fb0", O_RDWR);
if (fb_fd < 0)
{
std::cerr << "警告: 无法打开 /dev/fb0,切换为无头模式" << std::endl;
no_display = true;
}
else
{
struct fb_var_screeninfo vinfo;
if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo) < 0)
{
std::cerr << "无法获取屏幕信息" << std::endl;
close(fb_fd);
no_display = true;
}
else
{
scr_w = vinfo.xres;
scr_h = vinfo.yres;
size_t fb_size = vinfo.yres_virtual * vinfo.xres_virtual
* vinfo.bits_per_pixel / 8;
fb_buf = (uint16_t *)mmap(nullptr, fb_size,
PROT_READ | PROT_WRITE,
MAP_SHARED, fb_fd, 0);
if (fb_buf == MAP_FAILED)
{
std::cerr << "无法映射帧缓冲区" << std::endl;
close(fb_fd);
no_display = true;
}
else
{
printf("屏幕分辨率: %dx%d\n", scr_w, scr_h);
}
}
}
}
// ========================================================
// 第 3 步: 主循环
// ========================================================
cv::Mat frame, bev, proc, gray, canny, amplitude, theta, result;
int frame_count = 0;
int zebra_frames = 0;
bool was_zebra = false;
printf("\n开始斑马线检测 (按 Ctrl+C 退出)...\n");
while (g_running.load())
{
// ---- 3a. 摄像头采集一帧 ----
if (!cap.read(frame) || frame.empty())
{
usleep(5000);
continue;
}
// ---- 3b. [IPM] 逆透视变换: 前视图 → 鸟瞰图 ----
// 将倾斜透视拉平为俯瞰视角, 让斑马线恢复矩形平行特征
// 未启用 IPM 时直接使用原始帧
if (use_ipm)
{
bev = apply_ipm(frame);
}
else
{
bev = frame; // 使用前视图
}
// ---- 3c. 尺寸归一化: BEV/原图 → 400×400 ----
cv::resize(bev, proc, cv::Size(PROC_SIZE, PROC_SIZE));
// ---- 3d. 预处理: 蓝色通道 + 中值滤波 + 形态学 ----
gray = preprocess(proc);
// ---- 3e. Canny 边缘检测 (双阈值 30/90) ----
cv::Canny(gray, canny, 30, 90, 3);
// ---- 3f. 梯度模值与方向 ----
compute_gradient(canny, amplitude, theta);
// ---- 3g. 滑动窗口逐窗检测 ----
int img_h = proc.rows;
bool detected = false;
int z_ymin = img_h;
int z_ymax = 0;
for (int top = 0; top <= img_h - WIN_H; top += WIN_STEP)
{
cv::Rect roi(WIN_COL_L, top, WIN_W, WIN_H);
if (is_zebra_window(amplitude(roi), theta(roi)))
{
detected = true;
if (top < z_ymin) z_ymin = top;
if (top + WIN_H > z_ymax) z_ymax = top + WIN_H;
}
}
// ---- 3h. 写入状态文件 (仅在状态变化时写) ----
if (detected != was_zebra)
{
std::ofstream ofs("./zebra_detected", std::ios::trunc);
ofs << (detected ? "1" : "0");
was_zebra = detected;
}
// ---- 3i. 绘制调试叠加图 ----
result = proc.clone();
for (int top = 0; top <= img_h - WIN_H; top += WIN_STEP)
{
cv::line(result, cv::Point(WIN_COL_L, top),
cv::Point(WIN_COL_R, top),
cv::Scalar(128, 128, 128), 1);
}
if (detected)
{
cv::rectangle(result,
cv::Rect(WIN_COL_L, z_ymin, WIN_W, z_ymax - z_ymin),
cv::Scalar(255, 0, 255), 3);
}
if (detected)
{
cv::putText(result, "ZEBRA CROSSING!",
cv::Point(10, 30),
cv::FONT_HERSHEY_SIMPLEX, 0.8,
cv::Scalar(0, 0, 255), 2);
}
// ---- 3j. LCD 显示 ----
if (!no_display && fb_buf)
display_on_fb(result, fb_fd, fb_buf, scr_w, scr_h);
// ---- 3k. 统计 ----
frame_count++;
if (detected) zebra_frames++;
if (frame_count % 30 == 0)
{
printf("帧 %d | 斑马线: %s | 累计命中: %d/%d | IPM: %s\n",
frame_count,
detected ? "YES" : "NO ",
zebra_frames, frame_count,
use_ipm ? "ON" : "OFF");
}
}
// ========================================================
// 第 4 步: 清理
// ========================================================
cap.release();
if (!no_display && fb_buf && fb_buf != MAP_FAILED)
{
struct fb_var_screeninfo vinfo;
if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo) == 0)
{
size_t fb_size = vinfo.yres_virtual * vinfo.xres_virtual
* vinfo.bits_per_pixel / 8;
munmap(fb_buf, fb_size);
}
close(fb_fd);
}
printf("\n退出。共处理 %d 帧,命中 %d 帧 (%.1f%%), IPM: %s\n",
frame_count, zebra_frames,
frame_count > 0 ? (100.0 * zebra_frames / frame_count) : 0.0,
use_ipm ? "ON" : "OFF");
return 0;
}