Skip to content

Latest commit

 

History

History
617 lines (480 loc) · 16.5 KB

File metadata and controls

617 lines (480 loc) · 16.5 KB

STM32超声波测距系统 - HC-SR04完整实现

前言

这是一个基于STM32F103C8T6和HC-SR04超声波模块的测距监测系统。项目在温湿度采集系统的代码框架基础上开发,复用了任务调度器、按键、数码管等底层驱动,重点实现了超声波测距和距离监控功能。从代码移植到功能调试,记录了完整的开发过程。

一、项目概述

1.1 系统功能

  • 超声波测距:使用HC-SR04模块,测量范围2cm-400cm
  • 实时显示:4位数码管显示当前距离/阈值(单位:厘米)
  • 距离监控:支持距离上限/下限两个阈值设置
  • 智能报警:距离超出阈值范围时蜂鸣器PWM报警
  • 按键控制:单击切换显示模式,双击进入阈值设置
  • LED指示:LED1/LED2分别指示距离模式和阈值设置模式
  • 串口回显:每500ms输出当前距离和系统状态

1.2 硬件清单

硬件模块 型号/规格 说明
主控MCU STM32F103C8T6 72MHz主频,64KB Flash
超声波模块 HC-SR04 测距范围2-400cm
数码管 4位共阴极数码管 显示距离和阈值
按键 4个独立按键 菜单和参数调节
蜂鸣器 无源蜂鸣器 PWM驱动报警
LED指示灯 2个LED 模式指示
串口模块 USART1 调试和数据回显

1.3 引脚定义

// HC-SR04超声波模块
HC_SR04_TRIG -> PB8  // 触发引脚
HC_SR04_ECHO -> PB9  // 回响引脚

// 数码管
段选 A-G,DP  -> PA0-PA7
位选 DIG1-4  -> PB0, PB1, PB10, PB11

// 按键
KEY1(模式)   -> PB12
KEY2(切换)   -> PB13
KEY3(增加)   -> PB14
KEY4(减少)   -> PB15

// LED指示灯
LED1         -> PB6  // 距离显示模式
LED2         -> PB7  // 阈值设置模式

// 蜂鸣器 (通过TIM3 PWM驱动)
BEEP         -> PB5 (TIM3_CH2, 部分重映射)

// 定时器
TIM2         -> 超声波计时10μs中断TIM4         -> 系统时基1ms中断TIM3         -> 蜂鸣器PWM2.7kHz

二、HC-SR04超声波测距原理

2.1 工作原理

HC-SR04使用超声波测距,基本流程:

  1. 触发信号:主机给TRIG引脚发送≥10μs的高电平
  2. 发射超声波:模块自动发射8个40kHz超声波脉冲
  3. 等待回响:ECHO引脚输出高电平,持续时间即为超声波往返时间
  4. 计算距离:距离 = (回响时间 × 音速) / 2

2.2 距离计算公式

音速(25°C):v = 346 m/s = 0.0346 cm/μs
距离(cm):Distance = (time × 0.0346) / 2

代码实现:

Distance = (time_end * 346) / 2;      // time_end单位:10μs
Distance_mm = Distance / 100;         // 转换为毫米
distance_cm = Distance_mm / 10;       // 转换为厘米

2.3 测量范围与精度

  • 理论范围:2cm - 400cm
  • 盲区:2cm以内(回响信号未分离)
  • 精度:±3mm(受温度、湿度影响)
  • 测量角度:15°(有效探测角)

三、代码框架移植

3.1 从温湿度项目继承

本项目复用了温湿度采集系统的代码架构:

// 相同的模块(直接复用)任务调度器 (sch_task)
✅ 按键驱动 (key.c/h) - 支持单击/双击数码管驱动 (smg.c/h) - 动态扫描LED驱动 (led.c/h)
✅ 串口驱动 (usart.c/h)
✅ 延时函数 (delay.c/h)

// 需要修改的模块
🔧 蜂鸣器 (beep.c/h) - 引脚从PA6改为PB5
🔧 定时器 (timer.c/h) - 新增TIM2用于超声波计时

// 新增模块HC-SR04驱动 (HCSR04.c/h) - 超声波测距

3.2 引脚变更记录

功能 原引脚(温湿度) 新引脚(超声波) 变更原因
传感器DATA PB8 (DHT11) - 更换为HC-SR04
超声波TRIG - PB8 新增
超声波ECHO - PB9 新增
蜂鸣器 PA6 (TIM3_CH1) PB5 (TIM3_CH2) PCB布线优化

3.3 变量调整

// 温湿度项目
unsigned char temp;         // 温度
unsigned char humi;         // 湿度
unsigned char temp_high;    // 温度上限
unsigned char temp_low;     // 温度下限
unsigned char humi_high;    // 湿度上限
unsigned char humi_low;     // 湿度下限

// 超声波项目
unsigned int distance;      // 当前距离(厘米)
unsigned int distance_high; // 距离上限(厘米)
unsigned int distance_low;  // 距离下限(厘米)

3.4 菜单简化

// 温湿度:6个菜单
menu = 0: 显示温度
menu = 1: 显示湿度
menu = 2: 温度上限
menu = 3: 温度下限
menu = 4: 湿度上限
menu = 5: 湿度下限

// 超声波:4个菜单
menu = 0: 显示距离
menu = 1: 阈值设置预留未使用menu = 2: 距离上限
menu = 3: 距离下限

四、核心功能实现

4.1 HC-SR04驱动实现

初始化

void HC_SR04_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;

    // TRIG - 推挽输出
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;  // PB8
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    // ECHO - 下拉输入
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;  // PB9
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    GPIO_WriteBit(GPIOB, GPIO_Pin_8, 0);  // TRIG初始为低
    delay_us(15);
}

测距函数

int16_t sonar_mm(void)
{
    uint32_t Distance, Distance_mm = 0;

    // 1. 发送10μs触发信号
    GPIO_WriteBit(GPIOB, Trig, 1);
    delay_us(15);  // 至少10μs
    GPIO_WriteBit(GPIOB, Trig, 0);

    // 2. 等待ECHO变为高电平
    while(GPIO_ReadInputDataBit(GPIOB, Echo) == 0);

    // 3. 开始计时
    time = 0;

    // 4. 等待ECHO变为低电平(回响结束)
    while(GPIO_ReadInputDataBit(GPIOB, Echo) == 1);

    // 5. 记录时间
    time_end = time;

    // 6. 判断是否超时(38ms对应约650cm)
    if(time_end / 100 < 38)
    {
        Distance = (time_end * 346) / 2;
        Distance_mm = Distance / 100;
    }

    return Distance_mm;
}

TIM2计时中断

// 每10μs触发一次中断
void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
    {
        time++;  // 计时变量累加
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}

4.2 蜂鸣器PWM配置修改

由于引脚从PA6改为PB5,需要使用TIM3的部分重映射功能。

void Init_timer3(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;

    // 使能时钟和复用功能
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB |
                           RCC_APB2Periph_AFIO, ENABLE);

    // 关键:TIM3部分重映射,CH2 -> PB5
    GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);

    // 配置PB5为复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    // 时基配置:72MHz / 72 / 370 ≈ 2.7kHz
    TIM_TimeBaseStructure.TIM_Period = 369;
    TIM_TimeBaseStructure.TIM_Prescaler = 71;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

    // PWM配置
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;  // 初始占空比0
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

    // 通道2初始化(不是CH1)
    TIM_OC2Init(TIM3, &TIM_OCInitStructure);
    TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);

    TIM_ARRPreloadConfig(TIM3, ENABLE);
    TIM_Cmd(TIM3, ENABLE);
}

// 开启蜂鸣器
void Open_Beep(void)
{
    TIM_SetCompare2(TIM3, 180);  // 注意是SetCompare2
}

// 关闭蜂鸣器
void Close_Beep(void)
{
    TIM_SetCompare2(TIM3, 0);
}

4.3 报警逻辑

void alarm_proc(void)
{
    // 只在距离显示模式(menu=0)下检测报警
    if(menu == 0)
    {
        // 当距离超出上限或低于下限时报警
        if(distance > distance_high || distance < distance_low)
            Open_Beep();
        else
            Close_Beep();
    }
    else
    {
        // 阈值设置模式下,关闭报警
        Close_Beep();
    }
}

4.4 数码管显示

void seg_proc(void)
{
    unsigned int display_value = 0;

    // 根据菜单选择显示内容
    switch(menu)
    {
        case 0: display_value = distance;      break;
        case 2: display_value = distance_high; break;
        case 3: display_value = distance_low;  break;
        default: display_value = 0;            break;
    }

    // 三位数显示(支持0-400cm)
    smg_buf[0] = display_value / 100;         // 百位
    smg_buf[1] = (display_value / 10) % 10;   // 十位
    smg_buf[2] = display_value % 10;          // 个位
    smg_buf[3] = 10;                          // 熄灭
}

五、调试过程与问题解决

5.1 定时器重映射问题

问题现象:蜂鸣器不响,PA6有PWM输出但PB5没有

排查过程

  1. 使用示波器测量PA6,发现有PWM波形
  2. 测量PB5,没有输出
  3. 查阅STM32参考手册,发现TIM3可以部分重映射

解决方案

// 添加重映射配置
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);

// TIM3部分重映射后的引脚对应关系:
// CH1: PA6 -> PA7
// CH2: PA7 -> PB5  ← 我们使用这个
// CH3: PB0 -> PB0 (不变)
// CH4: PB1 -> PB1 (不变)

经验教训

  • STM32的复用功能需要显式开启重映射
  • 不同的重映射模式有不同的引脚对应关系
  • 必须查阅芯片参考手册确认

5.2 超声波测距不稳定

问题现象:距离数据跳变严重,偶尔显示0或400+

排查过程

  1. 使用逻辑分析仪抓取TRIG和ECHO波形
  2. 发现ECHO有时没有响应
  3. 检查HC-SR04供电,发现电压偏低(3.1V)

解决方案

  • 使用5V供电(HC-SR04推荐5V)
  • 添加软件滤波:
void ultrasonic_task(void)
{
    int16_t dist_mm = sonar_mm();
    distance = dist_mm / 10;

    // 限幅处理
    if(distance > 400) distance = 400;
    if(distance < 2) distance = 2;
}

5.3 TIM2中断频率设置

关键参数

// 目标:10μs中断一次
// 公式:T = (Prescaler + 1) * (Period + 1) / 72MHz

TIM_TimeBaseStructure.TIM_Period = 9;     // ARR = 9
TIM_TimeBaseStructure.TIM_Prescaler = 71; // PSC = 71

// 计算:T = 72 * 10 / 72,000,000 = 10μs ✓

5.4 阻塞式测距问题

初版问题

// while循环阻塞,导致其他任务无法执行
while(GPIO_ReadInputDataBit(GPIOB, Echo) == 0);
while(GPIO_ReadInputDataBit(GPIOB, Echo) == 1);

改进方向: 可以使用外部中断+定时器方式实现非阻塞测距:

  • ECHO上升沿:开始TIM2计时
  • ECHO下降沿:停止TIM2计时,读取CNT寄存器

目前采用300ms采集周期,阻塞时间短(<30ms),暂未影响系统响应。

六、系统工作流程

6.1 任务调度

task_t sch_task[] = {
    {key_proc,        10,  0},   // 按键扫描 - 10ms
    {seg_proc,        20,  0},   // 数码管显示 - 20ms
    {led_proc,        100, 0},   // LED指示 - 100ms
    {ultrasonic_task, 300, 0},   // 超声波采集 - 300ms
    {alarm_proc,      100, 0},   // 报警检测 - 100ms
    {uart_proc,       500, 0}    // 串口回显 - 500ms
};

6.2 操作流程

  1. 上电初始化:显示当前距离,LED1亮
  2. KEY1单击:切换到阈值设置模式(预留)
  3. KEY1双击:进入距离上限 ↔ 距离下限设置切换
  4. KEY3:阈值+1(设置模式下)
  5. KEY4:阈值-1(设置模式下)
  6. 自动监控:距离超出范围时蜂鸣器报警

6.3 串口输出示例

========== 超声波测距系统 ==========
         ==== 距离监测 =====
             == YYDS ==
Distance: 25 cm, Menu: 0, LED1: 1, LED2: 0, Alarm: OFF
Distance: 102 cm, Menu: 0, LED1: 1, LED2: 0, Alarm: ON
Distance: 50 cm, Menu: 2, LED1: 0, LED2: 1, Alarm: OFF

七、性能测试

7.1 测距精度测试

实际距离(cm) 测量值(cm) 误差(cm) 误差率
5 5 0 0%
10 10 0 0%
20 20 0 0%
50 51 +1 2%
100 101 +1 1%
200 203 +3 1.5%
300 295 -5 1.7%

结论:近距离(<100cm)精度较高,远距离(>200cm)误差略增大

7.2 响应速度

  • 采集周期:300ms
  • 单次测距时间:约20-30ms(取决于距离)
  • 报警延迟:<100ms(报警任务周期)

7.3 环境影响

测试条件 测量稳定性 备注
室内静止 优秀 波动±1cm
移动目标 良好 需要滤波
强光照射 无影响 超声波不受光线影响
软质表面 较差 吸收超声波,反射弱
倾斜角度>15° 不稳定 回响信号弱

八、代码优化建议

8.1 非阻塞测距

使用外部中断方式:

// EXTI9配置为ECHO引脚
void EXTI9_IRQHandler(void)
{
    if(EXTI_GetITStatus(EXTI_Line9) != RESET)
    {
        if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_9))
        {
            // 上升沿:开始计时
            TIM_SetCounter(TIM2, 0);
            TIM_Cmd(TIM2, ENABLE);
        }
        else
        {
            // 下降沿:停止计时
            time_end = TIM_GetCounter(TIM2);
            TIM_Cmd(TIM2, DISABLE);
        }
        EXTI_ClearITPendingBit(EXTI_Line9);
    }
}

8.2 数字滤波

中值滤波或均值滤波:

#define FILTER_SIZE 5
uint16_t distance_buffer[FILTER_SIZE];

uint16_t median_filter(void)
{
    // 冒泡排序
    for(int i=0; i<FILTER_SIZE-1; i++)
        for(int j=0; j<FILTER_SIZE-1-i; j++)
            if(distance_buffer[j] > distance_buffer[j+1])
                swap(&distance_buffer[j], &distance_buffer[j+1]);

    return distance_buffer[FILTER_SIZE/2];  // 返回中位数
}

8.3 温度补偿

音速随温度变化:

// 音速(m/s) = 331.5 + 0.6 * 温度(°C)
float sound_speed = 331.5 + 0.6 * temperature;
Distance = (time_end * sound_speed / 10000) / 2;  // 单位转换

九、项目总结

9.1 技术收获

  1. 模块化设计:复用代码框架,快速开发新功能
  2. 定时器应用:掌握TIM基本定时、PWM输出、输入捕获
  3. 引脚复用:学会使用GPIO重映射功能
  4. 协议通信:理解HC-SR04的时序要求
  5. 调试技巧:逻辑分析仪、示波器的使用

9.2 遇到的挑战

挑战 解决方案 经验
定时器重映射配置 查阅参考手册 不能想当然,必须看文档
测距数据跳变 滤波+限幅处理 传感器数据需要预处理
阻塞式测距影响响应 优化采集周期 可改用中断方式
蜂鸣器引脚冲突 更换引脚和通道 提前规划引脚分配

9.3 项目亮点

  1. 代码复用:90%的底层代码直接复用,大幅提高开发效率
  2. 任务调度:清晰的多任务架构,便于功能扩展
  3. 精确计时:10μs级定时器中断,满足测距精度要求
  4. 完善的报警:实时监控,超阈值自动报警
  5. 友好交互:数码管+LED+按键,操作简单直观

9.4 改进方向

  • 增加LCD显示距离变化曲线
  • 实现非阻塞式测距(外部中断)
  • 添加数字滤波算法
  • 温度补偿提高精度
  • 支持多目标检测
  • 低功耗模式

十、参考资料

附录

A. 默认配置

距离上限100 cm
距离下限10 cm
采集周期300 ms
PWM频率2.7 kHz
计时精度10 μs

B. 引脚对照表

功能 引脚 复用功能 备注
HC-SR04 TRIG PB8 GPIO 输出
HC-SR04 ECHO PB9 GPIO 输入
蜂鸣器 PB5 TIM3_CH2 部分重映射
系统时基 - TIM4 1ms中断
超声波计时 - TIM2 10μs中断

项目作者:学电子他就能回来吗 完成时间:2025年12月 开源地址:[GitHub仓库链接]

如果这个项目对你有帮助,欢迎点赞收藏!有问题欢迎在评论区讨论~