STM32 智能垃圾桶项目笔记(一):超声波模块(HC-SR04)原理与驱动实现
本系列笔记是笔者学习 B 站 up 主 “技术探索者” STM32 系列视频所作的记录,有不明白的地方推荐观看视频
目录
- 一、前言
- 二、超声波模块(HC-SR04)原理
- 2.1 模块引脚与核心特性
- 2.2 工作原理与测距逻辑
- 2.3 时序图与中断触发方案
- 三、CubeMX 工程配置
- 3.1 基础配置(芯片 / 时钟 / Debug)
- 3.2 TIM3 配置(1μs 高精度延时)
- 3.3 引脚与中断配置(Trig/Echo)
- 3.4 串口配置(数据传输与验证)
- 四、核心代码实现
- 4.1 1μs 延时函数(基于 TIM3)
- 4.2 串口重定向(printf 输出)
- 4.3 超声波驱动函数(触发 / 中断回调)
- 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 的工作流程由 “触发信号” 启动,通过 “回响信号” 反馈距离,具体步骤如下:
- 触发测距:STM32 向 Trig 引脚输出至少 10μs 的高电平信号(需高精度延时,笔者在定时器笔记中已实现,可回顾);
- 模块自动发送 / 接收超声波:Trig 接收到触发信号后,模块会自动发送 8 个 40kHz 的方波,同时开始检测是否有超声波反射回来;
- 回响信号反馈:若超声波反射回来,模块会通过 Echo 引脚输出高电平 —— 高电平的持续时间 = 超声波从 “发射到返回” 的总时间;
- 距离计算:根据 “时间 - 距离” 公式推导,最终距离 = (Echo 高电平持续时间 × 声速) / 2
(注:声速取 340m/s,除以 2 是因为超声波需 “发射→反射→返回”,走了两倍距离)。
2.3 时序图与中断触发方案
超声波模块的 Echo 引脚电平变化时序图如下:
从时序图可观察到:
- Echo 引脚从低电平变高电平时,产生上升沿(标志着超声波开始返回);
- Echo 引脚从高电平变低电平时,产生下降沿(标志着超声波返回结束);
- 上升沿到下降沿的时间差,就是 Echo 高电平持续时间(即超声波往返时间)。
因此,我们需要用双边沿触发中断来捕获这两个时刻:
- 上升沿触发中断:记录当前时间
t1
(开启定时器计数); - 下降沿触发中断:记录当前时间
t2
(停止定时器计数); - 计算时间差
t = t2 - t1
,代入距离公式即可得到测量结果。
三、CubeMX 工程配置
笔者使用的是 STM32F103C8T6 最小系统板,以下配置步骤均基于该芯片,按步骤操作即可完成基础工程搭建。
3.1 基础配置(芯片 / 时钟 / Debug)
-
芯片选择:打开 CubeMX,搜索并选择
STM32F103C8T6
,如下图:
-
Debug 配置:进入
System Core → SYS
,Debug 选择Serial Wire
(SWD 调试模式,常用且稳定); -
时钟配置:
- 进入
System Core → RCC
,High Speed Clock(HSE)选择Crystal/Ceramic Resonator
(外部晶振); - 进入
Clock Configuration
,将 HCLK 配置为 72MHz(STM32F103 常用最高主频),配置如下:
- 进入
-
工程生成配置:
- 进入
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 级延时,配置步骤如下:
- 进入
Timers → TIM3
,模式选择Internal Clock
(内部时钟); - 使能中断:勾选
NVIC Settings → TIM3 global interrupt
(后续延时函数无需中断,但需开启定时器计数); - 参数计算与配置:
- 预分频系数(Prescaler):
72 - 1
(72MHz 时钟 / 72 = 1MHz,即 1 次计数 = 1μs); - 自动重载值(Counter Period):
65535
(16 位定时器最大计数,避免频繁溢出);
- 预分频系数(Prescaler):
- 配置如下图:
3.3 引脚与中断配置(Trig/Echo)
笔者选择的引脚:Trig→PA9(输出)、Echo→PA8(中断输入),配置如下:
-
Trig 引脚(PA9):
- 进入
GPIO → PA9
,模式设为GPIO_Output
(推挽输出),无需中断;
- 进入
-
Echo 引脚(PA8):
- 进入
GPIO → PA8
,模式设为GPIO_EXTI8
(外部中断模式); - 触发方式:
Trigger Selection → Rising/Falling edge
(双边沿触发); - 上拉电阻:
Pull-up/Pull-down → Pull-up
(避免引脚悬空误触发); - 配置如下图:
- 进入
-
使能 Echo 中断:
- 进入
NVIC → NVIC Settings
,勾选EXTI line[9:5] interrupts
(PA8 对应 EXTI8,属于 line [9:5] 组)。
- 进入
3.4 串口配置(数据传输与验证)
需通过串口输出测量结果,配置 USART1 如下:
-
进入
Connectivity → USART1
,模式选择Asynchronous
(异步通信); -
基本参数:波特率
115200
,数据位8
,停止位1
,校验位None
; -
使能串口中断:勾选
NVIC Settings → USART1 global interrupt
(可选,本次仅用发送,不强制); -
配置如下图:
-
硬件接线:STM32 的 PB6(USART1_TX)接 TTL-USB 转换器的 RX,PB7(USART1_RX)接转换器的 TX,转换器与开发板共地。
四、核心代码实现
生成工程后,新建 driver
文件夹,创建 driver_sr04.c
和 driver_sr04.h
(驱动文件),并将 driver
文件夹添加到 Keil 工程路径(Options for Target → C/C++ → Include Paths
)。
4.1 1μs 延时函数(基于 TIM3)
该函数与之前定时器笔记的实现略有不同,通过 “设置计数起始值” 实现精准延时,代码如下:
#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
中:
#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 个字符,超时 1000msreturn 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 高电平,启动模块测距:
/*** @brief 超声波模块触发函数(启动测距)* @param 无* @retval 无*/
void SR04_Trigger(void)
{Trig_ON; // PA9 拉高(触发开始)Delay_us(10); // 保持 10μs 高电平Trig_OFF; // PA9 拉低(触发结束)
}
4.3.2 中断回调函数(捕获 Echo 电平)
需额外配置 TIM4(用于计数 Echo 高电平时间),步骤如下:
- CubeMX 中配置 TIM4:
Internal Clock
,预分频72-1
,自动重载65535
,无需使能中断; - 在
driver_sr04.c
中实现 GPIO 中断回调,捕获 Echo 上升沿 / 下降沿:
// 声明 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 → 转换为 cmdistance_cm = count * 340 / 2 * 0.000001 * 100;count = 0; // 清零计数值,准备下次测量}}
}
4.4 驱动头文件(driver_sr04.h)
声明函数与宏定义,供外部调用:
#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)的核心开发:
- 理解了模块的 “触发→测距→反馈” 工作流程,以及基于双边沿中断的时间捕获方案;
- 完成 CubeMX 配置(基础时钟、TIM3/TIM4 定时器、引脚中断、串口);
- 实现了 1μs 延时、触发函数、中断回调函数,为后续距离测量做好准备。
下一篇笔记将结合本次驱动文件,实现完整逻辑。请关注 Hello_Embed,我们一起逐步完成智能垃圾桶项目!