HC-SR04超声波测距模块——基于stm32标准库和HAL库
常见的HC-SR04超声波模块代码通常为while循环一直等待ECHO引脚的电平变化来进行测距,这种很容易由于超声波接线松动或一些其它原因而引起程序卡死。此博客通过外部中断检测超声波ECHO引脚电平变化从而来进行测距,避免了程序因陷入死循环而卡死。
HC-SR04超声波测距模块——基于stm32标准库和HAL库
一、模块简介
常见的超声波模块代码通常为while循环一直等待ECHO引脚的电平变化来进行测距,这种很容易由于超声波接线松动或一些其它原因而引起程序卡死。此博客通过外部中断检测超声波ECHO引脚电平变化从而来进行测距,避免了程序因陷入死循环而卡死。
工程文件代码链接位于最下方
1、产品特点
HC-SR04 超声波测距模块可提供 2cm-400cm 的非接触式距离感测功能,测
距精度可达高到 3mm;模块包括超声波发射器、接收器与控制电路。
基本工作原理:
(1)采用 IO 口 TRIG 触发测距,给最少 10us 的高电平信呈。
(2)模块自动发送 8 个 40khz 的方波,自动检测是否有信号返回;
(3)有信号返回,通过 IO 口 ECHO 输出一个高电平,高电平持续的时间就是超声
波从发射到返回的时间。测试距离=(高电平时间*声速(340M/S))/2;
2、实物图
如下图接线,
VCC 供 5V电源
GND 为地线
TRIG 触 发 控 制 信 号 输入
ECHO 回响信号输出等四个接口端。

3、电气参数

4、超声波时序图

以上时序图表明你只需要提供一个 10uS 以上脉冲触发信号,该模块内部将发出 8 个 40kHz 周期电平并检测回波。一旦检测到有回波信号则输出回响信号。回响信号的脉冲宽度与所测的距离成正比。由此通过发射信号到收到的回响信号时间间隔可以计算得到距离。公式:uS/58=厘米或者 uS/148=英寸;或是:距离= 高电平时间*声速(340M/S)/2;建议测量周期为 60ms 以上,以防止发射信号对回响信号的影响。
注:
1、此模块不宜带电连接,若要带电连接,则先让模块的 GND 端先连接,否则会影响模块的正常工作。
2、测距时,被测物体的面积不少于 0.5 平方米且平面尽量要求平整,否则影响测量的结果
二、标准库
下述代码接线:
超声波:
VCC——5V(建议用降压模块供稳定5V)
GND——GND
ECHO——PA1
TRIG——PA0
OLED:
VCC——3.3V
GND——GND
SCL——PB11
SDA——PB10
1、代码编写
ultrasonic.c(超声波初始化函数):
#include "ultrasonic.h"
#include "timer.h"
#include "stm32f10x_exti.h"
//超声波计数
u16 msHcCount;
void Ultrasonic_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd (RCC_APB2Periph_AFIO|ULTRASONIC_GPIO_CLK, ENABLE ); // 打开连接 超声波传感器 的单片机引脚端口时钟
GPIO_InitStructure.GPIO_Pin = ULTRASONIC_TRIG_GPIO_PIN; // 配置连接 传感器TRIG 的单片机引脚模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 设置为推挽输出
GPIO_Init(ULTRASONIC_GPIO_PORT, &GPIO_InitStructure); // 初始化
GPIO_InitStructure.GPIO_Pin = ULTRASONIC_ECHO_GPIO_PIN; // 配置连接 传感器ECHO 的单片机引脚模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 设置为浮空输入输入
GPIO_Init(ULTRASONIC_GPIO_PORT, &GPIO_InitStructure); // 初始化
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line1; // 根据实际引脚选择EXTI线
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; // 双边沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
// 配置NVIC中断优先级
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; // 指定EXTI1的中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 设置抢占优先级为0(最高)
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 设置子优先级为0(最高)
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断通道
NVIC_Init(&NVIC_InitStructure);
TIM4_Int_Init(1000-1,72-1);
}
void EXTI1_IRQHandler(void)
{
// 检查是否是EXTI线1的中断
if (EXTI_GetITStatus(EXTI_Line1) != RESET)
{
// 获取当前GPIO引脚的状态
uint8_t pin_state = GPIO_ReadInputDataBit(ULTRASONIC_GPIO_PORT, ULTRASONIC_ECHO_GPIO_PIN);
// 检测到上升沿
if (pin_state == 1)
{
// 上升沿操作
OpenTimerForHc();
}
// 检测到下降沿
else
{
CloseTimerForHc();
// 下降沿操作
}
// 清除中断标志,避免重复触发
EXTI_ClearITPendingBit(EXTI_Line1);
}
}
//打开定时器4
static void OpenTimerForHc()
{
TIM_SetCounter(TIM4,0);
msHcCount = 0;
TIM_Cmd(TIM4, ENABLE);
}
//关闭定时器4
static void CloseTimerForHc()
{
TIM_Cmd(TIM4, DISABLE);
}
//获取定时器4计数器值
u32 GetEchoTimer(void)
{
u32 t = 0;
t = msHcCount*1000;
t += TIM_GetCounter(TIM4);
TIM4->CNT = 0;
delay_ms(50);
return t;
}
//通过定时器4计数器值推算距离
float UltrasonicGetLength(void)
{
u32 t = 0;
int i = 0;
float lengthTemp = 0;
float sum = 0;
while(i!=5)
{
TRIG_Send = 1;
delay_us(20);
TRIG_Send = 0;
// while(ECHO_Reci == 0);
// OpenTimerForHc();
i = i + 1;
// while(ECHO_Reci == 1);
// CloseTimerForHc();
delay_ms(60);
t = GetEchoTimer();
lengthTemp = ((float)t/58.0);//cm
sum = lengthTemp + sum ;
}
lengthTemp = sum/5.0;
return lengthTemp;
}
ultrasonic.h:
#ifndef __ULTRASONIC_H
#define __ULTRASONIC_H
#include "stm32f10x.h"
#include "adcx.h"
#include "delay.h"
#include "math.h"
/***************根据自己需求更改****************/
// ULTRASONIC GPIO宏定义
#define ULTRASONIC_GPIO_CLK RCC_APB2Periph_GPIOA
#define ULTRASONIC_GPIO_PORT GPIOA
#define ULTRASONIC_TRIG_GPIO_PIN GPIO_Pin_0
#define ULTRASONIC_ECHO_GPIO_PIN GPIO_Pin_1
#define TRIG_Send PAout(0)
#define ECHO_Reci PAin(1)
/*********************END**********************/
void Ultrasonic_Init(void);
float UltrasonicGetLength(void);
void OpenTimerForHc(void);
void CloseTimerForHc(void);
u32 GetEchoTimer(void);
#endif /* __ADC_H */
time.c:
#include "delay.h"
#include "timer.h"
#include "adcx.h"
#include "led.h"
#include "string.h"
#include "ultrasonic.h"
extern u16 msHcCount;
void TIM4_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //时钟使能
//定时器TIM4初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ClearFlag(TIM4, TIM_FLAG_Update);
TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE ); //使能指定的TIM4中断,允许更新中断
//中断优先级NVIC设置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; //TIM4中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
TIM_Cmd(TIM4,DISABLE); //禁用TIM4
}
//定时器4中断服务程序
void TIM4_IRQHandler(void) //TIM4中断
{
if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM4, TIM_IT_Update );
msHcCount++;
}
}
timer.h:
#ifndef _TIMER_H
#define _TIMER_H
#include "sys.h"
void TIM4_Int_Init(u16 arr,u16 psc); //定时器4初始化
#endif
main.c:
#include "stm32f10x.h"
#include "led.h"
#include "usart.h"
#include "delay.h"
#include "oled.h"
#include "ultrasonic.h"
#include "timer.h"
float distance;
int main(void)
{
SystemInit();//配置系统时钟为72M
delay_init(72);
LED_Init();
LED_On();
Ultrasonic_Init();
USART1_Config();//串口初始化
OLED_Init();
printf("Start \n");
delay_ms(1000);
OLED_Clear();
//显示“距离:”
OLED_ShowChinese(0,0,0,16,1);
OLED_ShowChinese(16,0,1,16,1);
OLED_ShowChar(32,0,':',16,1);
OLED_ShowString(60,20,"cm",16,1);
while (1)
{
LED_Toggle();
distance = UltrasonicGetLength();
// OLED_ShowNum(40,20,distance,2,16,1);
OLED_ShowFloat(40,20,distance,4,2,16,1);
//delay_ms(60); //若有问题,改大试试?
}
}
三、HAL库
1、工程创建
1、设置RCC
2、设置串口(默认stlink)
3、设置时钟
4、项目文件设置
(1)设置项目名称
(2)设置存储路径
(3)选择所用IDE
(4)复制所用文件的.C和.H
(5)每个功能生成独立的.C和.H文件
5、设置IO口
(1)设置ULTRASONIC_TRIG
(2)设置ULTRASONIC_ECHO

注意使能外部中断:

(3)设置定时器1

注意使能定时器更新中断:
2、代码编写
ultrasonic.c(超声波初始化函数):
#include "ultrasonic.h"
//#include "timer.h"
//#include "stm32f10x_exti.h"
#include "stm32f1xx_hal.h"
#include "tim.h"
//超声波计数
u16 msHcCount;
// 打开定时器 1
void OpenTimerForHc()
{
HAL_TIM_Base_Start_IT(&htim1); // 启动定时器 1
msHcCount = 0; // 重置计数值
__HAL_TIM_SET_COUNTER(&htim1, 0); // 将定时器计数器清零
}
// 关闭定时器 1
void CloseTimerForHc()
{
HAL_TIM_Base_Stop_IT(&htim1); // 停止定时器 1
}
// 获取定时器 4 计数器值
u32 GetEchoTimer(void)
{
uint32_t t = 0;
t = msHcCount * 1000; // 将毫秒计数值转换为微秒
t += __HAL_TIM_GET_COUNTER(&htim1); // 获取定时器当前计数值
__HAL_TIM_SET_COUNTER(&htim1, 0); // 将定时器计数器清零
HAL_Delay(50); // 延时 50ms
return t;
}
float UltrasonicGetLength(void)
{
uint32_t t = 0;
int i = 0;
float lengthTemp = 0;
float sum = 0;
while (i != 3)
{
// 发送 TRIG 信号
TRIG_SendH;
HAL_Delay(20); // 20us
TRIG_SendL;
// 等待 60ms
HAL_Delay(60);
// 获取 ECHO 信号的持续时间
t = GetEchoTimer();
// 计算距离 (单位: cm)
lengthTemp = ((float)t / 58.0);
sum += lengthTemp;
i++;
}
// 计算平均距离
lengthTemp = sum / 3.0;
return lengthTemp;
}
ultrasonic.c:
#ifndef __ULTRASONIC_H
#define __ULTRASONIC_H
#include "stm32f1xx_hal.h"
//#include "adcx.h"
#include "delay.h"
#include "math.h"
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
//#define ULTRASONIC_TRIG_Pin GPIO_PIN_4
//#define ULTRASONIC_TRIG_GPIO_Port GPIOA
//#define ULTRASONIC_ECHO_Pin GPIO_PIN_5
//#define ULTRASONIC_ECHO_GPIO_Port GPIOA
//#define ULTRASONIC_ECHO_EXTI_IRQn EXTI9_5_IRQn
/***************根据自己需求更改****************/
// ULTRASONIC GPIO宏定义
//#define TRIG_Send PAout(0)
//#define ECHO_Reci PAin(1)
#define ULTRASONIC_GPIO_CLK RCC_APB2Periph_GPIOA
#define ULTRASONIC_GPIO_PORT ULTRASONIC_ECHO_GPIO_Port
#define ULTRASONIC_TRIG_GPIO_PIN ULTRASONIC_TRIG_Pin
#define ULTRASONIC_ECHO_GPIO_PIN ULTRASONIC_ECHO_Pin
#define TRIG_SendH HAL_GPIO_WritePin(ULTRASONIC_GPIO_PORT,ULTRASONIC_TRIG_GPIO_PIN,GPIO_PIN_SET)
#define TRIG_SendL HAL_GPIO_WritePin(ULTRASONIC_GPIO_PORT,ULTRASONIC_TRIG_GPIO_PIN,GPIO_PIN_RESET)
#define ECHO_Reci HAL_GPIO_ReadPin(ULTRASONIC_GPIO_PORT,ULTRASONIC_ECHO_GPIO_PIN)
/*********************END**********************/
//void Ultrasonic_Init(void);
float UltrasonicGetLength(void);
void OpenTimerForHc(void);
void CloseTimerForHc(void);
u32 GetEchoTimer(void);
#endif /* __ADC_H */
main.c:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "oled.h"
#include "ultrasonic.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
extern u16 msHcCount;
float distance;
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM1_Init();
/* USER CODE BEGIN 2 */
OLED_Init();
//HAL_Delay(1000);
// Ultrasonic_Init();
OLED_Clear();
HAL_TIM_Base_Start_IT(&htim1); //定时器1使能
//显示“距离:”
OLED_ShowChinese(0,0,0,16,1);
OLED_ShowChinese(16,0,1,16,1);
// OLED_ShowChar(32,0,':',16,1);
// OLED_ShowString(60,20,"cm",16,1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
distance = UltrasonicGetLength();
OLED_ShowFloat(40,32,distance,4,2,16,1);
// OLED_ShowNum(40,20,msHcCount,6,16,1);//定时中断已进
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
//void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
//if (htim->Instance == htim1.Instance) {
//}
//else if(htim-> Instance == htim2.Instance) {
//}
//}
// 定时器1 更新中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM1)
{
msHcCount++; // 增加计数值
}
}
//外部中断
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if(GPIO_Pin == ULTRASONIC_ECHO_Pin) {
//OLED_ShowString(60,20,"jin",16,1);
// 获取当前GPIO引脚的状态
uint8_t pin_state = HAL_GPIO_ReadPin(ULTRASONIC_ECHO_GPIO_Port,ULTRASONIC_ECHO_Pin);
// 检测到上升沿
if (pin_state == GPIO_PIN_SET)
{
// 上升沿操作
OpenTimerForHc();
}
// 检测到下降沿
else
{
CloseTimerForHc();
// 下降沿操作
}
}
}
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
3、问题解决
1、如果自己移植,注意在主函数中使能定时器1:
2、注意添加外部中断使能与处理、定时器中断处理:
下述代码位于上述main.c文件后面

四、视频演示
视频演示位于我主页的视频中
视频中测得的距离有点误差,可以通过调整下述参数来进行调节
下述参数为多次测量值求和取平均,改大测量误差可能小一点
五、工程文件
本代码由while循环一直等待ECHO引脚的电平变化改为通过外部中断检测超声波ECHO引脚电平变化
并将其从标准库移植到HAL库
如果对你有帮助请关注、点赞、收藏吧!
工程链接如下:
HC-SR04标准库工程文件
HC-SR04HAL库工程文件
如果你用了工程文件并且接线正确测出来的距离值仍然为0(不能正确测出距离值),那有可能是你给超声波HC-SR04的供电电压不够,请用万用表测量超声波模块的供电电压是否在5V左右。
更多推荐



所有评论(0)