257 lines
9.3 KiB
Markdown
257 lines
9.3 KiB
Markdown
# 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` |
|