一、模块简介

常见的超声波模块代码通常为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左右。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐