第五节:STM32输入捕获(用CubeMX学习STM32)

摘要:
测量时间。例如,超声波测距模块需要使用输入捕获功能,使用CubeMX完成配置,并编程HAL库功能,如图所示:溢出数和检测高电平和低电平的数据记录在自己设置的变量中,N*ARR+CRCx2是CNT计数的数量,不同的位存储不同的数据5.1操作介绍通过信号发生器向微控制器的相应引脚输入具有给定频率和占空比的矩形波信号,

输入捕获学习


《用CubeMX学习STM32》

注释 点击上面蓝字进入完整专栏,这个系列所有文章都会整合到这个专栏

5、STM32定时器输入捕获

前言: STM32定时器输入捕获简介

STM32的输入捕获可以用于捕获脉宽, 测量时间 . 例如超声波测距模块就是需要用输入捕获功能, 通过测量输入脉冲的高电平脉宽 , 从而计算出测量物体的距离 ;
定时器PWM工作模式上篇博客讲过了, 上篇是输出PWM, 本篇是要输入, 即外面的信号送给单片机的引脚, 然后单片机测量出脉宽 ;

注: 下面根据正点原子的标准库函数教程分析, 并用CubeMX完成配置以及HAL库函数编程


在这里插入图片描述

如图所示 : 以测量高电平脉宽为例, 我们先设置定时器通道为上升沿捕获, 到1的时候触发定时器计数, 然后立刻设置为下降沿捕获, 到2的时候就捕获到下降沿, 再记录输入捕获寄存器的值, 两个时间差就是高电平时长tH;
需要注意的是, 在tH这段高电平时间内, 是由很多个向上计数的脉冲来计数的。在这里面计数可能溢出N多次; 下面是原子的库函数指南pdf里面讲解的图

在这里插入图片描述

  在tH这段高电平里面, 可能有多个向上计数的脉冲, 而那个三角向上计数脉冲也可能溢出多次。就是利用这N多个向上计数的脉冲来计算tH的值的。  ARR的值是我们自己设定的,所以可以知道溢出一次是多长时间, 每溢出一次, 都给溢出次数加一。 溢出次数以及检测高低电平的数据记录在自己设定的一个变量里面
  N*ARR + CCRx2即为CNT计数次数, 从而就可以算出计数时间, 算出高电平时长
  N: 溢出次数  ARR: 溢出一次的时间   在一个tH内,溢出的次数不一定正好是整数, 所以用记录下CCRx2的值, 用以补充, 这样tH的值就更精确了

在这里插入图片描述

这是一个八位的变量,可以将其看做8位寄存器,不同的位储存不同的数据


5.1 操作简介

   通过信号发生器给单片机对应引脚输入一个给定频率和占空比的矩形波信号, 单片机通过输入捕获测量出高电平时长; 通过串口发送至PC端的串口调试助手查看测量的脉宽是否准确


5.2 STM32CubeMX配置初始化+IAR编程

Step1 : Cube配置

  • (1) 新建工程

  •   RCC和SYS配置, 时钟树配置都同前面一样; 还要用串口打印数据, 测试用。
    • RCC和SYS配置

在这里插入图片描述

  • USART1串口1配置(按照串口那一篇配置串口即可–>串口通信 )

在这里插入图片描述
注: 详细解释转至串口通信

  • 时钟树配置

在这里插入图片描述

  • (2)TIM5参数配置

    • 使用TIM5的通道一(TIM5_CH1)接收外部输入的信号。配置如下
      在这里插入图片描述
      注:上一篇介绍了如何计算定时器溢出时间,这里溢出时间为1us  点击查看—>定时器中断及定时器产生PWM
    • 使能TIM5中断(要在中断里面计数高电平脉宽)
    • NVIC设置(同样可以查看上一篇看详细讲解NVIC配置以及中断分组详解)

    在这里插入图片描述

  • (3) 工程配置(Project Manager)

    在这里插入图片描述
    注 : 高级设置默认即可

  • (4) 生成代码(Generate Code)


Step2 : Keil/IAR编程

  • (1)重定向printf函数(重定向之后我们才可以使用printf函数将调试信息打印到串口调试助手) 下面是串口通信那一篇博客写的话,直接搬到这里:
    在学习C语言的时候, 大家肯定都用过printf这个函数, printf可以将指定字符打印到电脑的显示器上;
    但是, 单片机要使用这个就要把他打印的方向改一下, 不是打印在电脑的命令行中, 而是打印到串口里面,传输到串口调试助手. 因此我们需要重定向printf函数;
    重定向后我们要将调试信息打印到USART1中, 需要对printf所依赖的打印函数fputc()重定向 .

    在usart.c里面添加重定向代码
    在这里插入图片描述
    以后这段代码直接抄就好了, copy下来用

    /* USER CODE BEGIN 0 */
    #include "stdio.h"
    
    #ifdef __GNUC__
        #define PUTCHAR_PROTOTYPE int _io_putchar(int ch)
    #else 
        #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
    #endif /* __GNUC__*/
        
    // 重定向C语言中的printf函数 
    PUTCHAR_PROTOTYPE
    {
        HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
        return ch;
    }
    
    /* USER CODE END 0 */
    
  • (2) 查看一下定时器相关代码学习

  • 打开之后跟正点原子的标准库函数写的代码对比一下, 又利于自己理解CubeMX配置的机理, 以后会更得心应手, 慢慢的自己就可以一直标准库函数用HAL库写了。
    在这里插入图片描述
    这张图左边是CubeMX配置后自动生成的代码, 蓝色框框里面就是对应的CubeMX里面的配置; 右侧是原子的标准库例程代码, 可以对比一下, 增强理解

    tips:CSDN只能上传不超过5M的图片, 所以这个图片经过了压缩 , 放大看可以看清晰一点。

  • (3) 编写中断部分函数

    • 因为要在中断中捕获上升沿和下降沿, 所以主要代码写在中断服务函数里面
      下图是计数中断
      在这里插入图片描述
      TIM5CH1_CAPTURE_STA虽然是我们定义的一个变量,但可以把它看做是一个8位的寄存器
      在这里插入图片描述
    • 下图是捕获中断
      在这里插入图片描述
      在HAL_TIM_PeriodElapsedCallback()回调函数中用以处理计数次数和时间;   在HAL_TIM_IC_CaptureCallback()回调函数负责处理捕获到的上升沿和下降沿,
      并随着捕获到上升沿而更改为下降沿捕获,   随着捕获到下降沿而更改定时器为上升沿捕获.

    下面是完整代码:

    /* USER CODE BEGIN 1 */
    /*	bit7 捕获完成标识	bit6 捕获到高电平标识	bit5~0 捕获高电平后定时器溢出的次数 */
    uint8_t		TIM5CH1_CAPTURE_STA = 0;	// 输入捕获状态
    uint32_t	TIM5CH1_CAPTURE_VAL;		// 输入捕获值(TIM2/TIM5是32位的定时器所以这里定义为uint32_t)
    // 中断服务函数里面会自动调用这个回调函数  这个是定时器更新中断中处理的函数
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
      if (htim->Instance == TIM5)	// 判断是定时器5发生中断
      {
        if ((TIM5CH1_CAPTURE_STA & 0x80) == 0) // 还未成功捕获
        {
          if (TIM5CH1_CAPTURE_STA & 0x40)		   // 捕获到高电平
          {
            if ( (TIM5CH1_CAPTURE_STA & 0x3f) == 0x3f )		// 如果高电平太长  做溢出处理
            {
              TIM5CH1_CAPTURE_STA |= 0x80;				// 标记成功捕获了一次
              TIM5CH1_CAPTURE_VAL = 0xffffffff;
            }
            else
            {
              TIM5CH1_CAPTURE_STA++;		// 若没有溢出, 就只让TIM5CH1_CAPTURE_STA自加就ok
            }
          }
        }
      }
    }
    
    // 定时器输入捕获中断处理回调函数,该函数在 HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim) 中会被调用 
    void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
    {
      if ( (TIM5CH1_CAPTURE_STA & 0x80) == 0 )	// 还未成功捕获
      {
        if (TIM5CH1_CAPTURE_STA & 0x40)			// 捕获到一个下降沿
        {
          TIM5CH1_CAPTURE_STA |= 0x80;		// 标记成功捕获到一次高电平脉宽
          TIM5CH1_CAPTURE_VAL = HAL_TIM_ReadCapturedValue(&htim5, TIM_CHANNEL_1);	// 获取当前的捕获值. 即CCRx2
          TIM_RESET_CAPTUREPOLARITY(&htim5, TIM_CHANNEL_1);						// 清除原来的设置
          TIM_SET_CAPTUREPOLARITY(&htim5, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING);	// 配置TIM5通道1上升沿捕获
          
        }
        else
        {
          TIM5CH1_CAPTURE_STA = 0;	// 清空自定义的状态寄存器
          TIM5CH1_CAPTURE_VAL = 0;	// 清空捕获值
          TIM5CH1_CAPTURE_STA |= 0x40;// 标记捕获到了上升沿
          __HAL_TIM_DISABLE(&htim5);	//关闭定时器5
          __HAL_TIM_SET_COUNTER(&htim5,0);
          TIM_RESET_CAPTUREPOLARITY(&htim5,TIM_CHANNEL_1);   //一定要先清除原来的设置!!
          TIM_SET_CAPTUREPOLARITY(&htim5,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING);//定时器5通道1设置为下降沿捕获
          __HAL_TIM_ENABLE(&htim5);//使能定时器5
        }
      }
    }
    
    /* USER CODE END 1 */
    

    tips:每句话都有注释, 不要一看到密密麻麻代码就不看了, 看一下并不是很难理解。 也不要因为看到全是大写字母的函数或者变量而犯怵, 静下心来用两分钟看一看很容易看懂
    在这里插入图片描述

    • 还有一个问题:就是这里为什么用HAL_TIM_PeriodElapsedCallback而不是其他的callback呢? 原因在IRQ_Handler函数里面。
      在这里插入图片描述
  • (4) 主函数程序(main.c)

    • 首先使能定时器中断、同时定义一个变量备用:
      在这里插入图片描述
      /* USER CODE BEGIN 2 */
      HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_1);	// 开启输入捕获中断
      __HAL_TIM_ENABLE_IT(&htim5,TIM_IT_UPDATE);	//使能更新中断
      long long temp = 0;	 	// 定义一个变量用以存储捕获到的时间 long long型是为了防止数据溢出
      /* USER CODE END 2 */
      
    • 在while(1)循环测量数据并打印
      在这里插入图片描述
      while (1)
      {
        /* USER CODE END WHILE */
      
        /* USER CODE BEGIN 3 */
        HAL_Delay(10);
      
        // 信号发生器输入信号 串口打印高电平时长  ms
        if (TIM5CH1_CAPTURE_STA & 0x80)   // 如果捕获完成
        {
            temp = TIM5CH1_CAPTURE_STA & 0x3f;
            temp *= 0xffffffff;				// Total Overflow Time(总的溢出时间)
            temp += TIM5CH1_CAPTURE_VAL;    // Get Total High Level Time(获取总的高电平时长)
            printf("HIGH: %lld ms
      ", temp/1000); // Print Total High Level Time(打印总的高电平时长)
            TIM5CH1_CAPTURE_STA = 0;			    // Clear Capture State , Open The Next Capture(清除捕获状态,打开下一次捕获)
        }
      }
      /* USER CODE END 3 */
      
  • (5) 至此程序就完成了.

    在这里插入图片描述

    在主函数里面, TIM5CH1_CAPTURE_STA & 0x80的意思是判断有没有捕获到高电平 用TIM5CH1_CAPTURE_STA和0x80相与, 从而判断TIM5CH1_CAPTURE_STA的6位是否为1, 进而判断出是否捕获到高电平; 下面的一些涉及到相与的操作也都类似, 把一个变量看做一个寄存器, 把0x80、 0xffffffff等转换为二进制就好判断了, 在演草纸上画一下就很清楚

    在这里插入图片描述

  • (6) 编译下载

上述代码都是之前经过测试的,但是当前由于疫情,没有条件展现结果, 如果有人用了这些代码并测试,有什么问题的话可以下面评论告知,感激不尽。效果展示会在后期补上


待到春暖花开时,愿人间皆安,山河无恙。樱花会如期而至

Author : 李光辉
date : Sun Feb 16 22:50:25 CST 2020
blog ID: Kevin_8_Lee
blog site : https://blog.csdn.net/Kevin_8_Lee

免责声明:文章转载自《第五节:STM32输入捕获(用CubeMX学习STM32)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇关于运维标准化的一些总结微服务架构下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

20个Chrome DevTools调试技巧

译者按: Chrome DevTools很强大,甚至可以替代IDE了! 原文: Art of debugging with Chrome DevTools 译者: Fundebug 为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。 小编推荐:Fundebug专注于JavaScript、微信小程序、微信小游戏,Nod...

时间片轮询-应用程序架构

大致应用程序的架构有三种:1. 简单的前后台顺序执行程序,这类写法是大多数人使用的方法,不需用思考程序的具体架构,直接通过执行顺序编写应用程序即可。 2. 时间片轮询法,此方法是介于顺序执行与操作系统之间的一种方法。 3. 操作系统,此法应该是应用程序编写的最高境界。 下面就分别谈谈这三种方法的利弊和适应范围 1. 前后台顺序执行法 前后台程序一般是指没有...

ASP.NET偷懒大法三 (利用Attribute特性简化多查询条件拼接sql语句的麻烦)

        最近公司在做武汉公交信息化管理系统,做这种管理项目,最让人痛苦的就是表单的添加、修改、查询。添加、修改在我以前的文章中提到过,利用反射机制可以做到基本不写代码来完成。参见《ORM框架实现数据的自动绑定添加修改 <一>》。(不过遗憾的是,目前做的项目中没使用,还是在痛苦的写赋值语句)         上文中只是解决了添加、修改、显...

Ztree 触发onClick事件

一.思路 1、利用 getNodeByParam 等方法找到你需要选中的节点2、利用 selectNode 方法选中节点3、直接调用 callback.click 二.代码 var treeObj = $.fn.zTree.getZTreeObj("divId"); var nodes = treeObj.getNodesByParam("code", "...

WPF中元素拖拽的两个实例

  今天结合之前做过的一些拖拽的例子来对这个方面进行一些总结,这里主要用两个例子来说明在WPF中如何使用拖拽进行操作,元素拖拽是一个常见的操作,第一个拖拽的例子是将ListBox中的子元素拖拽到ListView的某一个节点,从而将该子元素作为当前节点的子节点。第二个例子就是将ListView的某一项拖拽到另外一项上从而使两个子项位置互换,这两个例子的原理类...

Qt中使用定时器(可使用QObject::timerEvent定时执行,QTimer::singleShot可只触发一次)

在Qt中使用定时器有两种方法,一种是使用QObiect类的定时器;一种是使用QTimer类。定时器的精确性依赖于操作系统和硬件,大多数平台支持20ms的精确度 1.QObject类的定时器 QObject是所有Qt对象的基类,它提供了一个基本的定时器。通过QObject::startTimer(),可以把一个一毫秒为单位的时间间隔作为参数来开始定时器,这...