STM32-定时器输入捕获实验(捕获PWM方波的频率和占空比)

STM32-定时器输入捕获实验(捕获PWM方波的频率和占空比)

一、输入捕获模式

在输入捕获模式下,当检测到ICx信号上相应的边沿后,计数器的当前值被锁存到捕获/比较寄存器(TIMx_CCRx)中。当捕获事件发生时,相应的CCxIF标志(TIMx_SR寄存器)被置’1’,如果使能了中断或者DMA操作,则将产生中断或者DMA操作。如果捕获事件发生时CCxIF标志已经为高,那么重复捕获标志CCxOF(TIMx_SR寄存器)被置’1’。写CCxIF=0可清除CCxIF,或读取存储在TIMx_CCRx寄存器中的捕获数据也可清除CCxIF。写CCxOF=0可清除CCxOF。

二、如何捕获PWM方波的频率?

将捕获的模式设置为下降沿/上升沿捕获,当第一次捕获到上升沿/下降沿,则会触发中断,在中断中,把当前的计数值记录下来Value1;第二次捕获到上升沿/下降沿,再一次触发中断,在中断中我们把当前的计数值记录下来Value1。所以第二次捕获和第一次捕获之间的时间就是PWM方波的周期了。假设定时器的时钟被设置为72MHz,那么得到的PWM方波的频率应该为72MHz/(Value2 - Value1)。

三、如何捕获PWM方波的频率和占空比?

如果我们要同时捕获方波的频率和占空比的话,那么我们需要知道一个周期中高电平的时间和一个周期中低电平的时间。
首先把捕获的模式设置为上升沿捕获(当然设置为下降沿也是可以的),发生第一次捕获到上升沿的中断,以此中断时刻作为一个起点,得到的计数值为Value1,此时将捕获模式设置为下降沿捕获,在发生第二次中断的时候,捕获到了下降沿,得到的计数值为Value2,那么Value2和Value1之间的差值,就是一个周期中,高电平的时间,然后我们在中断中又将捕获的方式设置为上升沿捕获,那么在第三次产生中断的时候,得到的计数值为Value3,那么Value3和Value2之间的差值就是一个周期中低电平的时间。所以到此为止,我们知道了一个周期中高电平和低电平的时间,那么这个PWM方波的频率和占空比就得到了。

四、实例:捕获PWM方波的频率和占空比

代码如下,根据官方固件库提供的一个Example修改而来STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Examples\TIM\InputCapture。使用的是定时器3的通道2。
配置步骤:

  1. 配置时钟
  2. 配置中断优先级
  3. 配置GPIO口
  4. 初始化输入捕获模式

TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; // 选择通道2
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; // 上升沿触发
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; // 选择对应关系Direct
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; // 不进行分频
TIM_ICInitStructure.TIM_ICFilter = 0x0; // 不使用滤波器
TIM_ICInit(TIM3, &TIM_ICInitStructure);

  1. 打开定时器以及定时器的捕获比较中断和更新中断,至于为什么要打开更新中断,在后面的总结3中会解释。

然后在我的配置中,遇到了一些问题,在这里进行一些总结。

  1. 修改捕获的触发方式使用的函数是:
    TIM_OC2PolarityConfig(TIM3,TIM_ICPolarity_Rising);
    这个函数的名称具有一定的迷惑性,OC2明明意思明明就是输出比较,不过这个函数也适用于输入捕获,因为他们操作的实际上是同一个地方,见下图,一看就明白在这里插入图片描述
  2. TIM_GetCounter与TIM_GetCapture1之间的区别,TIM_GetCapture1得到的是CCR1寄存器的值,该值为:当发生捕获事件时,立马把当前计数器的值保存到CCR1寄存器中,是硬件级别的保存。而TIM_GetCounter是发生中断后,调用函数的时刻的计数器的值,相比TIM_GetCapture1来说,不如TIM_GetCapture1准确。那在程序中为什么用的是TIM_GetCounter呢,其实是偷了一个懒,因为第一次发生捕获事件,就通过TIM_SetCounter将当前的计数值设置为0了,所以在第二次第三次发生捕获事件的时候得到的就是当前时刻的事件,就省去了判断并进行时间的加减,不过按严谨的话,还是应该用TIM_GetCapture1函数,因为这样的更加准确。
  3. 数据溢出的问题,也是我找了很久才找出来的,感觉还是非常值得。
    首先我要进行捕获的方波是1000Hz,但是捕获得到的数据始终是不对的,那么我们来算一算对于1000Hz的方波,一个周期就是1ms,那么对于72MHz,1ms的时间计数会是多少呢,就应该是72000,而计数器CNT是16位的,最大为65535,那肯定会发生溢出了啦,那么我们就找到了问题的根源。所以我的解决方法是,将定时器的更新中断打开,设置一个count,也就是每次数据溢出会触发更新中断,让count++,然后在将捕获得到的Value加上65536*count,理论上就是正确的值了。但是呢,在调试结果的看来,居然没有效果,原因是我还没有改变量的类型,原来的变量类型的uint16_t,是16位的,最大值照样是65535,所以解决方法是将变量的类型定义为uint32_t,最后就调试结果就正确了。

uint32_t IC3ReadValue1 = 0, IC3ReadValue2 = 0;
uint32_t CaptureNumber = 0;
uint32_t Capture = 0;
uint32_t TIM3Freq = 0;
uint32_t TIM3Duty = 0;
uint32_t TIM3_Update_Cnt = 0;
/**
  * @brief  Configures the different system clocks.
  * @param  None
  * @retval None
  */
void InputCapture_RCC_Configuration(void)
{
    
    
  /* TIM3 clock enable */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

  /* GPIOA clock enable */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
}
/**
  * @brief  Configure the GPIOA Pins.
  * @param  None
  * @retval None
  */
void InputCapture_GPIO_Configuration(void)
{
    
    
  GPIO_InitTypeDef GPIO_InitStructure;

  /* TIM3 channel 2 pin (PA.07) configuration */
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_7;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_Init(GPIOA, &GPIO_InitStructure);
}

/**
  * @brief  Configure the nested vectored interrupt controller.
  * @param  None
  * @retval None
  */
void InputCapture_NVIC_Configuration(void)
{
    
    
  NVIC_InitTypeDef NVIC_InitStructure;

  /* Enable the TIM3 global Interrupt */
  NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
}

void InputCapture_Init(void)
{
    
    
	TIM_ICInitTypeDef  TIM_ICInitStructure;
	
	/* System Clocks Configuration */
  InputCapture_RCC_Configuration();

  /* NVIC configuration */
  InputCapture_NVIC_Configuration();

  /* Configure the GPIO ports */
  InputCapture_GPIO_Configuration();

  /* TIM3 configuration: Input Capture mode ---------------------
     The external signal is connected to TIM3 CH2 pin (PA.07)  
     The Rising edge is used as active edge,
     The TIM3 CCR2 is used to compute the frequency value 
  ------------------------------------------------------------ */

  TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
  TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
  TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
  TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
  TIM_ICInitStructure.TIM_ICFilter = 0x0;

  TIM_ICInit(TIM3, &TIM_ICInitStructure);
  
  /* TIM enable counter */
  TIM_Cmd(TIM3, ENABLE);

  /* Enable the CC2 Interrupt Request */
  TIM_ITConfig(TIM3, TIM_IT_CC2 | TIM_IT_Update, ENABLE);
}
/**
  * @brief  This function handles TIM3 global interrupt request.
  * @param  None
  * @retval None
  */
void TIM3_IRQHandler(void)
{
    
     
	if(TIM_GetITStatus(TIM3, TIM_IT_Update) == SET) 
  {
    
    
		/* Clear TIM3 Update compare interrupt pending bit */
    TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
		TIM3_Update_Cnt++;
	}
	
  if(TIM_GetITStatus(TIM3, TIM_IT_CC2) == SET) 
  {
    
    
    /* Clear TIM3 Capture compare interrupt pending bit */
    TIM_ClearITPendingBit(TIM3, TIM_IT_CC2);
    if(CaptureNumber == 0)
    {
    
    
      /* Get the Input Capture value */
			TIM_SetCounter(TIM3, 0);
			TIM3_Update_Cnt = 0;	// 需要特别注意,一定要在第一次捕获到的时候清零
      CaptureNumber = 1;
			TIM_OC2PolarityConfig(TIM3,TIM_ICPolarity_Falling);	// 配置为下降沿
    }
    else if(CaptureNumber == 1)
    {
    
    
      /* Get the Input Capture value */
      IC3ReadValue1 = TIM_GetCounter(TIM3) + 65536*TIM3_Update_Cnt;
			CaptureNumber = 2;
			TIM_OC2PolarityConfig(TIM3,TIM_ICPolarity_Rising);	// 配置为上降沿
    }
		else if(CaptureNumber == 2)
		{
    
    
			/* Get the Input Capture value */
      IC3ReadValue2 = TIM_GetCounter(TIM3) + 65536*TIM3_Update_Cnt; 
      
			/* Frequency computation */
      TIM3Freq = (uint32_t) (SystemCoreClock * 1.0 / IC3ReadValue2 + 0.5);
			TIM3Duty = (uint32_t) (IC3ReadValue1 * 100.0 / IC3ReadValue2 + 0.5);
      CaptureNumber = 0;
		}
  }
}

2021/02/02更新

在之前提到了TIM_GetCounter与TIM_GetCapture1之间的区别,知道了TIM_GetCapture1要准确一些,但是在之前的程序由于偷懒还是使用的是TIM_GetCounter嘿嘿,这次更新一下使用TIM_GetCapture1来检测的中断服务函数,不过下面的代码使用的时定时器2的通道2, 没有添加占空比的检测,可以自己根据需要,按照上面的方法进行检测。
需要特别注意,Tim2_Update_Cnt 一定要在第一次捕获到的时候清零,在写蓝桥杯嵌入式的代码的时候,因为把它放在了第二次捕获的时候清零,导致得到的周期是之前的一倍,找了很久才发现错误,害…都是泪啊…

/**
  * @brief  This function handles TIM2 global interrupt request.
  * @param  None
  * @retval None
  */
void TIM2_IRQHandler(void)
{
    
     
	if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) 
  {
    
    
    /* Clear TIM2 Capture compare interrupt pending bit */
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    Tim2_Update_Cnt++;
  }
	
  if(TIM_GetITStatus(TIM2, TIM_IT_CC2) == SET) 
  {
    
    
		total_meter_now++;
    /* Clear TIM2 Capture compare interrupt pending bit */
    TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);
    if(Time2_CaptureNumber == 0)
    {
    
    
      /* Get the Input Capture value */
			Tim2_Update_Cnt = 0;
      Time2_IC2ReadValue1 = TIM_GetCapture2(TIM2);
      Time2_CaptureNumber = 1;
    }
    else if(Time2_CaptureNumber == 1)
    {
    
    
      /* Get the Input Capture value */
      Time2_IC2ReadValue2 = TIM_GetCapture2(TIM2); 
      
      Time2_Capture = ( Tim2_Update_Cnt * (0xFFFF) + Time2_IC2ReadValue2 - Time2_IC2ReadValue1);
      
      /* Frequency computation */ 
      TIM2Freq = (uint32_t) ((SystemCoreClock * 1.0 / Time2_Capture) + 0.5);
      Time2_CaptureNumber = 0;
    }
  }
}

猜你喜欢

转载自blog.csdn.net/qq_43715171/article/details/113372999