因为一个项目的要求,需要测速,之前就想学一下stm32的捕获模式了,借这个机会,刚好利用一下,现在分享出来。

硬件:

MCU:stm32f103zet6

平台:航太电子流行7号智能小车

显示:mini12864


软件:

语言:C语言

IDE:keil5


测速部分环境是典型的测速方法,测速码盘加光电对管,单片机采集光电对管模块传来的PWM波,要对两个轮子测速,实际速度取俩轮的较大值。

软件上采用了TIM1的捕获模式,使用了通道CH1和CH4,分别对应PA8和PA11,先看下官方对输入捕获模式的描述:

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

以下例子说明如何在TI1输入的上升沿时捕获计数器的值到TIMx_CCR1寄存器中,步骤如下: 

● 选择有效输入端:TIMx_CCR1必须连接到TI1输入,所以写入TIMx_CCR1寄存器中的CC1S=01,只要CC1S不为’00’,通道被配置为输入,并且TIMx_CCR1寄存器变为只读。

● 根据输入信号的特点,配置输入滤波器为所需的带宽(即输入为TIx时,输入滤波器控制位是TIMx_CCMRx寄存器中的ICxF位)。假设输入信号在最多5个内部时钟周期的时间内抖动,我们须配置滤波器的带宽长于5个时钟周期;因此我们可以(以fDTS频率)连续采样8次,以确认在TI1上一次真实的边沿变换,即在TIMx_CCMR1寄存器中写入IC1F=0011。 

● 选择TI1通道的有效转换边沿,在TIMx_CCER寄存器中写入CC1P=0(上升沿)。 

● 配置输入预分频器。在本例中,我们希望捕获发生在每一个有效的电平转换时刻,因此预分频器被禁止(写TIMx_CCMR1寄存器的IC1PS=00)。 

● 设置TIMx_CCER寄存器的CC1E=1,允许捕获计数器的值到捕获寄存器中。                                      

       ● 如果需要,通过设置TIMx_DIER寄存器中的CC1IE位允许相关中断请求,通过设置TIMx_DIER寄存器中的CC1DE位允许DMA请求。


● 产生有效的电平转换时,计数器的值被传送到TIMx_CCR1寄存器。

● CC1IF标志被设置(中断标志)。当发生至少2个连续的捕获时,而CC1IF未曾被清除,CC1OF也被置1。

● 如设置了CC1IE位,则会产生一个中断。 

● 如设置了CC1DE位,则还会产生一个DMA请求。 为了处理捕获溢出,建议在读出捕获溢出标志之前读取数据,这是为了避免丢失在读出捕获溢出标志之后和读取数据之前可能产生的捕获溢出信息。

 注: 设置TIMx_EGR寄存器中相应的CCxG位,可以通过软件产生输入捕获中断和/或DMA请求。


注意我们在初始化时需要设置好跳变的采样次数,因为车轮产生的PWM波频率相对捕获速度会很低,所以我们应该把采样次数设置大一点,我们设置的是最大,32次对应值时0x0f。之前没注意,一直让他为默认的0,导致得到的结果很大,而且变动也很大,这是采样不准的结果。

下面是对TM1捕获模式的初始化,采用72M的晶振速度,配置的定时器频率是10KHz,周期是65535:

speed.c

void MeaSpeedInit(void)
{
	  //参数声明
  GPIO_InitTypeDef WheelGPIO_InitStructure;
  NVIC_InitTypeDef WheelNVIC_InitStructure;
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  TIM_ICInitTypeDef  TIM_ICInitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
	
	  //速度捕获通道时基和通道配置 时钟 10000
  TIM_TimeBaseStructure.TIM_Period = 65535;	
  TIM_TimeBaseStructure.TIM_Prescaler = 7200-1;
  TIM_TimeBaseStructure.TIM_ClockDivision = 0;
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

	/**************左轮配置******************/
  TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
  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 = 0x0f;
	
	  //左轮速度捕获输入管脚配置 PA8
  WheelGPIO_InitStructure.GPIO_Pin =  GPIO_Pin_8;
  WheelGPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  WheelGPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &WheelGPIO_InitStructure);
	
	TIM_ICInit(TIM1, &TIM_ICInitStructure);
	TIM_ITConfig(TIM1, TIM_IT_CC1, ENABLE);	
/**************左轮配置结束******************/

	/**************右轮配置******************/
  TIM_ICInitStructure.TIM_Channel = TIM_Channel_4;
  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 = 0x0f;
	
	  //右轮速度捕获输入管脚配置 PA11
  WheelGPIO_InitStructure.GPIO_Pin =  GPIO_Pin_11;
  WheelGPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  WheelGPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &WheelGPIO_InitStructure);
	
	TIM_ICInit(TIM1, &TIM_ICInitStructure);
	TIM_ITConfig(TIM1, TIM_IT_CC4, ENABLE);	
/**************右轮配置结束******************/

  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  WheelNVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQn;
  WheelNVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  WheelNVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  WheelNVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&WheelNVIC_InitStructure);

  TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
  TIM_Cmd(TIM1, ENABLE);
	
}


注意下面几个宏定义,不同通道获取的捕获值在不同的寄存器中:

//定义左轮捕捉通道与捕获值  TIM1通道1
#define  LEFTCAPTURECHANNEL TIM1, TIM_IT_CC1
#define  LEFTCAPTUREVALUE TIM_GetCapture1(TIM1)
//<span style="font-family: Arial, Helvetica, sans-serif;">TIM1通道4</span>
#define  RIGHTCAPTURECHANNEL TIM1, TIM_IT_CC4
#define  RIGHTCAPTUREVALUE TIM_GetCapture4(TIM1)



另外,由于使用到的是高级定时器1,中断入口是 TIM1_CC_IRQn

stm32f10x_it.c

/**-------------------------------------------------------
  * @函数名 TIM1_IRQHandler
  * @功能   TIM1中断处理函数,轮子测速捕获模式
  * @参数   无
  * @返回值 无
***------------------------------------------------------*/
void TIM1_CC_IRQHandler(void)
{
	WheelCaptureIRQ();
}


下面来看中断处理函数,记录最相邻的两个下降沿的时间,并记录到buf中,buf中的数据为最近的每一个脉冲周期的时间,方便后面的滤波计算,左右轮测速在同一个中断函数中

speed.c

/*
左右轮测脉冲周期中断服务函数  在stm32f10x-it.c中的TIMx_IRQHandler()处注册使用
*/
void WheelCaptureIRQ(void)
{
  if(TIM_GetITStatus(LEFTCAPTURECHANNEL) == SET) 
  {
		speed_contiue=100;
    TIM_ClearITPendingBit(LEFTCAPTURECHANNEL);
    if(LeftWheelCaptureTime == 0)
    {
      LeftWheel1stCapture = LEFTCAPTUREVALUE;
      LeftWheelCaptureTime = 1;
    }
    else if(LeftWheelCaptureTime == 1)
    {
      LeftWheel2ndCapture = LEFTCAPTUREVALUE; 
      if (LeftWheel2ndCapture > LeftWheel1stCapture)
      {
        LeftWheelPulsePeriod = (LeftWheel2ndCapture - LeftWheel1stCapture); 
      }
      else
      {
        LeftWheelPulsePeriod = ((0xFFFF - LeftWheel1stCapture) + LeftWheel2ndCapture); 
      }
			LeftPeriodBuf[LeftPeriodIndex++] = LeftWheelPulsePeriod;//记录最近的10个值
			if(LeftPeriodIndex == PERIOD_BUFSIZE) 
				LeftPeriodIndex = 0;
      LeftWheelCaptureTime= 0;
    }
  }
	
	 if(TIM_GetITStatus(RIGHTCAPTURECHANNEL) == SET) 
  {
		speed_contiue=100;
    TIM_ClearITPendingBit(RIGHTCAPTURECHANNEL);
    if(RightWheelCaptureTime == 0)
    {
      RightWheel1stCapture = RIGHTCAPTUREVALUE;
      RightWheelCaptureTime = 1;
    }
    else if(RightWheelCaptureTime == 1)
    {
      RightWheel2ndCapture = RIGHTCAPTUREVALUE; 
      if (RightWheel2ndCapture > RightWheel1stCapture)
      {
        RightWheelPulsePeriod = (RightWheel2ndCapture - RightWheel1stCapture); 
      }
      else
      {
        RightWheelPulsePeriod = ((0xFFFF - RightWheel1stCapture) + RightWheel2ndCapture); 
      }
			RightPeriodBuf[RightPeriodIndex++] = RightWheelPulsePeriod;//记录最近的10个值
			if(RightPeriodIndex == PERIOD_BUFSIZE) RightPeriodIndex = 0;
      RightWheelCaptureTime= 0;
    }
  }
}


下面开始计算速度,滤除最大值和最小值,然后求加权平均值,这样得到的相对稳定的速度值,下面两个函数可以在需要速度的时候调用即可,不需要实时计算因为LeftPeriodBuf和RightPeriodBuf的值一直会是最新的。

unsigned int GetLeftSpeed(void)
{
	int index,intex_temp=0;
	uint16_t LeftPeriodBufTemp[PERIOD_BUFSIZE];
	u8 max_index=0,min_index=0;
	uint16_t LeftValueTemp=0;
	unsigned int LeftValueAvg=0;//平均值
	u8 index_total=0;
	//将存储的数据按顺序读取到本地缓存
	for(index = LeftPeriodIndex;index < PERIOD_BUFSIZE ;index++)
	{
		LeftPeriodBufTemp[intex_temp++] = LeftPeriodBuf[index];
	}
	for(index = 0;index < LeftPeriodIndex ;index++)
	{
		LeftPeriodBufTemp[intex_temp++] = LeftPeriodBuf[index];
	}	
	
	//取得最大值及其编号
	LeftValueTemp = LeftPeriodBufTemp[0];
	for(index = 0;index < PERIOD_BUFSIZE-1; index++)
	{
		if(LeftValueTemp < LeftPeriodBufTemp[index+1])
		{
			max_index = index+1;
			LeftValueTemp = LeftPeriodBufTemp[index+1];
		}
	}
	//取得最小值及其编号
	LeftValueTemp = LeftPeriodBufTemp[0];
	for(index = 0;index < PERIOD_BUFSIZE-1; index++)
	{
		if(LeftValueTemp > LeftPeriodBufTemp[index+1])
		{
			min_index = index+1;
			LeftValueTemp = LeftPeriodBufTemp[index+1];
		}
	}
	
	//去掉最大值和最小值 求加权平均值
	for(index = 0;index < PERIOD_BUFSIZE;index++)
	{
		if((index != min_index) && (index != max_index))
		{
			LeftValueAvg += LeftPeriodBufTemp[index]*(index+1);
			index_total += index+1;
		}
	}
	LeftValueAvg = LeftValueAvg/index_total;//取平均值
	
	for(index = 0;index < PERIOD_BUFSIZE;index++)
	{
		printf("%d\t",LeftPeriodBufTemp[index]);
	}	
	
	if(LeftValueAvg == 0) return 0;
	LeftValueAvg = 10445/LeftValueAvg;
	printf("\t%d\r\n",LeftValueAvg);
	return LeftValueAvg;
}

unsigned int GetRightSpeed(void)
{
	int index,intex_temp=0;
	uint16_t RightPeriodBufTemp[PERIOD_BUFSIZE];
	u8 max_index=0,min_index=0;
	uint16_t RightValueTemp=0;
	unsigned int RightValueAvg=0;//平均值
	u8 index_total=0;
	
	//将存储的数据按顺序读取到本地缓存
	for(index = RightPeriodIndex;index < PERIOD_BUFSIZE ;index++)
	{
		RightPeriodBufTemp[intex_temp++] = RightPeriodBuf[index];
	}
	for(index = 0;index < RightPeriodIndex ;index++)
	{
		RightPeriodBufTemp[intex_temp++] = RightPeriodBuf[index];
	}	
	
	//取得最大值及其编号
	RightValueTemp = RightPeriodBufTemp[0];
	for(index = 0;index < PERIOD_BUFSIZE-1; index++)
	{
		if(RightValueTemp < RightPeriodBufTemp[index+1])
		{
			max_index = index+1;
			RightValueTemp = RightPeriodBufTemp[index+1];
		}
	}
	//取得最小值及其编号
	RightValueTemp = RightPeriodBufTemp[0];
	for(index = 0;index < PERIOD_BUFSIZE-1; index++)
	{
		if(RightValueTemp > RightPeriodBufTemp[index+1])
		{
			min_index = index+1;
			RightValueTemp = RightPeriodBufTemp[index+1];
		}
	}
	
	//去掉最大值和最小值 求加权平均值
	for(index = 0;index < PERIOD_BUFSIZE;index++)
	{
		if((index != min_index) && (index != max_index))
		{
			RightValueAvg += RightPeriodBufTemp[index]*(index+1);
			index_total += index+1;
		}
	}
	RightValueAvg = RightValueAvg/index_total;//取平均值
	if(RightValueAvg == 0) return 0;
	RightValueAvg = 10445/RightValueAvg;
	return RightValueAvg;
}