STM32 智能垃圾桶项目笔记(一):超声波模块(HC-SR04)原理与驱动实现

本系列笔记是笔者学习 B 站 up 主 "技术探索者" STM32 系列视频所作的记录,有不明白的地方推荐观看视频

目录

  • 一、前言
  • 二、超声波模块(HC-SR04)原理
    • [2.1 模块引脚与核心特性](#2.1 模块引脚与核心特性)
    • [2.2 工作原理与测距逻辑](#2.2 工作原理与测距逻辑)
    • [2.3 时序图与中断触发方案](#2.3 时序图与中断触发方案)
  • [三、CubeMX 工程配置](#三、CubeMX 工程配置)
    • [3.1 基础配置(芯片 / 时钟 / Debug)](#3.1 基础配置(芯片 / 时钟 / Debug))
    • [3.2 TIM3 配置(1μs 高精度延时)](#3.2 TIM3 配置(1μs 高精度延时))
    • [3.3 引脚与中断配置(Trig/Echo)](#3.3 引脚与中断配置(Trig/Echo))
    • [3.4 串口配置(数据传输与验证)](#3.4 串口配置(数据传输与验证))
  • 四、核心代码实现
    • [4.1 1μs 延时函数(基于 TIM3)](#4.1 1μs 延时函数(基于 TIM3))
    • [4.2 串口重定向(printf 输出)](#4.2 串口重定向(printf 输出))
    • [4.3 超声波驱动函数(触发 / 中断回调)](#4.3 超声波驱动函数(触发 / 中断回调))
    • [4.4 驱动头文件(driver_sr04.h)](#4.4 驱动头文件(driver_sr04.h))
  • 五、总结与预告

一、前言

本系列笔记将记录笔者运用 STM32 知识完成 "智能垃圾桶" 入门级项目的全过程。若想跟做该项目,需提前准备以下耗材:

  • 蓝牙模块:JDY-31
  • 语音合成模块:SYN6288
  • 超声波模块:HC-SR04(本次核心讲解模块)
    智能垃圾桶的核心功能是 "检测物体靠近后自动开盖",而超声波模块的作用就是精准测量物体与桶的距离 ------ 本次笔记将从模块原理、CubeMX 配置、代码实现三方面,完成超声波模块的基础驱动开发。

二、超声波模块(HC-SR04)原理

2.1 模块引脚与核心特性

HC-SR04 超声波模块共引出 4 个引脚,功能与接线要求如下:

引脚 功能描述 接线建议
VCC 电源正极 接 5V(模块工作电压为 5V)
Trig 触发信号输入(外部控制) 接 STM32 普通 GPIO 输出引脚
Echo 回响信号输出(反馈距离) 接 STM32 带中断的 GPIO 引脚
GND 电源负极 接 STM32 GND(需共地)

模块核心特性(无需深入研究参数细节,重点关注以下 3 点):

  • 测距范围:2cm ~ 400cm(满足垃圾桶开盖检测需求)
  • 测量精度:0.3cm(精度足够)
  • 工作频率:40kHz(超声波标准频率,无额外干扰)

2.2 工作原理与测距逻辑

HC-SR04 的工作流程由 "触发信号" 启动,通过 "回响信号" 反馈距离,具体步骤如下:

  1. 触发测距:STM32 向 Trig 引脚输出至少 10μs 的高电平信号(需高精度延时,笔者在定时器笔记中已实现,可回顾);
  2. 模块自动发送 / 接收超声波:Trig 接收到触发信号后,模块会自动发送 8 个 40kHz 的方波,同时开始检测是否有超声波反射回来;
  3. 回响信号反馈:若超声波反射回来,模块会通过 Echo 引脚输出高电平 ------ 高电平的持续时间 = 超声波从 "发射到返回" 的总时间;
  4. 距离计算 :根据 "时间 - 距离" 公式推导,最终距离 = (Echo 高电平持续时间 × 声速) / 2
    (注:声速取 340m/s,除以 2 是因为超声波需 "发射→反射→返回",走了两倍距离)。

2.3 时序图与中断触发方案

超声波模块的 Echo 引脚电平变化时序图如下:

从时序图可观察到:

  • Echo 引脚从低电平变高电平时,产生上升沿(标志着超声波开始返回);
  • Echo 引脚从高电平变低电平时,产生下降沿(标志着超声波返回结束);
  • 上升沿到下降沿的时间差,就是 Echo 高电平持续时间(即超声波往返时间)。
    因此,我们需要用双边沿触发中断来捕获这两个时刻:
  1. 上升沿触发中断:记录当前时间 t1(开启定时器计数);
  2. 下降沿触发中断:记录当前时间 t2(停止定时器计数);
  3. 计算时间差 t = t2 - t1,代入距离公式即可得到测量结果。

三、CubeMX 工程配置

笔者使用的是 STM32F103C8T6 最小系统板,以下配置步骤均基于该芯片,按步骤操作即可完成基础工程搭建。

3.1 基础配置(芯片 / 时钟 / Debug)

  1. 芯片选择 :打开 CubeMX,搜索并选择 STM32F103C8T6,如下图:

  2. Debug 配置 :进入 System Core → SYS,Debug 选择 Serial Wire(SWD 调试模式,常用且稳定);

  3. 时钟配置

    • 进入 System Core → RCC,High Speed Clock(HSE)选择 Crystal/Ceramic Resonator(外部晶振);
    • 进入 Clock Configuration,将 HCLK 配置为 72MHz(STM32F103 常用最高主频),配置如下:
  4. 工程生成配置

    • 进入 Project Manager → Code Generator,勾选 Generate peripheral initialization as a pair of .c/.h files per peripheral(外设初始化文件分开,便于管理);
    • 进入 Project Manager → Project,Toolchain/IDE 选择 MDK-ARM(Keil 编译器),填写工程名并选择保存路径;
    • 点击 Generate Code 生成工程,编译确保无错误。

3.2 TIM3 配置(1μs 高精度延时)

超声波触发需要 10μs 高电平,需用 TIM3 实现 1μs 级延时,配置步骤如下:

  1. 进入 Timers → TIM3,模式选择 Internal Clock(内部时钟);
  2. 使能中断:勾选 NVIC Settings → TIM3 global interrupt(后续延时函数无需中断,但需开启定时器计数);
  3. 参数计算与配置
    • 预分频系数(Prescaler):72 - 1(72MHz 时钟 / 72 = 1MHz,即 1 次计数 = 1μs);
    • 自动重载值(Counter Period):65535(16 位定时器最大计数,避免频繁溢出);
  4. 配置如下图:

3.3 引脚与中断配置(Trig/Echo)

笔者选择的引脚:Trig→PA9(输出)、Echo→PA8(中断输入),配置如下:

  1. Trig 引脚(PA9)

    • 进入 GPIO → PA9,模式设为 GPIO_Output(推挽输出),无需中断;
  2. Echo 引脚(PA8)

    • 进入 GPIO → PA8,模式设为 GPIO_EXTI8(外部中断模式);
    • 触发方式:Trigger Selection → Rising/Falling edge(双边沿触发);
    • 上拉电阻:Pull-up/Pull-down → Pull-up(避免引脚悬空误触发);
    • 配置如下图:
  3. 使能 Echo 中断

    • 进入 NVIC → NVIC Settings,勾选 EXTI line[9:5] interrupts(PA8 对应 EXTI8,属于 line [9:5] 组)。

3.4 串口配置(数据传输与验证)

需通过串口输出测量结果,配置 USART1 如下:

  1. 进入 Connectivity → USART1,模式选择 Asynchronous(异步通信);

  2. 基本参数:波特率 115200,数据位 8,停止位 1,校验位 None

  3. 使能串口中断:勾选 NVIC Settings → USART1 global interrupt(可选,本次仅用发送,不强制);

  4. 配置如下图:

  5. 硬件接线:STM32 的 PB6(USART1_TX)接 TTL-USB 转换器的 RX,PB7(USART1_RX)接转换器的 TX,转换器与开发板共地。

四、核心代码实现

生成工程后,新建 driver 文件夹,创建 driver_sr04.cdriver_sr04.h(驱动文件),并将 driver 文件夹添加到 Keil 工程路径(Options for Target → C/C++ → Include Paths)。

4.1 1μs 延时函数(基于 TIM3)

该函数与之前定时器笔记的实现略有不同,通过 "设置计数起始值" 实现精准延时,代码如下:

c 复制代码
#include "driver_sr04.h"
#include "stm32f1xx_hal.h"

// 声明 TIM3 句柄(CubeMX 自动生成在 tim.c 中)
extern TIM_HandleTypeDef htim3;

/**
 * @brief  1μs 级高精度延时函数
 * @param  us:目标延时时间(单位:μs,最大 65530μs,避免溢出)
 * @retval 无
 */
void Delay_us(uint16_t us)
{
    uint16_t differ = 0xffff - us - 5;  // 计算计数起始值(-5 为代码执行补偿)
    __HAL_TIM_SET_COUNTER(&htim3, differ);  // 设置 TIM3 计数起始值
    HAL_TIM_Base_Start(&htim3);  // 启动 TIM3 计数
    
    // 等待计数到接近 0xffff(避免溢出)
    while(differ < 0xffff - 5)
    {
        differ = __HAL_TIM_GET_COUNTER(&htim3);  // 实时读取计数值
    }
    
    HAL_TIM_Base_Stop(&htim3);  // 停止 TIM3 计数
}

逻辑说明

  • 定时器从 differ 向上计数到 0xffff,计数次数 = 0xffff - differ ≈ us(补偿值 -5 抵消函数调用耗时);
  • 1MHz 时钟下,1 次计数 = 1μs,因此总延时 ≈ 目标 us 值。

4.2 串口重定向(printf 输出)

需实现 fputc 函数,让 printf 通过 USART1 输出,代码添加在 usart.c 中:

c 复制代码
#include <stdio.h>  // 包含 printf 所需头文件

// 声明 USART1 句柄
UART_HandleTypeDef huart1;

/**
 * @brief  串口重定向函数,printf 输出到 USART1
 * @param  ch:要输出的字符
 * @param  f:文件指针(标准输出,无需关注)
 * @retval 输出的字符
 */
int fputc(int ch, FILE *f)
{
    HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 1000);  // 发送 1 个字符,超时 1000ms
    return ch;
}

Keil 配置

  • 打开工程 Options for Target → Target,勾选 Use MicroLIB(启用微库,支持 printf);
  • 进入 Debug → Settings → Flash Download,勾选 Reset and Run(下载后自动运行)。

4.3 超声波驱动函数(触发 / 中断回调)

4.3.1 触发函数(SR04_Trigger)

向 Trig 引脚输出 10μs 高电平,启动模块测距:

c 复制代码
/**
 * @brief  超声波模块触发函数(启动测距)
 * @param  无
 * @retval 无
 */
void SR04_Trigger(void)
{
    Trig_ON;    // PA9 拉高(触发开始)
    Delay_us(10);  // 保持 10μs 高电平
    Trig_OFF;   // PA9 拉低(触发结束)
}
4.3.2 中断回调函数(捕获 Echo 电平)

需额外配置 TIM4(用于计数 Echo 高电平时间),步骤如下:

  1. CubeMX 中配置 TIM4:Internal Clock,预分频 72-1,自动重载 65535,无需使能中断;
  2. driver_sr04.c 中实现 GPIO 中断回调,捕获 Echo 上升沿 / 下降沿:
c 复制代码
// 声明 TIM4 句柄(CubeMX 自动生成)
extern TIM_HandleTypeDef htim4;
uint32_t distance_cm = 0;  // 测量距离(单位:cm)

/**
 * @brief  GPIO 中断回调函数(处理 Echo 引脚电平变化)
 * @param  GPIO_Pin:触发中断的引脚
 * @retval 无
 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    static uint32_t count = 0;  // 定时器计数值(暂存)
    
    if(GPIO_Pin == GPIO_PIN_8)  // 确认是 Echo 引脚(PA8)
    {
        if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_8) == 1)  // Echo 上升沿(开始返回)
        {
            HAL_TIM_Base_Start(&htim4);  // 启动 TIM4 计数
            __HAL_TIM_SetCounter(&htim4, 0);  // 清零计数值
        }
        else  // Echo 下降沿(返回结束)
        {
            HAL_TIM_Base_Stop(&htim4);  // 停止 TIM4 计数
            count = __HAL_TIM_GetCounter(&htim4);  // 读取计数值(1 计数 = 1μs)
            
            // 距离计算:count(μs) × 340(m/s) / 2 → 转换为 cm
            distance_cm = count * 340 / 2 * 0.000001 * 100;
            count = 0;  // 清零计数值,准备下次测量
        }
    }
}

4.4 驱动头文件(driver_sr04.h)

声明函数与宏定义,供外部调用:

c 复制代码
#ifndef __DRIVER_SR04_H
#define __DRIVER_SR04_H

#include "main.h"
#include <stdio.h>

// 宏定义:Trig 引脚(PA9)电平控制
#define Trig_ON    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_SET)
#define Trig_OFF   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_RESET)

// 函数声明
void Delay_us(uint16_t us);        // 1μs 延时函数
void SR04_Trigger(void);           // 超声波触发函数

// 外部变量声明(可选,供 main.c 读取距离)
extern uint32_t distance_cm;

#endif

五、总结与预告

本次笔记完成了超声波模块(HC-SR04)的核心开发:

  1. 理解了模块的 "触发→测距→反馈" 工作流程,以及基于双边沿中断的时间捕获方案;
  2. 完成 CubeMX 配置(基础时钟、TIM3/TIM4 定时器、引脚中断、串口);
  3. 实现了 1μs 延时、触发函数、中断回调函数,为后续距离测量做好准备。
    下一篇笔记将结合本次驱动文件,实现完整逻辑。请关注 Hello_Embed,我们一起逐步完成智能垃圾桶项目!
相关推荐
菠萝地亚狂想曲2 小时前
极简文件列表
c语言
聪明的笨猪猪3 小时前
面试清单:JVM类加载与虚拟机执行核心问题
java·经验分享·笔记·面试
忘川w3 小时前
红宝书 基础词回忆
笔记
努力毕业的小土博^_^3 小时前
【深度学习|学习笔记】详细讲解一下 深度学习训练过程中 为什么 Momentum 可以加速训练?
人工智能·笔记·深度学习·学习·momentum
Larry_Yanan3 小时前
QML学习笔记(十四)QML的自定义模块
开发语言·笔记·qt·学习·ui
wdfk_prog3 小时前
[Linux]学习笔记系列 -- lib/sort.c 通用的排序库(Generic Sorting Library) 为内核提供标准的、高效的排序功能
linux·运维·c语言·笔记·stm32·学习·bug
shark_dev4 小时前
C/C++ 指针详解与各种指针定义
c语言·c++
what&&why5 小时前
STM32控制继电器
单片机·嵌入式硬件
钮钴禄·爱因斯晨5 小时前
数据结构|图论:从数据结构到工程实践的核心引擎
c语言·数据结构·图论