STM32超声波模块 HAL库 输入捕获

2023-11-11

前言

本教程使用的是STM32F103C6T6,利用定时器输入捕获双边沿,实现超声波模块的使用。
创作初衷是在于大多数博客都是照抄前人的形式,且代码上有出入,cubemx开启通道一,代码内却是通道二等情况很多,对于新手来说很不友好。

软件及硬件准备

软件:
keil5
CubeMX
XCOM
硬件:
主控板:STM32F103C6T6(也可以使用STM32其他型号)
超声波模块:HC_SR04
USB转TTL

硬件连接

在这里插入图片描述
VCC —— 5v(该模块工作电压需要5v,如果接3.3v,模块可能不工作)
GND —— GND
Trig —— 连接单片机的io口(任意io口都可,起控制作用)
Echo —— 连接单片机的定时器输入捕获通道

CubeMX配置

定时器配置

在这里插入图片描述

如图,按照指示进行定时器配置即可
我选择的是TIM2CH2,可根据自己的需求选择定时器和通道

在这里插入图片描述

这里是最重要的一步,使能定时器中断,如果最后超声波模块的值一直为0,一定要回来检查定时器配置

在这里插入图片描述

选择任意io口,作为Trig脚来使用,配置为output输出模式即可,不必上下拉,初始化为低电平
此处我使用的是PA5

串口配置

在这里插入图片描述

开启串口,选择异步模式,修改波特率为115200,可自行修改为其他波特率

keil软件编写

/* USER CODE BEGIN 1 */
//[7]:0,没有成功的捕获;1,成功捕获到一次.
//[6]:0,还没捕获到低电平;1,已经捕获到低电平了.
//[5:0]:捕获低电平后溢出的次数
uint8_t TIM2CH2_CAPTURE_STA;  // 输入捕获状态
uint16_t TIM2CH2_CAPTURE_VAL; //输入捕获值
//溢出回调函数和捕获回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if ((TIM2CH2_CAPTURE_STA & 0x80) == 0) // 还未捕获成功
  {
    if (TIM2CH2_CAPTURE_STA & 0x40) // 捕获到一个下降沿
    {
      if ((TIM2CH2_CAPTURE_STA & 0x3F) == 0x3F) // 高电平的时间太长
      {
        TIM2CH2_CAPTURE_STA |= 0X80; // 标记为成功捕获一次
        TIM2CH2_CAPTURE_VAL = 0XFFFF;
      }
      else
        TIM2CH2_CAPTURE_STA++; // 否则标记溢出数加1
    }
  }
}

// 捕获中断发生时执行 上升沿复位开始计时,下降沿获取捕获值计算
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
  if ((TIM2CH2_CAPTURE_STA & 0X80) == 0) //还未捕获成功  [7]:0,没有成功的捕获;1,成功捕获到一次.
  {
    if (TIM2CH2_CAPTURE_STA & 0X40) // 成功率捕获到1个下降沿  [6]:0,还没捕获到低电平;1,已经捕获到低电平了.
    {
      // usart_printf("get down\r\n");
      TIM2CH2_CAPTURE_STA |= 0X80;                                            // 标记成功,捕获到1次高电平完成
      TIM2CH2_CAPTURE_VAL = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_2); // 捕获当前设置捕获值
      TIM_RESET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2);                       // 清除原来设置
      TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2, TIM_ICPOLARITY_RISING);  // 捕获到下降沿之后,将捕获到复位为上升沿
    }
    else // 捕获到一个上升沿
    {
      // usart_printf("get up\r\n");
      TIM2CH2_CAPTURE_STA = 0;
      TIM2CH2_CAPTURE_VAL = 0;
      TIM2CH2_CAPTURE_STA |= 0X40; //将STA置为0x40 当下一次触发中断时,会进入上面的if语句
      __HAL_TIM_DISABLE(&htim2);
      __HAL_TIM_SET_COUNTER(&htim2, 0);
      TIM_RESET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2);
      TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2, TIM_ICPOLARITY_FALLING);
      __HAL_TIM_ENABLE(&htim2);
    }
  }
}
/* USER CODE END 1 */

代码是原子哥写的,我只是搬运工,在原来注释的基础上,加了一些我自己的注释
将该段代码复制到tim.c中,在cubemx配置好定时器后,会自动生成tim.c文件。
在tim.c文件最下方可以看见/* USER CODE BEGIN 1 */,复制到这里即可

/**
 * @file HCSR04.c
 * @author Zhong Zepeng (1935595312@qq.com)
 * @brief
 * @version 0.1
 * @date 2022-11-25
 *
 * @copyright Copyright (c) 2022
 *
 */
#include "HCSR04.h"
#include "gpio.h"
#include "tim.h"
/**
 * @brief 激活超声波定时器
 * 
 */
void HCSR_04()
{
    uint32_t i;
    HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET);
    for (i = 0; i < 72 * 40; i++)
        __NOP();
    HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET);
}

/**
 * @brief 计算超声波检测的距离
 * 
 * @return float 
 */
float getSR04Distance()
{
    float len = 0;
    uint32_t time = 0;
    if (TIM2CH2_CAPTURE_STA & 0X80) //输入捕获 触发
    {
        time = TIM2CH2_CAPTURE_STA & 0X3f;   //获得溢出次数
        time *= 65536;                       //一次溢出为65536 得到溢出的时间
        time += TIM2CH2_CAPTURE_VAL;         //溢出的时间+现在定时器的值 得到总的时间
        len = time * 342.62 * 100 / 2000000; // 计算得到距离

        TIM2CH2_CAPTURE_STA = 0; //清除溢出
    }
    return len;
}

↑ 编写SR04.c文件,将代码复制进去

#ifndef __HCSR04_H
#define __HCSR04_H

#include "stdint.h"


void HCSR_04(void);
float getSR04Distance(void);

#endif

↑ SR04.h文件

#include "User_Debug.h"

#include "stdio.h"
#include "stdarg.h"
#include "string.h"
#include "usart.h"

void usart_printf(const char *fmt,...)
{
  static uint8_t tx_buf[256] = {0};
  static va_list ap;
  static uint16_t len;
		
  va_start(ap, fmt);
  len = vsprintf((char *)tx_buf, fmt, ap);
  va_end(ap);
  HAL_UART_Transmit(&huart1,tx_buf,len,100);
}

↑ 编写串口打印文件,我使用的是USART1,如果使用其他串口,只需修改最后一行的&huart1
在头文件里声明usart_printf()函数即可

/* USER CODE BEGIN Includes */
#include "User_Debug.h"
#include "HCSR04.h"
/* USER CODE END Includes */

↑ 在main.c里引用SR04头文件和串口打印头文件

  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start(&htim2);                 //开启定时器
  HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2); //开启TIM2的捕获通道2,并开启捕获
  __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE); //使能更新中断
  usart_printf("start ok\r\n");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    HAL_Delay(100); //延时,不做延时的话,会超过采样频率
    HCSR_04();      //激活超声波模块
    float distance = getSR04Distance();
    usart_printf("dis = %.2f\r\n", distance);

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

↑ 在main函数的while(1)及上方添加上述代码

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.h
  * @brief          : Header for main.c file.
  *                   This file contains the common defines of the application.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2022 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 */

/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __MAIN_H
#define __MAIN_H

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "stm32f1xx_hal.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Exported types ------------------------------------------------------------*/
/* USER CODE BEGIN ET */

/* USER CODE END ET */

/* Exported constants --------------------------------------------------------*/
/* USER CODE BEGIN EC */

/* USER CODE END EC */

/* Exported macro ------------------------------------------------------------*/
/* USER CODE BEGIN EM */

/* USER CODE END EM */

/* Exported functions prototypes ---------------------------------------------*/
void Error_Handler(void);

/* USER CODE BEGIN EFP */

/* USER CODE END EFP */

/* Private defines -----------------------------------------------------------*/
#define TRIG_Pin GPIO_PIN_5
#define TRIG_GPIO_Port GPIOA
/* USER CODE BEGIN Private defines */
//[7]:0,没有成功的捕获;1,成功捕获到一次.
//[6]:0,还没捕获到低电平;1,已经捕获到低电平了.
//[5:0]:捕获低电平后溢出的次数
extern uint8_t TIM2CH2_CAPTURE_STA;  // 输入捕获状态
extern uint16_t TIM2CH2_CAPTURE_VAL; //输入捕获值
/* USER CODE END Private defines */

#ifdef __cplusplus
}
#endif

#endif /* __MAIN_H */

↑ 在main.h中添加全局变量
extern uint8_t TIM2CH2_CAPTURE_STA; // 输入捕获状态
extern uint16_t TIM2CH2_CAPTURE_VAL; //输入捕获值

代码讲解

//[7]:0,没有成功的捕获;1,成功捕获到一次.
//[6]:0,还没捕获到低电平;1,已经捕获到低电平了.
//[5:0]:捕获低电平后溢出的次数
uint8_t TIM2CH2_CAPTURE_STA;  // 输入捕获状态
uint16_t TIM2CH2_CAPTURE_VAL; //输入捕获值
//溢出回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if ((TIM2CH2_CAPTURE_STA & 0x80) == 0) // 还未捕获成功
  {
    if (TIM2CH2_CAPTURE_STA & 0x40) // 已捕获到一个上升沿
    {
      if ((TIM2CH2_CAPTURE_STA & 0x3F) == 0x3F) // 高电平的时间太长
      {
        TIM2CH2_CAPTURE_STA |= 0X80; // 标记为成功捕获一次
        TIM2CH2_CAPTURE_VAL = 0XFFFF;
      }
      else
        TIM2CH2_CAPTURE_STA++; // 否则标记溢出数加1
    }
  }
}

原子哥的代码采用的是寄存器的思想,设置一个8位的状态寄存器TIM2CH2_CAPTURE_STA
该寄存器最高位为定时器是否捕捉到边沿变化
次高位为定时器是否开始捕捉下降沿,用于记录当前定时器状态

所以 if ((TIM2CH2_CAPTURE_STA & 0x80) == 0) 该句的意思为如果最高位是0,即还未捕捉到边沿变化时,会进入if语句内(0x80 = 1000 0000)

if (TIM2CH2_CAPTURE_STA & 0x40) 该句的意思为 次高位如果是1,则进入if语句(0x40 = 0100 0000)
所以现在有两种情况
1、还未捕捉到上升沿,进入第一个if语句后,不会做任何操作。
2、已经捕捉到了上升沿,等待捕捉下降沿,会进入到第二个if语句中,直到定时器捕捉到下降沿才会跳出if语句

第二个if内部有一组if else。其意思是STA会在此处自加,但是如果STA加到0x3f,就会手动置最高位为1,使程序跳出,不会死锁在此处。并将VAL写为0xFFFF,将VAL填满,表示为距离超过最大测量范围。
因为STA的最高位和最低位是状态位,所以STA最高可以计数到0x3f = 0011 1111。

// 捕获中断发生时执行 上升沿复位开始计时,下降沿获取捕获值计算
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
  if ((TIM2CH2_CAPTURE_STA & 0X80) == 0) //还未捕获成功  [7]:0,没有成功的捕获;1,成功捕获到一次.
  {
    if (TIM2CH2_CAPTURE_STA & 0X40) // 成功率捕获到1个下降沿  [6]:0,还没捕获到低电平;1,已经捕获到低电平了.
    {
      // usart_printf("get down\r\n");
      TIM2CH2_CAPTURE_STA |= 0X80;                                            // 标记成功,捕获到1次高电平完成
      TIM2CH2_CAPTURE_VAL = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_2); // 捕获当前设置捕获值
      TIM_RESET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2);                       // 清除原来设置
      TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2, TIM_ICPOLARITY_RISING);  // 捕获到下降沿之后,将捕获到复位为上升沿
    }
    else // 捕获到一个上升沿
    {
      // usart_printf("get up\r\n");
      TIM2CH2_CAPTURE_STA = 0;
      TIM2CH2_CAPTURE_VAL = 0;
      TIM2CH2_CAPTURE_STA |= 0X40;                                            //将STA置为0x40 当下一次触发中断时,会进入上面的if语句
      __HAL_TIM_DISABLE(&htim2);                                              //关闭定时器
      __HAL_TIM_SET_COUNTER(&htim2, 0);                                       //将定时器计数值清零
      TIM_RESET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2);						  //清除输入捕获标志位
      TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2, TIM_ICPOLARITY_FALLING); //将输入捕获上升沿改为捕获下降沿
      __HAL_TIM_ENABLE(&htim2);												  //使能定时器,开启定时器
    }
  }
}

中断函数,当定时器捕获到边沿变换时,会执行下列操作,第一个if前面已经讲过,咱直接看里面的if else

我们先看else,当我们捕获到上升沿时会进入else语句中,此时会将寄存器清零,并将次高位置1,表示已经捕获到上升沿,接下来要捕获下降沿,VAL清零。然后将定时器复位
else语句完成后,定时器会复位并被打开,且此时开始捕捉下降沿

当定时器再一次捕捉时,捕捉到的是下降沿,此时超声波模块已经完成了一次工作,我们只需把时间记录下来就可以计算距离了。
首先使STA最高位置1,使程序跳出上面的溢出函数,这时STA不再自加,然后将定时器里的值读取到VAL中。最后修改复位一下定时器,即完成了一次超声波模块的完整工作

void HCSR_04()
{
    uint32_t i;
    HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET);
    for (i = 0; i < 72 * 40; i++)
        __NOP();
    HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET);
}
float getSR04Distance()
{
    float len = 0;
    uint32_t time = 0;
    if (TIM2CH2_CAPTURE_STA & 0X80) //输入捕获 触发
    {
        time = TIM2CH2_CAPTURE_STA & 0X3f;   //获得溢出次数
        time *= 65536;                       //一次溢出为65536 得到溢出的时间
        time += TIM2CH2_CAPTURE_VAL;         //溢出的时间+现在定时器的值 得到总的时间
        len = time * 342.62 * 100 / 2000000; // 计算得到距离

        TIM2CH2_CAPTURE_STA = 0; //清除溢出
    }
    return len;
}

我们在main函数中调用了这两个函数,上面一个函数用来激活超声波模块,根据超声波模块的使用要求,先将Trig引脚置高,等待至少10us,再将Trig置低,就会激活超声波模块开始工作

下面这个函数用来计算距离,当超声波模块完成一次工作后,STA的最高位会被置为1
TIM2CH2_CAPTURE_STA |= 0X80; 将最高位置为1;
此时程序不会再进行输入捕获,所以在我们计算得到距离后,需要将STA清零,使得超声波可以进行下一次工作,TIM2CH2_CAPTURE_STA = 0;
距离是由时间计算出来的,而时间由两部分组成,一部分是定时器溢出次数,一部分是定时器内还未溢出的值,即STA*65536+VAL
得到time后,按照公式即可算出距离len = time * 342.62 * 100 / 2000000;

注意事项

当返回值一直为0
1、首先检查配置,特别是cubemx中定时器中断是否开启
2、超声波模块是否激活,Trig引脚是否拉高了有10个us,一般在15到20us

github下载地址:https://github.com/FollowTheWay/HC_SR04.git
CSND下载地址:https://download.csdn.net/download/qq_51967985/87196492

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32超声波模块 HAL库 输入捕获 的相关文章

  • 在gitee网页中创建分支后,在vscode中更新分支

    在vscode中更新gitee上创建的分支 在网页创建分支之后 vscode中git pull origin更新在gitee创建的分支 更新之后这里有origin test分支 选中之后 通过git bracn就可以看到线上的分支了
  • 深度理解yolov3损失函数

    深度理解yolov3损失函数 在yolov3中 loss分为三个部分 一个是xywh部分带来的误差 也就是bbox带来的loss 一个是置信度带来的误差 也就是obj带来的loss 最后一个是类别带来的误差 也就是class带来的loss
  • 支付订单同步回调

    public function pay params input 接收数据 判断订单号支付方式不为空 validate this gt validate params order sn gt require pay code 支付方式 gt

随机推荐

  • 30秒内便能学会的30个超实用Python代码片段

    许多人在数据科学 机器学习 web开发 脚本编写和自动化等领域中都会使用Python 它是一种十分流行的语言 Python流行的部分原因在于简单易学 本文将简要介绍30个简短的 且能在30秒内掌握的代码片段 1 唯一性 以下方法可以检查给定
  • MFC多线程同步

    MFC提供了多种同步对象 下面我们只介绍最常用的四种 1 临界区 CCriticalSection 2 事件 CEvent 3 互斥量 CMutex 4 信号量 CSemaphore 一 临界区 使用CCriticalSection类 是一
  • vue路由拦截

    1 在main js中写入下面这些代码 路由拦截 router beforeEach to from next gt if to meta requireAuth 判断该路由是否需要登录权限 if sessionStorage getIte
  • git 下载和安装

    1 什么是git git 是一个分布式的版本控制工具 git 官网 git 官网 2 为什么使用git 保留之前所有版本 以便后续的修改和回滚 方便多人协同开发 3 安装git 3 1 在Linux 上安装 以 Fedora 为例 如果你在
  • Nginx Ubuntu下编译和安装

    1 登录nginx官网http nginx org en download html 下载nginx最新版本 解压nginx 得到目录nginx 1 17 8 tar zxvf nginx 1 17 8 tar gz 2 下载http fl
  • YouCompleteMe unavailable : requires Vim 7.4.143

    版本问题 ubuntu 14 05 安装完YouCompleteMe后不生效 提示 YouCompleteMe unavailable requires Vim 7 4 143经过检索与查询 ubuntu自带的vim为7 4 50 需要安装
  • js延时函数

    js延时函数总结 在js中 延迟执行函数有两种 setTimeout和setInterval 用法如下 setTimeout test 5000 5秒后执行testFunction 函数 只执行一次 setInterval test 500
  • Ubuntu小技巧9--使用Samba服务实现Windows和Linux文件访问

    Ubuntu小技巧8 Ubuntu小技巧9 使用Samba服务实现Windows和Linux文件访问 很多时候做开发的时候需要在Linux上编译运行 但是又想用Windows的优秀工具 不停的将文件拷贝到Windows和Linux是极其浪费
  • 高德地图api之编码(Geocoder)

    高德地图目前仅支持中国范围内的的地理编码和反地理编码 当我们在做搜索功能的时候 由于用户不可以记住地点的经纬度 所以只可能输入地名 所以地理编码就显得额外重要 这里我们查看一下AMap api中的地理编码 地理编码 地理编码 顾名思义就是根
  • Linux系列:编写 jar包 启动脚本

    包含指定环境 启动的脚本 指定springBoot的 application properties 创建 start sh 文件 文件内容如下 1 直接启动 bin sh 该脚本为Linux下启动java程序的 1第一个参数为方法名star
  • 设计模式-装饰模式(Decorator)

    装饰模式又叫包装模式 通过一种对客户端透明的方式来扩展对象的功能 是继承关系的一个替换方案 角色和职责 1 抽象组件 Component Car 为统一接口 也是装饰类和被装饰类的统一接口 2 具体组件 concrete Component
  • DGL中minibatch训练子图Prefetch到GPU中加速

    2022 04 update 最近DGL官方提供了prefetch的功能 可以直接使用官方实现了 https github com dmlc dgl pull 3665 导读 这两天在研究怎么加速DGL上GNN的训练 使用line prof
  • 关于内核autoconf.h

    1 老版内核autoconf h的生成 在make menuconfig时生成 config然后再make zImage时就会由 config生成autoconf h 都是脚本做的 2 关于新版的内核 在比较老的版本比如http lxr l
  • 分享160多种ChatGPT 高频中文prompt 提示词指令合集——秒变AI训练师

    ChatGPT 中文 Prompt 提示词 常用 高频集合 在本篇文档中 我将展示超过 160 种 prompt 提示词 你可以挑选自己感兴趣的提示词进行尝试 国内免费ChatGPT聊天地址 https chat bytearch com
  • react学习—ref Hook

    ref Hook 一 ref Hook 1 useRef 2 其它使用 一 ref Hook 1 useRef useRef函数 一个参数 默认值 返回一个固定的对象 current 值 import React createRef use
  • 如何寻找数组中的最大值和最小值

    以下五种解法可以寻找到数组中的最大值和最小值 1 问题分解法 把本题看做两个独立的问题 而非一个问题 所以 每次分别找出最小值和最大值即可 此时 一共需要遍历两次数组 比较次数为2N次 N表示数组的长度 2 取单元素法 维持两个变量min和
  • 为什么ping不通某网站,但是却可以访问该网站?

    本文转载自 http blog csdn net u013078669 article details 48437559 今天在访问一个网站的时候 我想看看它的ip是多少 就在windows 命令行下去ping了下该网站 ip地址是解析出来
  • Hive sql语法详解

    1 DDL 操作 DDL 建表 删除表 修改表结构 创建 删除视图 创建数据库 显示命令 建表 CREATE EXTERNAL TABLE IF NOT EXISTS table name col name data type COMMEN
  • Sublime Text3 js语法错误提示

    1 插件安装输入 SublimeLinter 2 插件安装输入 sublimeLinter jshint 3 确保安装了nodeJs 4 命令行进入npm目录 输入 npm install g jshint 5 重启Sublime ctrl
  • STM32超声波模块 HAL库 输入捕获

    STM32 HAL库 CubeMX 输入捕获 超声波模块 前言 软件及硬件准备 硬件连接 CubeMX配置 定时器配置 串口配置 keil软件编写 代码讲解 注意事项 前言 本教程使用的是STM32F103C6T6 利用定时器输入捕获双边沿