HAL库STM32定时器输入捕获教程(六)

文章目录

  • 前言
  • 一、输入捕获原理及相关驱动
  • 1.1 输入捕获原理
  • 1.2 输入捕获相关的HAL驱动
  • 二、 输入捕获检测方波占空比
  • 2.1 原理
  • 2.2 STM32CubeMx设置
  • 2.3 程序设计
  • 2.4 示例结果
  • 三、 输入捕获检测PWM频率和占空比
  • 3.1 原理
  • 3.2 STM32CubeMx设置
  • 3.3 程序设计
  • 3.3 示例结果
  • 四、用定时器ETR方式计算PWM脉冲数
  • 4.1 ETR计算脉冲数原理
  • 4.2 STM32CubeMx设置
  • 4.3 程序设计
  • 4.4 示例结果
  • 4.5 问题反思
  • 五、总结

  • 前言

    1、STM32F407ZGT6
    2、STM32CubeMx软件
    3、keil5
    内容简述:
    通篇文章将涉及以下内容,如有错误,欢迎指出:
    定时器有关输入捕获的HAL库驱动程序
    (1)CubeMx配置
    (2)TIM驱动程序
    (3)输入捕获检测方波占空比
    (4) 输入捕获检测PWM频率和占空比
    (5)用定时器ETR方式计算PWM脉冲数


  • 有关于定时器 输出PWM功能不了解的可以看这篇文章 :HAL库STM32常用外设教程(一)—— 定时器 输出PWM
  • 有关于定时器 定时功能+基础定时器特性不了解的可以看这篇文章 :HAL库STM32常用外设教程(四)—— 定时器 基本定时
  • 有关于定时器输出比较+通用定时器特性不了解的可以看这篇文章 :HAL库STM32常用外设教程(五)—— 定时器 输出比较
  • 一、输入捕获原理及相关驱动

    1.1 输入捕获原理

      输入捕获(intput capture)就是检测输入通道输入方波信号的跳变沿,并将发生跳变沿时的计数器的值锁存到CCR。使用输入捕获功能可以检测方波信号周期,从而计算方波信号的频率,也可以检测方波的占空比。其基本原理可以看成下面的几个步骤:

    1、输入捕获通道配置:首先,需要选择一个特定的计时器作为输入捕获的计时源,并将其中的一个或多个通道配置为输入捕获模式。

    2、捕获触发条件设置:根据需要,在输入捕获通道上设置触发条件,例如上升沿、下降沿或两者都有的边缘触发。

    3、 外部信号检测:当外部信号满足触发条件(如上升沿)时,输入捕获通道会记录当前计时器的计数值并生成相应的中断或标志。

    4、 捕获值获取:在中断服务程序或适当的时间点,可以读取输入捕获通道的捕获寄存器,获取记录的捕获值。

    5、计算参数:通过对捕获值进行处理和计算,可以得到所需的参数,如脉冲宽度、频率、周期等。
    下面将通过用输入捕获功能计算按键按下时高电平的占空比、输入捕获计算PWM的周期和频率两个例子讲解定时器输入捕获的用法。

    1.2 输入捕获相关的HAL驱动

      输入捕获相关的HAL函数如表2.1 所示。这里仅列出了相关函数名,简要说明其功能相关函数在stm32f4xx_hal_tim.h中。
                     表2.1输入捕获相关HAL库函数

    函数名 功能描述
    HAL_TIM_IC_Init() 输入捕获初始化,需先执行HAL_TIM_Base_Init()进行定时器初始化
    HAL_TIM_IC_ConfigChannel() 输入通道配置
    HAL_TIM_IC_Start() 启动输入捕获,需要先执行HAL_TIM_Base_start()启动定时器
    HAL_TIM_IC_Stop() 停止输入捕获
    HAL_TIM_IC_Start_IT() 以中断方式启动输入捕获,需要先执行HAL_TIM_Base_start_IT()启动定时器
    HAL_TIM_IC_Stop_IT() 停止输入捕获
    HAL_TIM_IC_GetState() 返回定时器状态,与HAL_TIM_Base_GetState()功能相同
    __HAL_TIM_SET_CAPTUREPOLARITY() 设置捕获输入极性,上跳沿、下降沿或双边捕获
    __HAL_TIM_SET_COMPARE() 设置比较寄存器CCR的值
    __HAL_TIM_GET_COMPARE() 读取比较寄存器CCR的值
    HAL_TIM_IC_CaptureCallback() 产生捕获事件时的回调函数

    二、 输入捕获检测方波占空比

    2.1 原理

      如图2.1所示,如果两个上跳沿的捕获发生在同一个计数周期内,两个计数值分别为CCR1和CCR2,则方波的周期为CCR2-CCR1个计数周期,根据定时器的周期就可以计算出方波周期和频率。如果方波周期超过定时器的计数周期,或两次输入捕获发生在相邻的两个定时周期内,如图2.2中CCR2和CCR3,则只需将计数器的计数周期和UEV事件发生次数考虑进去即可,根据CCR2和CCR3计算的脉冲周期应该是 ARR – CCR1 + CCR2。
      输入捕获还可以对输入设置滤波,滤波系数0 ~ 15,用于输入抖动时的处理。输入捕获还可以设置与分频系数 N,数值N的取值为1、2、4或8,表示发生了N个时间才执行一次的捕获

                       图2.1 捕获PWM高电平示意图(1)
                       图2.2 捕获PWM高电平示意图(2)

      本次示例通过按键去提供一次高电平,测试按键按下时的高电平时间,所以需要将TIM输入捕获的引脚接到按键的引脚上,可以在CubeMx里面将按键引脚配置成定时器输入捕获模式(如图2.3)
    通过观察开发板原理图,如图2.3 到 图2.5所示,可以发现KEY_UP按键的引脚可以实现配成TIM2_CH1的输入捕获通道。

                       图2.3 CubeMx里按键引脚配置


                       图2.4 按键原理图(1)

                       图2.5 按键原理图(2)

    2.2 STM32CubeMx设置

    1、TIM配置
      如图所示,按照图2.6 中的步骤将TIM2的通道1配置输入捕获模式,还需要使能TIM2中断(在中断里面计数高电平脉冲)。
                         图2.6 CubeMx里TIIM2输入捕获设置


                         图2.7 使能TIM2中断(1)


                         图2.8 使能TIM2中断(2)

      因为需要通过串口将高电平时间计算出来,所以实验也需要配置好串口,本次用到的是串口1

                         图2.9 使能TIM2中断(2)
    配置完成后生成工程。

    2.3 程序设计

      STM32CubeMx配置结束后,生成工程。先进行初步分析STM32CubeMx生成的工程,以便更好的理解STM32CubeMx里面的配置。

                         图2.10 Counter Settings
    如图所示,逐行解释上图片中标记的代码

  • htim2.Instance = TIM2;
      将定时器设置为TIM2。
  • htim2.Init.Prescaler = 84-1;
       设置预分频器的值为83(实际值减去1)。预分频器用于将输入时钟频率分频,从而得到更低的计数频率。在这里,输入时钟频率将被除以84,获得的计数频率为84Mhz /(84-1 +1) = 1MHz。
  • htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
       将计数模式设置为向上计数模式。在向上计数模式下,计数器从0递增到自动重载值。
  • htim2.Init.Period = 0xFFFFFFFF;
      设置自动重载寄存器的值为0xFFFFFFFF(32位最大值)。这意味着计数器将从0递增到0xFFFFFFFF,然后重新从0开始循环。
  • htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
      设置时钟分频系数为不分频(TIM_CLOCKDIVISION_DIV1)。时钟分频用于进一步分频计数器的时钟频率。
  • htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
       启用自动重载预装载功能。当该功能启用时,新的自动重载值在下一个更新事件时生效,以避免在计数器工作时意外更改自动重载值。

  •                      图2.10 Counter Settings设置

  • sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
       这段代码的作用是将定时器的时钟源设置为内部时钟。sClockSourceConfig是一个结构体,其中的ClockSource成员用于配置定时器的时钟源。通过将ClockSource成员设置为TIM_CLOCKSOURCE_INTERNAL,代码指定定时器使用内部时钟作为计数时钟源。

  •                图2.11 TRGO Parameters和Input Capture Channel设置

  • sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
       配置定时器的主输出触发源为 TIM_TRGO_RESET,表示在每次定时器重置(复位)时触发主输出触发事件。具体说,当定时器的计数值达到最大值并发生溢出时,定时器将自动复位到初始值。此时,如果配置了主输出触发源为 TIM_TRGO_RESET,就会产生一个触发事件,可以用来同步其他外设或执行相关的操作

  • sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
       禁用主从模式,即该定时器不会作为其他定时器的主定时器。

  • sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
      设置输入捕获通道的触发极性为上升沿触发。

  • sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
      配置输入捕获通道的输入源选择为直接触发模式。捕获选择器设置为 TIM_ICSELECTION_DIRECTTI 时,定时器会直接读取输入端的电平变化,并进行相应的计数和捕获操作。这通常适用于需要测量输入信号的上升沿和下降沿的情况。
      当将输入捕获选择器设置为 TIM_ICSELECTION_INDIRECTTI 时,定时器会先通过一个互补触发器(Complementary Trigger)来产生一个间接触发信号。然后,根据该触发信号的边沿变化来触发输入捕获操作。这种设置通常适用于需要测量输入信号的互补边沿(例如正边沿和负边沿)或需要使用输入信号的互补版本进行计数的情况。

  • sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
      设置输入捕获通道的输入分频器预分频系数为1,TIM_ICPSC_DIV1指定输入信号的频率不进行预分频,直接用于输入捕获。

  • sConfigIC.ICFilter = 0;
      将输入捕获通道的滤波器配置为无滤波器。ICFilter参数表示滤波器的设置值,0表示禁用滤波器,即不对输入信号应用任何滤波。通过将ICFilter设置为0,可以确保输入捕获模式下的输入信号不受滤波器的影响,即直接获取输入信号的原始状态。这适用于需要实时响应输入信号变化或对输入信号精确测量的情况。

  •   了解了上面的 tim.c 里面的基础配置后,开始在生成的工程上编写用户代码 实现输入捕获功能。
    (1)首先,需要对实现“串口重定向”,printf可以将指定字符打印到电脑的显示器上,但是要想使用printf在单片机中通过串口打印出来,就需要重定向到串口配置上。
    注:如果用到的是串口2,将“串口重定向中的”USART1换成USART2即可。

    /* 串口重定向 */
    int fputc(int ch,FILE *f)
    {
    	while(!(USART1->SR &(1<<7)));
    	USART1->DR = ch;
    	return ch;
    }	
    

    (2)进行相关的变量定义

    uint32_t tim_value=0; /* 储存计数器的记录值 */ 
    uint32_t tim_over=0;  /* 计数器溢出的个数 */
    uint32_t time;   	  /* 高电平持续时间 */	
    
    /* 捕获状态 */
    typedef enum{
    		idle	= 0,  /* 空闲 */
    		runing, 	  /* 运行*/
    		end			  /* 结束*/
    }IC_STATE;
    
    IC_STATE ic_state;	  /* 定义结构体变量 */
    

    (3)启动定时器TIM2的中断模式,并开始输入捕获功能。

    	HAL_TIM_Base_Start_IT(&htim2);																					/* 启动TIM2定时器的中断模式  */
    	HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);															/* 以中断方式开启TIM2的输入捕获模式  */
    

    (4) HAL_TIM_PeriodElapsedCallback() 函数是定时器计数溢出回调函数。当定时器的计数达到最大值并发生溢出时,该回调函数会被调用。在这段代码中,首先判断定时器是否为TIM2,然后检查状态是否为正在运行状态(ic_state == running)。如果满足条件,则将定时器溢出值(tim_over)自增一次。
    HAL_TIM_IC_CaptureCallback() 函数是定时器输入捕获回调函数。它在定时器TIM2的输入捕获事件发生时被调用。在这段代码中,同样首先判断定时器是否为TIM2。然后根据当前状态判断执行不同的操作:

  • 如果状态为idle(空闲),则表示开始进行输入捕获。将状态设置为running(正在运行),计数值清零,将通道1的捕获极性设置为下降沿捕获,并将定时器的计数值也设置为0。
  • 如果状态不为idle,表示之前已经开始了输入捕获过程,此时需要停止输入捕获功能,将状态设置为end(结束),获取此时的计数值,然后将通道1的捕获极性设置为上升沿捕获。
  • void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
    	if(htim == &htim2){								    /* 判断是否为定时器TIM2 */
    		 if(ic_state == runing){					    /* 判断是否为是 正在运行状态 */
    				tim_over ++;					        /* 定时器溢出值计数 */
    		 }
    	}
    }
    	
    void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
    {
    	if(htim == &htim2){							         /* 判断是否为定时器TIM2 */
    		 if(ic_state == idle){							 /* 判断状态是不是 空闲 */
    				ic_state = runing;						 /* 将状态设置为 正在运行 */
    				tim_value = 0;							 /* 计数值清零 */
    				__HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_FALLING); /* 将TIM2的通道1捕获极性设置为下降沿捕获 */
    			 __HAL_TIM_SET_COUNTER(&htim2,0);		     /* 将TIM2的计数值设置为0 */
    		 }else{								             /* 此时不是空闲状态 */
    				HAL_TIM_IC_Stop_IT(&htim2,TIM_CHANNEL_1);/* 停止定时器2输入捕获功能 */
    				ic_state = end;						     /* 将状态设置为 结束 */
    			  tim_value = HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_1);	 /* 获取此时计数值 */
    		 	 __HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING);/* 将TIM2的通道1捕获极性设置为上升沿捕获 */
    		 }
    	}
    }
    

    2.4 示例结果

      接上串口后观察打印结果,如图2.12所示,其显示的高电平持续时间和图2.13中示波器测出来的相同。
    请添加图片描述
                         图2.12 通过串口打印出的高电平时间

    请添加图片描述
                         图2.13 通过示波器捕获的按键高电平时间

    三、 输入捕获检测PWM频率和占空比

    3.1 原理

      如图3.1所示,想要测出PWM的周期和频率,必须知道PWM的占空比和周期(周期的倒数 = 频率),从这个角度去考虑,要进行三次捕获才能实现要求。如果三个上跳沿的捕获发生在同一个计数周期内,如图3.1,发生三次捕获的计数值分别为CCR0,CCR1和CCR2,则方波的周期为CCR2-CCR1个计数周期,方波的高电平周期是CCR2-CCR1个计数周期,根据定时器的周期就可以计算出方波占空比和频率。如果方波周期超过定时器的计数周期,或两次输入捕获发生在相邻的两个定时周期内,如图3.2,发生3次捕获的计数值是CC0、CCR1和CCR2,则只需将计数器的计数周期和UEV事件发生次数N考虑进去即可,计算的脉冲周期应该是 ARR * N + CCR2 – CCR1。
    注:在程序中为了更方便的进行计数和计算,在发生第一次上升沿捕获时,通过__HAL_TIM_SET_COUNTER()函数将定时器的值设置为0,相当于CCR0 = 0,这点在理解程序时需要注意。


                      图3.1 定时器输入捕获测量PWM(1)


                       图3.2 定时器输入捕获测量PWM(2)

    3.2 STM32CubeMx设置

      因为此处需要产生一个PWM,此处用TIM2产生一个频率为1K的PWM方波,如何产生PWM请参考这篇文章: HAL库STM32常用外设教程(一)—— 定时器 输出PWM

      将定时器TIM3的通道1配置成输入捕获通道,如图3.3。

                       图3.3 定时器输入捕获配置

      使能定时器TIM3中断,如图3.4和3.5。


                       图3.4 定时器中断使能(1)


                     图3.5 定时器中断使能(2)

    3.3 程序设计

    (1)参考 2.3节 先进行 串口重定向,完成后再进行下面相关变量的定义。

    uint16_t ccr1_cnt = 0;                   /* 发生第一次下升沿捕获时CCR1的值 */
    uint16_t ccr2_cnt = 0;					 /* 发生第二次上升沿捕获时CCR1的值 */
    uint16_t Period_cnt = 0;				 /* 发生计数器溢出事件的次数(过渡用) */
    uint16_t Period_cnt1 =0;				 /* 发生计数器溢出事件的次数--计数1 */
    uint16_t Period_cnt2 = 0;				 /* 发生计数器溢出事件的次数--计数2 */
    uint16_t ic_flag = 0;					 /* 输入捕获标志 */
    uint16_t end_flag = 0;					 /* 捕获结束标志 */
    float frequency = 0;				     /* 频率 */
    float duty_cycle = 0;				     /* 占空比 */
    

    (2)
    ① 启动定时器TIM2通道1的PWM输出功能以及定时器TIM3的输入捕获功能。

    	HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);        /* 启动定时器2通道1的PWM输出功能 */     
    	HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);		/* 启动定时器3的输入捕获功能 */  
    

    ② 这段代码的主要作用是获取定时器输入捕获的PWM信号的频率和占空比,并将其输出到串口进行显示。
      首先,通过 HAL_Delay(500) 函数进行延时,等待一段时间(这里是500毫秒)。然后,通过判断 end_flag 是否为1来确定是否进行了输入捕获,即判断是否完成了三次输入捕获过程、如果 end_flag 为1,表示完成了三次输入捕获,可以进行频率和占空比的计算。
      其中,Period_cnt1表示定时器TIM3第一次捕获到下降沿时溢出次数, ccr1_cnt 表示第一次捕获到下降沿时的CCR寄存器值。 Period_cnt2 表示定时器TIM3第二次捕获到上升沿时溢出次数, ccr2_cnt 表示第二次捕获到上升沿时的CCR寄存器值。

    计算占空比的公式为:

    (Period_cnt1 * 65536 + ccr1_cnt + 1) / (Period_cnt2 * 65536 + ccr2_cnt + 1) * 100

    (Period_cnt1 * 65536 + ccr1_cnt + 1) : 高电平时间
    (Period_cnt2 * 65536 + ccr2_cnt + 1): 一个周期时间
    注:
    Ⅰ、65535是CubeMx里面设置的自动重装载(ARR)的值 ;
    Ⅱ、涉及到的 “+1” 是因为计数时时从0开始计数的。

    计算频率的公式为:

    1000000 / (Period_cnt2 * 65536 + ccr2_cnt + 1)

      其中1000000 = 1MHz是因为总线时钟频率为84MHz,预分频系数(PSC)为84 – 1,故得出此时TIM3的时钟频率为

    84MHz /(84-1 +1)=84000 000 /(84-1 +1) = 1000 000

    最后,通过printf语句打印输出计算得到的频率和占空比。完成打印后,将end_flag置为0,准备进行下一次输入捕获。

    注:在printf函数的格式字符串中,%%表示一个百分号字符

    		HAL_Delay(500);																										 /* 500ms打印一次 */
    		if(end_flag){																											 /* 判断是否结束 */
    			duty_cycle=(float)(Period_cnt1 * 65536 + ccr1_cnt + 1) * 100 /(Period_cnt2 * 65536 + ccr2_cnt + 1);  /* 计算占空比 */
      		frequency=1000000 / (float)(Period_cnt2 * 65536 + ccr2_cnt + 1); /* 计算频率 */
    			printf("\r\n 频率 = %.2f Hz,占空比 = %.2f %%",frequency,duty_cycle);
    			end_flag = 0;																										 /* 将捕获结束标志置0(准备下一次捕获) */
    		}
    

    (3)
    HAL_TIM_PeriodElapsedCallback() 函数是定时器计数溢出回调函数。当定时器的计数达到最大值并发生溢出时,该回调函数会被调用。在这段代码中,Period_cnt变量用于记录定时器计数溢出的次数。每次回调函数被调用时,Period_cnt会自增一次,以表示又经过了一段时间。
    HAL_TIM_IC_CaptureCallback() 函数是定时器输入捕获回调函数。它在定时器的输入捕获事件发生时被调用。在这段代码中,主要针对定时器TIM3进行处理。根据ic_flag的不同值,可以确定当前处于输入捕获的哪个阶段。

  • 阶段一:第一次捕获到上升沿时,将相关参数清零,并将输入捕获设置为等待第二个阶段。
  • 阶段二:第一次捕获到下降沿时,获取存放在CCR寄存器的捕获值(CCR1),并记录计时器溢出次数1。然后将输入捕获设置为等待第三个阶段。
  • 阶段三:第二次捕获到上升沿时,获取存放在CCR寄存器的捕获值(CCR2),并记录计时器溢出次数2。完成两次输入捕获后,将输入捕获设置为等待第一个阶段,并将end_flag置为1,表示完成一次PWM的输入捕获。
  • void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){    /* 定时器计数溢出回调函数 */
    		Period_cnt ++;								            /* 定时器计数溢出时间次数 */
    }
    void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){	    /* 定时器输入捕获回调函数 */
    		if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1){		    /* 判断是不是定时器TIM1 */
    			if(end_flag == 0){							        /* 判断结束标志是不是0,在打印期间不进行捕获,防止打印时相关数值改变 */
    				switch(ic_flag){							    /* 判断此时处于捕获的第几个阶段 */
    					case 0:									    /* 阶段一:第一次捕获到上升沿 */
    					{
    						__HAL_TIM_SET_COUNTER(&htim3,0);	    /* 将定时器3计数设置为0 */
    						ccr1_cnt = 0;							/* 将相关参数清0 */
    						ccr2_cnt =0;
    						Period_cnt = 0;
    						Period_cnt1 = 0;
    						Period_cnt2 = 0;
    						__HAL_TIM_SET_CAPTUREPOLARITY(&htim3,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_FALLING);/* 设置成下降沿捕获 */
    						ic_flag = 1;							/* 捕获设置为等待第二个阶段 */
    						break;
    					}
    						case 1:									/* 阶段二:第一次捕获到下降沿 */
    					{
    						ccr1_cnt = __HAL_TIM_GET_COMPARE(&htim3,TIM_CHANNEL_1);/* 获取存放在CCR寄存器的值(捕获值CCR1) */
    						Period_cnt1 = 	Period_cnt;				/* 获取计时器溢出次数1 */
    						__HAL_TIM_SET_CAPTUREPOLARITY(&htim3,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING); /* 设置成上升沿捕获 */
    						ic_flag = 2;							/* 捕获设置等待为第三个阶段 */
    						break;
    					}
    					case 2:										/* 阶段三:第二次捕获到上升沿 */
    					{
    						ccr2_cnt = __HAL_TIM_GET_COMPARE(&htim3,TIM_CHANNEL_1);/* 取存放在CCR寄存器的值(捕获值CCR2) */
    						Period_cnt2 = Period_cnt;				/* 获取计时器溢出次数2 */
    						ic_flag = 0;							/* 捕获设置等待为第一个阶段 */
    						end_flag = 1;							/* 完成一次捕获,将标志置1 */
    						break;
    					}			
    				}
    			}
    		}
    }
    

    3.3 示例结果

      接上串口后观察打印结果,如图3.7和图3.8 所示,其显示的PWM频率域PWM占空比和图2.13中示波器测出来的相同。

    请添加图片描述
                     图3.6 接线


                       图3.7 串口结果显示

    请添加图片描述

                       图3.8 示波器显示

    四、用定时器ETR方式计算PWM脉冲数

    4.1 ETR计算脉冲数原理

       通过示例2能够计算PWM的占空比和频率,如果单纯的计算PWM数,就不得不提到定时器的另一个模式:ETR(External Trigger)模式。在这种模式下,定时器的计数器会在PWM信号发生时启动计数。计数器会开始累积时钟脉冲的数量,这个数量与PWM信号的脉冲数成正比。

                         图4.1 定时器ETR模式框图

    4.2 STM32CubeMx设置

    (1)因为此处需要产生一个PWM,此处用TIM2产生一个频率为1K的PWM方波,如何产生PWM请参考这篇文章:HAL库STM32常用外设教程(一)—— 定时器 输出PWM ,TIM2的配置如图4.2所示。

                         图4.2 定时器TIM2 STM32CubeMx配置

    (2)TIM3设置成ETR模式,如图4.3所示。然后使能TIM3中断,如图4.4所示


                         图4.3 定时器TIM3 STM32CubeMx配置


                         图4.4 使能定时器TIM3 中断

    (2)通过TIM6设置成1s的计时,如图4.6所示,之所以设置成1s进行计时是因为PWM频率的定义为:
      在单位时间内,PWM信号中的脉冲从起始到结束再到下一个脉冲的周期性重复次数。PWM频率通常以赫兹(Hz)为单位
      通俗来讲,1s内产生 x 个脉冲,其频率就为 x 赫兹。
    但是有一点需要注意,ETR模式是用来测量脉冲个数的,本次示例通过定时1s去测量ETR的数值(脉冲的个数)来计算脉冲的频率,此方法是不严谨的,因为脉冲的频率可能是不断变化的,举例来说:
      在图4.5中,一定时间内ETR测到的PWM1和PWM2的脉冲个数都是3个,如果按照本次示例中的测量方法得出的结论是PWM1和PWM2的频率是一样的(一定时间内脉冲个数是一样的),所以这种方式只能计算频率不变的脉冲频率。

                         图4.5 两种PWM


                         图4.6 定时器TIM6 STM32CubeMx配置

      然后使能定时器6中断,如图4.7所示。需要在TIM6的溢出中断里面进行计数,在“NVIC”里再次检测中断TIM3和TIM6的中断是否开启。如图4.8所示。

                         图4.7 使能定时器TIM6 中断


                         图4.8 STM32Cube"NVIC"设置

    4.3 程序设计

    (1)参考 2.3节 先进行 串口重定向,完成后再进行下面相关变量的定义。

    uint32_t count_over = 0; 	/* 定时器计数溢出次数 */
    uint32_t count_cnt = 0;		/* 定时器计数值 */
    uint32_t count = 0;				/* 1s 内脉冲总数 */
    
    uint8_t printf_flag = 0;
    

    (2)启动TIM2定时器的PWM输出,以中断方式启动TIM3定时器和TIM6定时器。
    ![在这里插入图片描述](https://i3.wp.com/img-blog.csdnimg.cn/direct/03caa9092ff845fa866b6cf8250423d9.png

    	HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);  /* 启动定时器TIM2的PWM输出 */
    	HAL_TIM_Base_Start_IT(&htim3);						/* 以中断方式启动定时器TIM3 */
    	HAL_TIM_Base_Start_IT(&htim6);						/* 以中断方式启动定时器TIM6 */
    

    (3)在定时器TIM3的中断服务例程里面进行溢出计数,在定时器6的中断服务例程里面通过函数 __HAL_TIM_GET_COUNTER()进行读出此时的CNT值,将打印标志置1,此时主循环检测到该标志置1后就可以进行打印,并将溢出值清0,定时器3的计数值清零,为下一秒计算脉冲频率做准备。

    void TIM3_IRQHandler(void)
    {
      /* USER CODE BEGIN TIM3_IRQn 0 */
    
      /* USER CODE END TIM3_IRQn 0 */
      HAL_TIM_IRQHandler(&htim3);
        /* USER CODE BEGIN TIM3_IRQn 1 */
    	count_over ++; 
      /* USER CODE END TIM3_IRQn 1 */
    }
    
    void TIM6_DAC_IRQHandler(void)
    {
      /* USER CODE BEGIN TIM6_DAC_IRQn 0 */
    	
    	count_cnt =  __HAL_TIM_GET_COUNTER(&htim3);   /* 获取此时的CNT值 */
    	printf_flag = 1;							  /* 将打印标志置1 */
    	count_over = 0;								  /* 溢出计数清0 */
    	__HAL_TIM_SET_COUNTER(&htim3,0);			/* 将定时器3的计数值清0 */
    	
      /* USER CODE END TIM6_DAC_IRQn 0 */
      HAL_TIM_IRQHandler(&htim6);
      /* USER CODE BEGIN TIM6_DAC_IRQn 1 */
    
      /* USER CODE END TIM6_DAC_IRQn 1 */
    }
    

    (4)在主循环里面通过判断 printf_flag标志将PWM频率打印出来,之所以放到主循环里面,是因为中断里面遵循“快进快出”的原则,在中断里面打印会占用过多的时间。

    count = count_over * 65535 + count_cnt;

      上述公式是计算的PWM的脉冲数,因为本次示例中自动重装载值(ARR)的数值设置的是65535,所以每溢出一次计数65535个,也是公式中count_over * 65535的由来。

    		if(printf_flag){								    /* 判断中断标志是否被置1 */
    			 count = count_over * 65535 + count_cnt;		/* 计算1s内产生的脉冲数,即脉冲频率 */
    			 printf("频率为 %d Hz \n\r",count);
    			 printf_flag = 0;								/* 将中断标志清0,为下一次打印做准备 */
    		}	
    

    4.4 示例结果

      通过杜邦线将PA0引脚和PD2引脚相连,就可以在串口工具里面看到输出的频率,本次示例为验证输出的是否正确,在PA0引脚上又接了一个示波器,观察输出的波形,从图4.10和4.11可以看出,产生PWM的频率是1KHz,捕获到的频率也是1KHz。
    请添加图片描述
                        图4.9 接线


                        图4.10 串口输出频率

    请添加图片描述
                        图4.11 示波器显示

    4.5 问题反思

    (1)在做该实验时,搞错了一个函数,将 __HAL_TIM_GET_COUNTER()
    函数错用成了 __HAL_TIM_GET_COMPARE()函数,下面补充一下这两个函数的作用

    __HAL_TIM_GET_COUNTER()函数

  • 作用:用于获取定时器的当前计数器值。
  • 示例:
  • uint32_t counterValue = __HAL_TIM_GET_COUNTER(&htim);
    
  • 这个宏返回当前定时器的计数器寄存器(CNT)的值。在定时器工作时,计数器值不断增加,通过这个宏可以读取当前的计数值,用于查看定时器整体的计数状态。
  • __HAL_TIM_GET_COUNTER()函数

  • 作用:用于获取定时器的比较寄存器值。。
  • 示例:
  • uint32_t compareValue = __HAL_TIM_GET_COMPARE(&htim, TIM_CHANNEL_1);
    
  • 这个宏返回指定通道(channel)的比较寄存器(Capture/Compare Register,通常缩写为CCR的值。比较寄存器通常用于与计数器值进行比较,从而确定何时触发中断或改变输出状态,用于查看与特定通道相关联的比较状态。这通常与定时器的PWM生成、捕获比较等操作有关。
  • (2)在进行 “ printf ” 打印时,第一次‘’将其放在了TIM6定时器的中断服务例程里面,结果发现打印出来的频率都是996Hz,然后考虑到了中断里面“快进快出原则 ,通过置标志的方式将打印内容放在了while循环里面,打印出的结果是1000Hz。在单片机中,这一原则有助于确保中断服务程序的及时响应和高效执行,该原则一般包括两个方面:

    尽量减小中断服务程序的执行时间:

  • 中断服务程序(ISR – Interrupt Service Routine)应该尽量简洁而高效。在中断服务程序中,应避免长时间的计算、复杂的数据处理等操作。
  • 长时间执行的中断服务程序会导致其他中断被阻塞,这可能影响系统的实时性和响应性。
  • 应该专注于在中断服务程序中完成必要的最小工作量,将复杂和耗时的任务移到主循环中或其他地方处理。
  • 尽量减小中断响应时间:

  • 中断响应时间是指从中断发生到中断服务程序开始执行的时间。较短的中断响应时间有助于提高系统的实时性。
  • 在设计中,可以通过适当配置中断控制器、合理选择中断优先级、以及优化硬件和软件的交互,来缩短中断响应时间。
  • 另外,及时清除中断标志位也是减小中断响应时间的一部分,确保中断服务程序能够迅速响应新的中断。
  • 五、总结

      上述主要讲述了定时器输入捕获的原理及其三个应用示例,包括每个示例的HAL库配置、程序编写以及测试结果。在学习输入捕获时,应该多去琢磨其捕获的原理,可以通过画图去研究其过程,例如图3.1和图3.2,明白该在哪一个跳变沿进行捕获等。


    参考书籍和文章:
    1、《STM32Cube高效开发教程(基础篇)》王维波
    2、《STM32F4xx中文参考手册》
    3、《STM32F407 探索者开发指南》
    4、【STM32】标准库与HAL库对照学习教程十–输入捕获实验
    5、[018] [STM32] 定时器 基本定时/输出比较/输入捕获功能详解与HAL库编程
    6、STM32频率测量
    7、第五节:STM32输入捕获(用CubeMx学习STM32)
    8、cube配置定时器ETR2模式测频实验
    9、STM32的ETR引脚计数功能
    10、基于STM32定时器ETR信号的应用示例


      生活总是这样,不能叫人处处都满意。但我们还要热情的活下去。人活一生,值得爱的东西很多,不要因为一个不满意,就灰心。
                        ——《人生》

    物联沃分享整理
    物联沃-IOTWORD物联网 » HAL库STM32定时器输入捕获教程(六)

    发表回复