HAL库STM32常用外设教程(六)——定时器 输入捕获


前言

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


一、输入捕获原理及相关驱动

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://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信号的应用示例


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

——《人生》

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值