系列文章目录(STM32常用外设/HAL库版)
一、HC-SR04超声波模块的使用(本篇)
二、OLED的HAL库代码介绍及使用
三、编码电机以及双电机驱动

本文主要介绍超声波模块HC-SR04的两种基于HAL库的使用方法,每一步代码都会有详细的解释说明。


前言

本文主要介绍超声波模块HC-SR04的两种基于HAL库的使用方法:
1.检测Echo接收端的高低电平法:根据超声波模块HC-SR04的时序图,直接给TRIG大于10us的高电平脉冲信号,然后读取ECHO引脚是否为高电平,若为高电平,则开启定时器,当其为低电平的时候,关闭定时器,获取计数器值,然后进行计算

2.利用定时器输入捕获功能,计算回响信号高电平持续时间,然后进行计算

一、超声波模块的简单介绍和程序原理图解

我这里也只是列出HC-SR04的时序图,详细的模块参数网上很多。
特别注意:HC-SR04每次发送超声波的时间间隔要大于60ms
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


二、Cube MX的配置

2.1 方法一的配置

方法一(检测Echo接收端的高低电平)需要用到TIM3、TIM4、USART1
其中TIM3使用定时器功能,TIM4设置为1us计数值加一的计数器

2.1.1 时钟树设置

在这里插入图片描述
在这里插入图片描述

2.1.2 引脚设置

在这里插入图片描述

2.1.3 定时器设置

TIM3设置:
在这里插入图片描述
TIM4设置:
将TIM4的预分频系数PSC设置为71,72M/(71+1)=1us,即TIM4每隔1us计数值加一
在这里插入图片描述

2.1.4 USART设置

开启USART1的异步模式,其余默认即可。
在这里插入图片描述

2.2 方法二的配置

方法二(输入捕获)需要用到TIM2、TIM3、USART1
其中TIM3与方法一*一致*使用定时器功能,TIM2使用输入捕获功能

2.2.1 时钟树设置

与 2.1.1 时钟树设置(方法一) 一致

2.2.2 引脚设置

方法二只需配置PB14(TRIG)为输出模式,这种方法的ECHO是定时器输入捕获通道,在配置TIM2时再设置。
在这里插入图片描述

2.2.3 定时器设置

TIM3设置与2.1.3 定时器设置(方法一) 一致

重点是TIM2的配置:
在这里插入图片描述

2.2.4 USART设置

与 2.1.4 USART1设置(方法一) 一致

2.3 其他设置

注:2.3的设置只是为了利用串口在电脑上通过超级终端软件观察超声波的测距值,这部分不设置以及之前不设置usart1,之后不添加retarget.c和retarget.h并不会影响超声波测距驱动程序的使用。

2.3.1 printf显示float变量

在这里插入图片描述

2.3.2将syscalls.c在构建中排除

务必将syscalls.c在构建中排除,因为syscalls.c会影响printf()的使用
在这里插入图片描述


三、超声波测距代码

3.1 方法一(检测Echo接收端的高低电平)

程序框架:
其中的retarget.c和retarget.h,本文后面第四部分的其他代码中给出
在这里插入图片描述

代码如下:
ultrasonic.c和ultrasonic.h文件可以放在一个文件夹中并添加到icode中(icode SourceFolder文件夹需要自己建立)

ultrasonic.h

#ifndef ULTRASONIC_ULTRASONIC_H_
#define ULTRASONIC_ULTRASONIC_H_

#include "stm32f1xx_hal.h" //HAL库文件声明
#include <main.h>
#include "../delay/delay.h"

#define TRIG_ON  HAL_GPIO_WritePin(GPIOB, TRIG_Pin, GPIO_PIN_SET)
#define TRIG_OFF  HAL_GPIO_WritePin(GPIOB, TRIG_Pin, GPIO_PIN_RESET)
extern TIM_HandleTypeDef htim3;
extern TIM_HandleTypeDef htim4;


void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) ;

#endif /* ULTRASONIC_ULTRASONIC_H_ */

ultrasonic.c

#include "ultrasonic.h"
/********************************************************************************************
 *设置变量
 *distances:超声波所测距离
 *t        :回响信号脉冲持续时间
 *count    :防止程序死循环所用计数值
 ********************************************************************************************/
float distances;
uint32_t t=0;
uint32_t count=0;

/********************************************************************************************
 * 定时器中断回调函数 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
 *
 * 因为SR04每次发送超声波的时间间隔要大于60ms,
 * 故通过Cube MX 已经将TIM3设置为100ms的定时器,每隔100ms才执行一次这个定时器中断回调函数
 *
 ********************************************************************************************/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{


	if(htim==&htim3)                      //判断是否为TIM3溢出中断
	{
		TRIG_OFF;                         //先将超声波模块SR04的发送端TRIG拉低
		TRIG_ON;                          //再将超声波模块SR04的发送端TRIG拉高,并且持续20ms后再拉低
		delay_us(20);                     //延时20us,即向TRIG端发送一个20us的高电平脉冲
		TRIG_OFF;
		while(!(HAL_GPIO_ReadPin(GPIOB,ECHO_Pin)))
		{                                 //读取ECHO,直至接收端ECHO接收到高电平,跳出while
			count++;
			if(count>100000)              //加入if(count>100000) 这个判断语句作用:防止程序死循环
			{
				break;
			}

		}
		count=0;
		__HAL_TIM_SET_COUNTER(&htim4,0);//等价于htim4.Instance->CNT = 0; 因为在Cube MX 中将TIM4的预分频系数设置为71,故TIM4每隔1us计数器的值加1
	    __HAL_TIM_ENABLE(&htim4);         //开启IM4
		while(HAL_GPIO_ReadPin(GPIOB,ECHO_Pin))
		{                                 //读取ECHO,直至接收端ECHO接收到低电平,跳出while
			count++;
			if(count>100000)
			{
				break;
			}

		}
		count=0;
		__HAL_TIM_DISABLE(&htim4);         //关闭TIM4
		t=__HAL_TIM_GET_COUNTER(&htim4);//等价于t=(htim4.Instance->CNT);
		distances= t*0.017;               //声速0.034cm/us,计算出的距离要除以2,distances的单位是cm

	}
}


main.c

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "../../icode/ultrasonic/ultrasonic.h"
#include "../Inc/retarget.h"  //用于超级终端

/* USER CODE END Includes */

int main(void)
{
  /* USER CODE BEGIN 1 */
	extern float distances;
  /* 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_TIM3_Init();
  MX_TIM4_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */

  HAL_TIM_Base_Start_IT(&htim3);   //开启TIM3溢出中断
  RetargetInit(&huart1);//将超级终端显示映射到串口一

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  	  printf("%f\r\n",distances);
  	  

  }
  /* USER CODE END 3 */
}


3.2 方法二(输入捕获)

程序框架:
其中的retarget.c和retarget.h,本文后面第四部分的其他代码中给出
在这里插入图片描述

代码如下:
chaoshengbo.c和 chaoshengbo.h文件可以放在一个文件夹中并添加到icode中(icode SourceFolder文件夹需要自己建立)

chaoshengbo.h

#ifndef CHAOSHENGBO_CHAOSHENGBO_H_
#define CHAOSHENGBO_CHAOSHENGBO_H_

#include "stm32f1xx_hal.h" //HAL库文件声明
#include <main.h>
#include "../delay/delay.h"

#define TRIG_ON  HAL_GPIO_WritePin(GPIOB, TRIG_Pin, GPIO_PIN_SET)   //定义TRIG输出高电平
#define TRIG_OFF  HAL_GPIO_WritePin(GPIOB, TRIG_Pin, GPIO_PIN_RESET)//定义TRIG输出低电平
extern TIM_HandleTypeDef htim2;//声明TIM2的HAL库结构体
extern TIM_HandleTypeDef htim3;//声明TIM3的HAL库结构体

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);//定时器中断回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);//定时器输入捕获中断回调函数

#endif /* CHAOSHENGBO_CHAOSHENGBO_H_ */

chaoshengbo.c

#include "chaoshengbo.h"
/********************************************************************************************
 *设置变量
 *distances:超声波所测距离
 *t        :回响信号脉冲持续时间
 *high_time[0]:回响信号脉冲上升沿发生时间
 *high_time[1]:回响信号脉冲下降沿发生时间
 *c_values :标志值,用于定时器输入捕获回调函数

 ********************************************************************************************/
float distances;
uint32_t t=0;
uint32_t high_time[2]={0};
uint8_t c_values=0;

/********************************************************************************************
 * 定时器中断回调函数 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
 *
 * 说明:
 * 因为SR04每次发送超声波的时间间隔要大于60ms,
 * 故通过Cube MX 已经将TIM3设置为100ms的定时器,每隔100ms才执行一次这个定时器中断回调函数
 * 即每隔100ms发送一个20us的高电平脉冲,同时开启定时器2输入捕获并设置为上升沿捕获
 *
 ********************************************************************************************/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{


	if(htim==&htim3)                      //判断是否为TIM3溢出中断
	{
		TRIG_OFF;                         //先将超声波模块SR04的发送端TRIG拉低
		TRIG_ON;                          //再将超声波模块SR04的发送端TRIG拉高,并且持续20ms后再拉低
		delay_us(20);
		TRIG_OFF;
		__HAL_TIM_SET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_1,TIM_ICPOLARITY_RISING);//设置为上升沿捕获
		HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);//开启定时器输入捕获
	}

}

/********************************************************************************************
 * 定时器输入捕获回调函数 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
 *
 *说明:
 *ECHO接收回响信号脉冲,这个回调函数要被执行2次
 *这个回调函数在类似于一个分叉路口,上升沿捕获走路口0,下降沿捕获走路口1
 *
 ********************************************************************************************/
	void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
	{
		if(htim==&htim2)
		{
			switch(c_values)
			{//标志值c_values初始设定值为0,上升沿输入捕获,先执行回调函数执行case(0)中内容

			case(0): high_time[0]=HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_1);//获取上升沿的捕获值
			         __HAL_TIM_SET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING);//设置为下降沿捕获
			         c_values++;//标志值c_values值变为1,下次回调函数执行case(1)中内容
			         break;


			case(1): high_time[1]=HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_1);//获取下降沿的捕获值
			         HAL_TIM_IC_Stop_IT(&htim2,TIM_CHANNEL_1); //关闭TIM2输入捕获
			         c_values=0;                 //标志值清零,用于下次输入捕获回调函数
			        __HAL_TIM_SET_COUNTER(&htim2,0);//等价于 htim2.Instance->CNT=0;      TIM2计数值清零
			         t=high_time[1]-high_time[0];//下降沿捕获值-上升沿捕获值=回响信号高电平脉冲持续时间t
				     distances= t*0.017; //速度0.034cm/us,计算出的距离要除以2,distances的单位是cm
			         break;

			default: break;

			}

		}

	}


main.c

/* USER CODE BEGIN Includes */
#include "../../icode/chaoshengbo/chaoshengbo.h"
#include "../Inc/retarget.h"  //用于printf函数串口重映射

/* USER CODE END Includes */

int main(void)
{
  /* USER CODE BEGIN 1 */
	extern float distances;
  /* 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_TIM3_Init();
  MX_USART1_UART_Init();
  MX_TIM2_Init();
  /* USER CODE BEGIN 2 */

  HAL_TIM_Base_Start_IT(&htim3);   //开启TIM3溢出中断
  RetargetInit(&huart1);           //用于将printf()函数映射到串口UART1上

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

  	  printf("%f\r\n",distances);

  }
  /* USER CODE END 3 */
}


四、其它代码

4.1 us级延时函数代码

delay.h

#ifndef DELAY_DELAY_H_
#define DELAY_DELAY_H_

#include "stm32f1xx_hal.h" HAL库文件声明
void delay_us(uint32_t us); //us级延时函数

#endif /* DELAY_DELAY_H_ */

delay.c

delay.c和delay.h文件可以放在一个delay文件夹中并添加到icode中

#include "delay.h"

void delay_us(uint32_t us) //利用CPU循环实现的非精准应用的微秒延时函数
{
    uint32_t delay = (HAL_RCC_GetHCLKFreq() / 8000000 * us); //使用HAL_RCC_GetHCLKFreq()函数获取主频值,经算法得到1微秒的循环次数
    while (delay--); //循环delay次,达到1微秒延时
}

4.2 超级终端使用代码

在STM32中使用printf()和scanf的学习链接

这部分代码是直接采用官方库的代码,无需理解直接采用即可。 将retarget.h 添加到Core->Inc中, retarget.c添加到Core->Src中。

retarget.h

#ifndef INC_RETARGET_H_
#define INC_RETARGET_H_

#include "stm32f1xx_hal.h"
#include "stdio.h"
#include <sys/stat.h>

void RetargetInit(UART_HandleTypeDef  *huart);

int _isatty(int fd);
int _write(int fd, char* ptr, int len);
int _close(int fd);
int _lseek(int fd, int ptr, int dir);
int _read(int fd, char* ptr, int len);
int _fstat(int fd, struct stat* st);

#endif /* INC_RETARGET_H_ */

retarget.c

#include <_ansi.h>
#include <_syslist.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/times.h>
#include <limits.h>
#include <signal.h>
#include <../Inc/retarget.h>
#include <stdint.h>
#include <stdio.h>
#if !defined(OS_USE_SEMIHOSTING)
#define STDIN_FILENO  0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2

UART_HandleTypeDef *gHuart;

void RetargetInit(UART_HandleTypeDef *huart)  {
  gHuart = huart;
  /* Disable I/O buffering for STDOUT  stream, so that
   * chars are sent out as soon as they are  printed. */
  setvbuf(stdout, NULL, _IONBF, 0);
}
int _isatty(int fd) {
  if (fd >= STDIN_FILENO && fd <=  STDERR_FILENO)
    return 1;
  errno = EBADF;
  return 0;
}
int _write(int fd, char* ptr, int len) {
  HAL_StatusTypeDef hstatus;
  if (fd == STDOUT_FILENO || fd ==  STDERR_FILENO) {
    hstatus = HAL_UART_Transmit(gHuart,  (uint8_t *) ptr, len, HAL_MAX_DELAY);
    if (hstatus == HAL_OK)
      return len;
    else
      return EIO;
  }
  errno = EBADF;
  return -1;
}
int _close(int fd) {
  if (fd >= STDIN_FILENO && fd <=  STDERR_FILENO)
    return 0;
  errno = EBADF;
  return -1;
}
int _lseek(int fd, int ptr, int dir) {
  (void) fd;
  (void) ptr;
  (void) dir;
  errno = EBADF;
  return -1;
}
int _read(int fd, char* ptr, int len) {
  HAL_StatusTypeDef hstatus;
  if (fd == STDIN_FILENO) {
    hstatus = HAL_UART_Receive(gHuart,  (uint8_t *) ptr, 1, HAL_MAX_DELAY);
    if (hstatus == HAL_OK)
      return 1;
    else
      return EIO;
  }
  errno = EBADF;
  return -1;
}
int _fstat(int fd, struct stat* st) {
  if (fd >= STDIN_FILENO && fd <=  STDERR_FILENO) {
    st->st_mode = S_IFCHR;
    return 0;
  }
  errno = EBADF;
  return 0;
}

#endif //#if !defined(OS_USE_SEMIHOSTING)


五、超级终端软件的使用

你也可以用别的,本文重点是超声波模块的2种测距方法
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

超级终端软件的下载

链接:https://pan.baidu.com/s/1RKecInwbcZjybld_EUBEGg?pwd=lswp
提取码:lswp


六、演示效果

超声波测距演示

测量20cm:

数据有很多重复的,是因为每隔100ms超声波才测距一次,而超级终端是不停地显示数据
在这里插入图片描述
在这里插入图片描述


总结

其实无论是哪种方法,超声波测距的原理就是距离=速度X时间,可能最后测出的距离与标准值有些许误差,是由于我在计算距离时用的光速是理论值340m/s,但实际上肯定有一丢丢误差。

最后放上文章中定时器输入捕获源码 https://download.csdn.net/download/LYH6767/85218812

欢迎大家积极交流,本文未经允许谢绝转载!!!

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐