09 - 超声波测距应用
本章节介绍 Pico-G1 扩展板上的超声波测距应用示例 —— sonic_display。该应用演示了如何使用 GPIO 接口驱动 HC-SR04 超声波模块进行距离测量,并通过距离值控制RGB LED显示不同颜色(类似倒车雷达),同时在 TFT 屏幕上实时显示距离值和状态。这是学习 GPIO 定时测量、脉宽检测和多GPIO协同控制的进阶示例。
应用源码位于 SDK 目录 source/app/09_sonic_display/,提供了完整的GPIO脉宽测量和RGB灯控制实现,是学习嵌入式传感器应用和状态机编程的重要参考。
1 应用概述
1.1 功能特性
- 超声波测距:通过 GPIO 接口驱动 HC-SR04 模块进行精确距离测量
- 脉宽测量:使用高精度时钟测量 ECHO 脉宽,计算距离
- RGB倒车雷达:根据距离值自动控制RGB LED显示不同颜色
- 实时显示:在 TFT 屏幕上显示距离值、状态和颜色说明
- 智能颜色算法:
- 蓝色:无回波/超量程(< 0cm 或 > 4m)
- 红色:很近距离(< 20cm,危险警告)
- 黄色:较近距离(20~50cm,注意提醒)
- 绿色:安全距离(≥ 50cm)
- 超时保护:30ms 超时保护避免测量卡死
1.2 技术参数
| 参数 | 值 |
|---|---|
传感器型号 | HC-SR04(兼容 JSN-SR04T) |
TRIG 接口 | GPIO6_7(触发脉冲输出) |
ECHO 接口 | GPIO7_0(脉宽输入) |
RGB_R 接口 | GPIO6_6(红色LED) |
RGB_G 接口 | GPIO6_5(绿色LED) |
RGB_B 接口 | GPIO5_3(蓝色LED) |
测距范围 | 2cm~4m |
测量精度 | ±3mm |
触发脉冲 | ≥10μs(本驱动20μs) |
刷新频率 | 300ms(可配置) |
TFT 显示 | ST7789 240×240 |
1.3 测试用例列表
| index | 名称 | 测试指令 | 预期现象(成功) | 失败可能原因 |
|---|---|---|---|---|
| 1 | 基础测距 | ./sonic_display | 屏幕显示距离值,RGB灯根据距离变色 | 超声波连接失败、GPIO配置错误 |
| 2 | 距离测试 | 在不同距离放置障碍物 | 距离值准确,RGB灯颜色正确 | 测量精度偏差、ECHO分压不当 |
| 3 | RGB测试 | 手动遮挡超声波传感器 | RGB灯显示蓝色(无回波) | RGB GPIO配置错误 |
| 4 | 超程测试 | 测量 > 4m 的距离 | 显示"out of range"状态 | 正常现象,符合设计 |
1.4 目录结构
source/app/09_sonic_display/
├── Makefile # 构建脚本
├── main.c # 主程序
├── sonic.c # 超声波驱动实现
├── sonic.h # 超声波驱动头文件
├── rgb.c # RGB LED 控制实现
├── rgb.h # RGB LED 控制头文件
├── gpio_hal.c # GPIO HAL 层实现
├── gpio_hal.h # GPIO HAL 层头文件
├── spi_hal.c # SPI HAL 层实现
├── spi_hal.h # SPI HAL 层头文件
├── st7789.c # ST7789 驱动实现
├── st7789.h # ST7789 驱动头文件
├── font8x16.h # 8×16 ASCII 点阵字库
└── README.md # 说明文档2 硬件连接说明
2.1 引脚定义
| 信号 | 板上 GPIO | 节点 | 说明 |
|---|---|---|---|
| TRIG | GPIO6_7 | /dev/gpiochip6 line7 | 触发脉冲输出(≥10μs) |
| ECHO | GPIO7_0 | /dev/gpiochip7 line0 | 回波脉宽输入(需分压) |
| RGB_R | GPIO6_6 | /dev/gpiochip6 line6 | 红色LED控制(共阴) |
| RGB_G | GPIO6_5 | /dev/gpiochip6 line5 | 绿色LED控制(共阴) |
| RGB_B | GPIO5_3 | /dev/gpiochip5 line3 | 蓝色LED控制(共阴) |
| VCC | 5V | — | HC-SR04 必须使用 5V 供电 |
| GND | GND | — | 地线 |
2.2 硬件电路
超声波模块接线:
Pico-G1 HC-SR04 Module
┌───────────┐ ┌──────────────┐
│ │ │ │
│ GPIO6_7 ──┼────── TRIG ───┤ INT │
│ │ │ │
│ GPIO7_0 ──┼────── ECHO ────┤ ECHO │
│ │ │ │
│ 5V ────┼─────────────┤ VCC │
│ │ │ │
│ GND ───┼─────────────┤ GND │
│ │ │ VCC ─── GND │
└───────────┘ └──────────────┘RGB LED接线(共阴极):
Pico-G1 RGB LED Module
┌───────────┐ ┌──────────────┐
│ │ │ │
│ GPIO6_6 ──┼────── R ─────┤ │
│ │ │ │
│ GPIO6_5 ──┼────── G ─────┤ │
│ │ │ │
│ GPIO5_3 ──┼────── B ─────┤ │
│ │ │ ┌──────┤ │
│ 3.3V ───┼─────────────┤ GND │ │ │
│ │ └─────┘ │ │
└───────────┘ └──────────────┘ │
公共阴极ECHO 信号分压警告
HC-SR04 的 ECHO 引脚输出 5V 高电平,必须分压到 3.3V:
- 建议分压:
ECHO → 10kΩ → GPIO7_0 → 20kΩ → GND - 如果使用 3.3V 版本超声波模块(如 JSN-SR04T),ECHO 可直接连接
RGB LED 共阴极连接
- 共阴极:GPIO 输出高电平时点亮,输出低电平时熄灭
- 本示例使用共阴极RGB LED,公共脚接 GND
- 颜色组合:红+绿=黄、红+蓝=紫、绿+蓝=青、红+绿+蓝=白
2.3 引脚复用说明
需要配置的引脚复用:
| pad | 物理地址 | 配置值 | 说明 |
|---|---|---|---|
| TRIG (GPIO6_7) | 0x100C0068 | 0x1000 | func0(GPIO) |
| ECHO (GPIO7_0) | 0x100C006C | 0x1000 | func0(GPIO) + 输入使能 |
| RGB_R (GPIO6_6) | 0x100C0064 | 0x1000 | func0(GPIO) |
| RGB_G (GPIO6_5) | 0x100C0060 | 0x1000 | func0(GPIO) |
| RGB_B (GPIO5_3) | 0x100C0030 | 0x1000 | func0(GPIO) |
所有引脚默认为 GPIO 功能,无需修改 IOCFG 寄存器。
3 编译与部署
3.1 前置条件
在编译本应用之前,请确保已完成以下准备工作:
- SDK 环境已搭建:参照 开发环境搭建 完成交叉编译工具链和 SDK 配置
- 硬件已连接:HC-SR04 模块和 RGB LED 正确连接到相应 GPIO 引脚
3.2 编译应用
# 设置工具链路径
export PATH=$PATH:<SDK>/tools/linux/toolchains/arm-gcc12.2.0-linux-uclibceabi/bin
# 进入示例目录
cd <SDK>/source/app/09_sonic_display
# 编译
make
# 清理
make clean3.3 部署到板端
# 使用 SCP 传输到开发板
scp sonic_display root@<板端IP>:/usr/bin/
# 或通过 TFTP 下载
tftp -g -r sonic_display <板端IP>3.4 运行应用
# 添加执行权限
chmod +x /usr/bin/sonic_display
# 运行超声波测距示例
/usr/bin/sonic_display应用启动后,TFT 屏幕将显示距离值,RGB LED 根据距离显示不同颜色,每 300ms 刷新一次,按 Ctrl+C 退出。
3.5 预期输出
控制台输出
/mnt # ./sonic_display
[sonic] pad 复用:GPIO6_7/GPIO7_0 -> func0(GPIO)
[sonic] pad 0x100C0068 -> 0x00001000
[sonic] pad 0x100C006C -> 0x00001000
[sonic] pad 0x100C0064 -> 0x00001000
[sonic] pad 0x100C0060 -> 0x00001000
[sonic] pad 0x100C0038 -> 0x00001000
[sonic] 初始化 SPI 屏(/dev/spidev2.0)...
[spi] pad 0x100C0028 -> 0x00001004
[spi] pad 0x100C002C -> 0x00001004
[spi] pad 0x100C0030 -> 0x00001000
[spi] pad 0x100C0020 -> 0x00001005
[spi] pad 0x100C001C -> 0x00001005
[spi] opening /dev/spidev2.0 ...
[spi] spidev opened, fd=3
[spi] spidev mode/bits/speed set (MODE3/8b/24MHz)
[spi] chardev request DC @ /dev/gpiochip4 line 5 ...
[spi] chardev request RES @ /dev/gpiochip4 line 4 ...
[spi] chardev request CS @ /dev/gpiochip5 line 1 ...
[spi] chardev-verify: DC=0(expect0) RES=1(expect1) CS=1(expect1) ==> OK(chardev 真驱动了引脚)
[spi] spi_hal_init done
[tft] init: SLPOUT
[tft] init: SLPOUT +120ms ok
[tft] init: config cmds ok
[tft] init: DISPON
[tft] init: DISPON ok
[tft] init: clear-flush start
[tft] flush #1 start
[tft] flush #1 done
[tft] init: clear-flush done
[sonic] 初始化 HC-SR04(TRIG/ECHO)...
[sonic] TRIG=/dev/gpiochip6 line7, ECHO=/dev/gpiochip7 line0 就绪。
[sonic] 初始化 RGB(R=6_6 G=6_5 B=5_3)...
[rgb] R=GPIO6_6, G=GPIO6_5, B=GPIO5_3 就绪(共阴)。
[sonic] 测距中,每 80ms 刷新,Ctrl+C 退出。
[sonic] 2.9 cmTFT 屏幕显示

不同距离的RGB显示:
| 距离范围 | RGB颜色 | TFT显示 | 说明 |
|---|---|---|---|
| < 0cm | 蓝色 | BLUE - 无回波 | 超量程或无回波 |
| 0~20cm | 红色 | RED - 危险 | 很近距离,警告 |
| 20~50cm | 黄色 | YELLOW - 注意 | 较近距离,提醒 |
| ≥ 50cm | 绿色 | GREEN - 安全 | 安全距离 |
固定与可变部分说明
- 固定部分:显示格式和布局(对于固定的代码)
- 可变部分:距离数值和RGB颜色状态(每次刷新都会更新)
4 RGB 倒车雷达功能
4.1 颜色算法实现
RGB LED 根据距离值自动显示不同颜色,类似汽车倒车雷达:
// 距离→颜色阈值(cm)
#define RGB_NEAR_RED_CM 20.0f /* < 20cm 显示红色 */
#define RGB_MID_YELLOW_CM 50.0f /* < 50cm 显示黄色 */
#define RGB_SAFE_GREEN_CM 50.0f /* ≥ 50cm 显示绿色 */
const char *rgb_by_distance(float d_cm)
{
if (d < 0.0f) {
rgb_set(0, 0, 1); /* 蓝:无回波/超量程 */
return "BLUE";
}
if (d < RGB_NEAR_RED_CM) {
rgb_set(1, 0, 0); /* 红:很近 */
return "RED";
}
if (d < RGB_MID_YELLOW_CM) {
rgb_set(1, 1, 0); /* 黄:较近 */
return "YELLOW";
}
rgb_set(0, 1, 0); /* 绿:安全 */
return "GREEN";
}4.2 RGB 引脚配置
RGB LED 使用3个GPIO控制红绿蓝三色:
// RGB GPIO 句柄结构
static gpio_handle_t g_r = {
.chip_path = "/dev/gpiochip6",
.line_offset = 6, // GPIO6_6 = R
.gpio_mode = GPIOHANDLE_REQUEST_OUTPUT,
.default_value = 0,
.consumer_label = "rgb-r",
};
static gpio_handle_t g_g = {
.chip_path = "/dev/gpiochip6",
.line_offset = 5, // GPIO6_5 = G
.gpio_mode = GPIOHANDLE_REQUEST_OUTPUT,
.default_value = 0,
.consumer_label = "rgb-g",
};
static gpio_handle_t g_b = {
.chip_path = "/dev/gpiochip5",
.line_offset = 3, // GPIO5_3 = B
.gpio_mode = GPIOHANDLE_REQUEST_OUTPUT,
.default_value = 0,
.consumer_label = "rgb-b",
};4.3 颜色组合逻辑
共阴极RGB LED的颜色组合:
| R | G | B | 结果颜色 | 十六进制 |
|---|---|---|---|---|
| 0 | 0 | 0 | 灭 | 0x000000 |
| 1 | 0 | 0 | 红 | 0xFF0000 |
| 0 | 1 | 0 | 绿 | 0x00FF00 |
| 0 | 0 | 1 | 蓝 | 0x0000FF |
| 1 | 1 | 0 | 黄 | 0xFFFF00 |
| 1 | 0 | 1 | 紫 | 0xFF00FF |
| 0 | 1 | 1 | 青 | 0x00FFFF |
| 1 | 1 | 1 | 白 | 0xFFFFFF |
5 测距原理详解
5.1 超声波测距原理
HC-SR04 超声波测距采用脉冲回波原理:
- 触发阶段:TRIG 引脚输出≥10μs 高电平触发脉冲
- 发射阶段:模块内部发射8个40kHz 超声波(8个脉冲,约40μs总时长)
- 接收阶段:ECHO 引脚拉高,表示声波已发出
- 回波阶段:收到障碍物反射的回波后,ECHO 引脚拉低
- 计算阶段:测量 ECHO 高电平持续时间,计算距离
距离计算公式:
距离(cm) = ECHO 高电平时间(μs) / 58说明:声速约为 340m/s,往返1cm需要约58μs。
5.2 脉宽测量实现
使用高精度时钟进行脉宽测量:
// 测量 ECHO 脉宽
struct timespec start, current;
int pulse_width_us;
// 等待 ECHO 拉高
while (gpio_get_value(ECHO_GPIO) == 0) {
clock_gettime(CLOCK_MONOTONIC_RAW, &start);
if (is_timeout(start, 30000)) return -1; // 30ms 超时保护
}
// 记录开始时间
clock_gettime(CLOCK_MONOTONIC_RAW, &start);
// 等待 ECHO 拉低
while (gpio_get_value(ECHO_GPIO) == 1) {
clock_gettime(CLOCK_MONOTONIC_RAW, ¤t);
if (is_timeout(start, 30000)) return -1; // 30ms 超时保护
}
// 计算脉宽(微秒)
pulse_width_us = (current.tv_sec - start.tv_sec) * 1000000 +
(current.tv_nsec - start.tv_nsec) / 1000;
// 计算距离
float distance_cm = pulse_width_us / 58.0f;5.3 超时保护机制
为防止测量卡死,设置30ms超时保护:
static int is_timeout(struct timespec start, int timeout_ms)
{
struct timespec current;
clock_gettime(CLOCK_MONOTONIC_RAW, ¤t);
long elapsed_us = (current.tv_sec - start.tv_sec) * 1000000 +
(current.tv_nsec - start.tv_nsec) / 1000;
return elapsed_us >= (timeout_ms * 1000);
}
// 在等待循环中使用
while (gpio_get_value(ECHO_GPIO) == 0) {
clock_gettime(CLOCK_MONOTONIC_RAW, &start);
if (is_timeout(start, 30000)) {
return -1; // 30ms 超时
}
}6 关键编程要点
6.1 GPIO 输入输出操作
TRIG 触发脉冲:
// 输出20μs触发脉冲
gpio_set_value(TRIG_GPIO, 1); // 拉高
usleep(20); // 保持20μs
gpio_set_value(TRIG_GPIO, 0); // 拉低ECHO 脉宽读取:
// 读取GPIO输入状态
int echo_state = gpio_get_value(ECHO_GPIO);6.2 RGB LED 多GPIO控制
同时控制3个GPIO:
void rgb_set(int r, int g, int b)
{
gpio_set_value(&g_r, r ? 1 : 0);
gpio_set_value(&g_g, g ? 1 : 0);
gpio_set_value(&g_b, b ? 1 : 0);
}初始化RGB GPIO:
int rgb_init(void)
{
if (gpio_handle_init(&g_r) < 0) return -1;
if (gpio_handle_init(&g_g) < 0) return -1;
if (gpio_handle_init(&g_b) < 0) return -1;
rgb_off(); // 初始关闭所有LED
return 0;
}6.3 误差处理与滤波
多次测量取中值:
#define SAMPLE_COUNT 5
float measure_distance_filtered(void)
{
float samples[SAMPLE_COUNT];
// 连续测量5次
for (int i = 0; i < SAMPLE_COUNT; i++) {
samples[i] = sonic_measure_once();
usleep(50000); // 间隔50ms
}
// 排序取中值
qsort(samples, SAMPLE_COUNT, sizeof(float), cmp_float);
return samples[SAMPLE_COUNT / 2];
}7 常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 显示 "no echo" | 前方无障碍、太近(<2cm)、ECHO未连接或分压不当 | 在10~50cm范围内测试,检查ECHO连接和分压电路 |
| 显示 "out of range" | 障碍物超出4m范围或声波发散 | 移近障碍物、确保反射面材质合适 |
| RGB灯不亮 | GPIO配置错误、RGB LED未初始化 | 检查GPIO6_6/6_5/5_3配置、确认rgb_init()执行 |
| RGB颜色错误 | 颜色算法参数设置不当 | 检查RGB_NEAR_RED_CM和RGB_MID_YELLOW_CM阈值 |
| 数值抖动大 | 用户态轮询抖动、近距离测量误差 | 增加滤波算法、避免近距离测量 |
| 程序卡死 | ECHO一直无信号、超时保护失效 | 检查超声波模块供电和连接 |
测量距离建议
为获得最佳测量效果:
- 障碍物面积建议 ≥ 10cm × 10cm
- 测量距离建议在 10cm ~ 300cm 范围内
- 障碍物表面建议为平面或轻微弧面
- 避免吸音材料(如海绵、泡沫)作为障碍物
8 进阶功能
8.1 实现数据滤波
#define FILTER_WINDOW 5
typedef struct {
float buffer[FILTER_WINDOW];
int index;
} DistanceFilter;
float filter_add_sample(DistanceFilter *f, float new_value)
{
f->buffer[f->index] = new_value;
f->index = (f->index + 1) % FILTER_WINDOW;
// 排序取中值
float temp[FILTER_WINDOW];
memcpy(temp, f->buffer, sizeof(temp));
qsort(temp, FILTER_WINDOW, sizeof(float), cmp_float);
return temp[FILTER_WINDOW / 2];
}8.2 倒车雷达多级告警
typedef enum {
ALERT_SAFE = 0, // ≥ 50cm 绿色
ALERT_ATTENTION, // 20~50cm 黄色
ALERT_DANGER, // 10~20cm 黄红闪烁
ALERT_CRITICAL, // < 10cm 红色闪烁
} alert_level_t;
alert_level_t get_alert_level(float distance)
{
if (distance < 10.0f) return ALERT_CRITICAL;
if (distance < 20.0f) return ALERT_DANGER;
if (distance < 50.0f) return ALERT_ATTENTION;
return ALERT_SAFE;
}8.3 多个超声波传感器
实现多传感器数据融合:
#define SONIC_SENSOR_COUNT 3
typedef struct {
int trig_gpio;
int echo_gpio;
float position_offset;
} SonicSensor;
float multi_sonic_measure(SonicSensor *sensors, int count)
{
float distances[SONIC_SENSOR_COUNT];
for (int i = 0; i < count; i++) {
distances[i] = sonic_measure_sensor(sensors[i]);
}
// 取最小值作为最终距离
float min_distance = distances[0];
for (int i = 1; i < count; i++) {
if (distances[i] < min_distance) {
min_distance = distances[i];
}
}
return min_distance;
}