STM32连接HC-SR04超声波测距(结合STM32CubeMX和HAL库函数)

实验说明

实验平台:STM32H743 (野火挑战者)

实验内容:使用GPIO激活HC-SR04超声波模块 ,接收信号。

使用IO口:PC10连接TRIG,PC11连接ECHO,GND接GND,VCC接5V。

HC-SR04超声波测距模块可提供2cm-400cm的非接触式距离感测功能,测距精度可达高到3mm;模块包括超声波发射器、接收器与控制电路。

STM32H743 控制器有 2 个高级控制定时器、10 个通用定时器和 2 个基本定时器,还有 2 个看门狗定时器。因为设计功能不复杂,这次主要使用的就是TIM6基本定时器,从表可以看出TIM6的时钟来源是APB1。
在这里插入图片描述
基本上定时器 TIM6 是一个 16 位向上递增的定时器,当我在自动重载寄存器(TIMx_ARR) 添加一个计数值后并使能 TIMx,计数寄存器 (TIMx_CNT) 就会从 0 开始递增,当TIMx_CNT 的数值与 TIMx_ARR 值相同时就会生成中断事件并把 TIMx_CNT 寄存器清 0。

原理十分简单,比如我往箱子里丢小球,丢一个小球为1s,如果我想计时10s,其实就是丢10次小球,那TIMx_CNT开始从0计数,当计数值达到10,就产生中断并清0,重新开始计数。这个10就是后面要设置的Period。

STM32CubeMX新建工程

1.设置定时器时钟

首先系统的时钟设置为400MHZ,具体如何设置可以参考STM32 RCC-使用HSE/HSI配置时钟(以STM32MUX时钟树作为图例)。最后可以看到APB1 Timer的时钟是200MHZ,不是上面那个100MHZ,那是搭载在APB1的其他外设时钟。
在这里插入图片描述

2.设置定时器

激活定时器TIM6,设置prescaler为19999,Counter Period为4999,auto-reload为Enable。
在这里插入图片描述

  • Prescaler:定时器预分频器设置,时钟源经该预分频器才是定时器时钟,它设定 TIMx_PSC寄存器的值。该寄存器位16位寄存器,可设置范围为 0 至 65535,实现 1 至 65536 分频。
  • CounterMode:定时器计数方式,可是在为向上计数、向下计数以及三种中心对齐模式。基本定时器只能是向上计数,即 TIMx_CNT 只能从 0 开始递增,并且无需初始化。
  • Period:定时器周期,实际就是设定自动重载寄存器的值。该寄存器位16位寄存器,可设置范围为 0 至 65535。

定时计算:
以上两个数字最终可以得出0.5s进一次中断,那是如何计算呢?

前面设置了定时器TIM6的时钟频率TIMxCLK为200MHZ,那是时钟来源,真正的定时器频率需要结合预分频进行计算,公式TIMxCLK/(TIM_Prescaler+1)=200M/(19999+1)=10000HZ。频率为10000HZ,时间就是0.1ms,也就是定时器计数增加1,时间为0.1ms。如果想要增加定时精度,就是需要调整预分频Prescaler。

现在知道了一次计数为0.1ms,我们需要定时0.5s,也就是500ms,可以反推出需要计数5000次。因为定时器是从0开始计算的,所以最终计数值Counter Period就是5000-1=4999。

点击NVIC中断管理,勾选定时器更新中断,产生溢出时会跳转到中断服务函数。
在这里插入图片描述

3.设置GPIO

GPIO口设置对应ECHO和TRIG引脚,一个触发信号输入,一个是回响信号输出。那就是设置两个GPIO口,一个输出触发超声波模块,一个输入接收信号。
在这里插入图片描述
选择自己喜欢的两个GPIO口,在下图中点击对应GPIO,选择Output Push Pull和Input mod模式。我选择的是PC10和PC11。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.生成工程

设置好参数,我们生成keil工程。

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

代码解析

生成的工程含有三个文件,分别是GPIO配置、定时器配置、中断相关文件。
在这里插入图片描述

gpio.c文件
void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : PtPin */
  GPIO_InitStruct.Pin = TRIG_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLDOWN;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(TRIG_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : PtPin */
  GPIO_InitStruct.Pin = ECHO_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLDOWN;
  HAL_GPIO_Init(ECHO_GPIO_Port, &GPIO_InitStruct);

}

这个文件主要是配置GPIO口,之前在STM32CubeMX设置的参数这里已经自动生成了,而且pin引脚也定义成了我们输入的标签TRIG_Pin和ECHO_Pin。

tim.c文件
void MX_TIM6_Init(void)
{
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim6.Instance = TIM6;
  htim6.Init.Prescaler = 19999;
  htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim6.Init.Period = 4999;
  htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

这是定时器初始化函数,比较重要的是设置预分频和计数值,19999和4999值。

void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM6)
  {
  /* USER CODE BEGIN TIM6_MspDeInit 0 */

  /* USER CODE END TIM6_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_TIM6_CLK_DISABLE();

    /* TIM6 interrupt Deinit */
    HAL_NVIC_DisableIRQ(TIM6_DAC_IRQn);
  /* USER CODE BEGIN TIM6_MspDeInit 1 */

  /* USER CODE END TIM6_MspDeInit 1 */
  }
}

这个是定时器中断回调函数,相当于定时中断就是进入这里执行代码。代码写在 /* USER CODE BEGIN TIM6_MspDeInit 0 */之间。

增加代码

前面都是CubeMX之间生成的工程文件,现在需要我们自己添加代码启动超声波模块。在tim.c文件添加两个启动和停止函数。

void start(void)
{
    __HAL_TIM_SetCounter(&TIM_Base,0);   //清0计数
    HAL_TIM_Base_Start_IT(&TIM_Base);   // 开启定时器更新中断       
}

unsigned int get_num(void)
{  
   unsigned int  num,dta;
   HAL_TIM_Base_Stop_IT (&TIM_Base);       //关闭定时器更新中断
   num = __HAL_TIM_GetCounter(&TIM_Base);  //获得计数值
   dta = num*0.1*34/2;                     //cm为单位
    return dta;
}

start经常用了HAL两个库函数,第一个函数作用是把TIMx_CNT 寄存器的计数值清0,第二个函数就是启动定时器更新中断。
在这里插入图片描述
通过超声波时序图可以知道, 只要ECHO引脚从0到1,我们开始定时,然后从1到0,停止定时。计算中间这段时间,然后乘以声音速度除于2(来回时间),就可以得知距离。

设置的计数值是4999,也就是0-4999计数5000次。一次0.1ms,总共计时0.5s进入中断,0.5s*340m/s/2等于85m。因为项目实验不需要测这么远,所以虽然我开了中断,但是这次实验并没有用到中断,而是在中断前就停止定时器。

我们通过__HAL_TIM_GetCounter函数得到TIMx_CNT寄存器的当前值,然后通过公式就可以算出距离。因为在循环测量距离当中并没有对定时器初始化进行循环,所以CNT的值并不会自动归0,这就是为什么start函数里面有清0操作__HAL_TIM_SetCounter(&TIM_Base,0)的原因。

建立echo.c文件存放超声波相关函数,如下。

//启动超声波模块
void  StartModule(void) 	//T1中断用来扫描数码管和计800MS启动模块
{
    HAL_GPIO_WritePin(TRIG_GPIO_Port,TRIG_Pin,1); 
    HAL_Delay(10);          //10ms延时
    HAL_GPIO_WritePin(TRIG_GPIO_Port,TRIG_Pin,0);  
}



//获得ECHO引脚状态  
int GetStatus(void)
{       
    return HAL_GPIO_ReadPin(ECHO_GPIO_Port,ECHO_Pin); 
    
}

在这里插入图片描述
通过超声波模块说明书,得知TRIG触发信号需要10us以上的高电平触发信号,这个可以通过延时和引脚写入函数完成,HAL_GPIO_WritePin()是HAL库的引脚写入函数,照着说明直接用就好。HAL_Delay()是从野火例程搬运的延时函数,用普通延时函数也可以的,只要保证10us以上就行。

读取ECHO状态使用的是HAL_GPIO_ReadPin()函数,这个也是HAL自带的读取引脚状态的函数,返回值为0或1。

	while(1)
	{	
        
        StartModule();               //启动超声波模块
        printf("超声波模块已启动\r\n");
        while(!GetStatus());        //当ECHO返回0,等待          
        start();
        while(GetStatus());		  //当ECHO为1计数并等待       
        dta = get_num();
        printf("测试距离为:%d cm\r\n",dta);	
        HAL_Delay(1000);		      //1000MS
	}	

当ECHO为0时,说明还没有信号返回,就while循环等待。如果ECHO变为1,会跳出循环,启动定时器,然后需要等待。如果ECHO从1到0,就会跳出循环,然后结束定时器计算距离。最后printf输出串口助手。
在这里插入图片描述

可以改进的方面

如果需要测量的距离大于85m怎么办,那就在中断函数里面计数,一次中断85m,两次中断170m,然后加上通过CNT算的距离,就是完整的距离。

一次计数是0.1ms,对应的距离是3.4cm,所以距离的分度值是3.4cm,测出的距离一定是它的倍数。如果想要增加精度,就需要把一次计数的时间0.1ms调小。通过公式TIMxCLK/(TIM_Prescaler+1)得知,我们把Prescaler调高,就可以增大定时器时钟频率,降低时钟周期。如果Prescaler取最小0,那定时器频率为200MHZ,一次计数的时间为1/200M=0.5*10负八次方s,对应的距离是1.7微米。

  • 15
    点赞
  • 124
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
以下是使用HAL库完成SR-04超声波传感器距离测量的步骤: 1. 配置STM32CUBEMX: - 将超声波传感器的Trig引脚连接STM32的GPIO输出引脚。 - 将超声波传感器的Echo引脚连接STM32的GPIO输入引脚。 - 在STM32CUBEMX中配置GPIO引脚的模式为输出和输入。 - 在STM32CUBEMX中配置TIM定时器,用于计算超声波的回声时间。 2. 编写HAL库程序: - 初始化GPIO引脚和TIM定时器。 - 发送一个10微秒的高电平脉冲到Trig引脚,触发超声波传感器。 - 等待Echo引脚变为高电平,并开始计时。 - 等待Echo引脚变为低电平,并停止计时。 - 根据计时器的值计算距离,并将其显示在LCD屏幕上。 下面是一个使用HAL库完成SR-04超声波传感器距离测量的示例代码: ```c #include "main.h" #include "stm32f1xx_hal.h" #define TRIG_PIN GPIO_PIN_0 #define TRIG_PORT GPIOA #define ECHO_PIN GPIO_PIN_1 #define ECHO_PORT GPIOA TIM_HandleTypeDef htim2; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_TIM2_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); while (1) { HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_RESET); HAL_Delay(2); HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_SET); HAL_Delay(10); HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_RESET); uint32_t start_time = 0; uint32_t end_time = 0; while (HAL_GPIO_ReadPin(ECHO_PORT, ECHO_PIN) == GPIO_PIN_RESET) { start_time = HAL_GetTick(); } while (HAL_GPIO_ReadPin(ECHO_PORT, ECHO_PIN) == GPIO_PIN_SET) { end_time = HAL_GetTick(); } uint32_t pulse_duration = end_time - start_time; float distance = pulse_duration * 0.034 / 2; // 将距离显示在LCD屏幕上 // ... HAL_Delay(100); } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; 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(); } 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(); } } static void MX_TIM2_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; htim2.Instance = TIM2; htim2.Init.Prescaler = 72 - 1; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 0xFFFFFFFF; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) { Error_Handler(); } } static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = TRIG_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(TRIG_PORT, &GPIO_InitStruct); GPIO_InitStruct.Pin = ECHO_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(ECHO_PORT, &GPIO_InitStruct); } ```
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值