STM32直流减速电机控制篇(三)编码器测速程序编写

  1. 编程思路
    任何一个程序的编写我们都应该先理清楚编程思路,通过上一篇讲解的编码器测速原理我们应该知道要想通过编码器得知电机转速我们第一步就应该是捕获A相和B相输出的脉冲
    在这里插入图片描述
    因为电机速度的定义是单位时间内的转数,所以第二步在一个定时中断里读取一次捕获到的脉冲数(即电机的几何位移)并清零记录脉冲数的变量。经过这两个步骤我们就完成了电机的速度测量。
  2. 代码编写
    利用32单片机(以STM32F103VET6为例,用Keil编程)捕获AB相的脉冲有两种方法第一种利用GPIO的外部中断来捕获跳变沿从而可以记录脉冲数。第二种利用定时器的编码器模式来记录脉冲数。
    外部中断捕获波形
/**************************************************************************
函数功能:外部中断采集编码器初始化
入口参数:无
返回  值:无
**************************************************************************/
void Encoder_Init_TIM_Exit0(void)//用于检测编码器A相使用PA0引脚
{
   	GPIO_InitTypeDef GPIO_InitStructure;
   	EXTI_InitTypeDef EXTI_InitStructure;
   	NVIC_InitTypeDef NVIC_InitStructure;
   
   	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//外部中断,需要使能AFIO时钟
   	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能PA端口时钟
   	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;	            //端口配置
   	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;         //上拉输入
   	GPIO_Init(GPIOA, &GPIO_InitStructure);					      //根据设定参数初始化GPIOB 
 	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
 	EXTI_InitStructure.EXTI_Line=EXTI_Line0;
 	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;	
 	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//跳变沿触发
 	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
 	EXTI_Init(&EXTI_InitStructure);	 	//根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
     
  // 配置中断优先级 													
   NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;	  
   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;	
   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
   NVIC_Init(&NVIC_InitStructure);
}


void Encoder_Init_TIM_Exit1(void)//用于检测编码器B相使用PA1引脚
{
   	GPIO_InitTypeDef GPIO_InitStructure;
   	EXTI_InitTypeDef EXTI_InitStructure;
   	NVIC_InitTypeDef NVIC_InitStructure;
   	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//外部中断,需要使能AFIO时钟
   	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能PA端口时钟
   	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;	            //端口配置
   	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;         //上拉输入
   	GPIO_Init(GPIOA, &GPIO_InitStructure);					      //根据设定参数初始化GPIOB 
 	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource1);
 	EXTI_InitStructure.EXTI_Line=EXTI_Line1;
 	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;	
 	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//跳变沿触发
 	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
 	EXTI_Init(&EXTI_InitStructure);	 	//根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
     
  //配置中断优先级												
   NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;	  
   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;	
   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
   NVIC_Init(&NVIC_InitStructure);
}

以上代码完成了A相 B相外部中断的初始化当有跳变沿发生时则进入外部中断服务函数,在外部中断服务函数中对脉冲进行计数,如果电机正转则计数的变量Encoder_A_EXTI(在中断服务函数中计数时使用)就加1,反转则减1。使用四倍频计数提高测速精度,所以A相和B相的跳变沿检测到后我们都进行计数。首先看A相

void EXTI0_IRQHandler(void)  //外部中断线0服务函数
{
		EXTI->PR=1<<0;  //清除LINE上的中断标志位
		if(PAin(0)==0)   //这里判断检测到的是否是下降沿
	{
	if(PAin(1)==0)   Encoder_A_EXTI++;//看B相的电平如果是高电机就是正转则加1,否则就是反转减1
	else             Encoder_A_EXTI--;
	}
	else                  //上升沿
	{ 
	if(PAin(1)==0)  Encoder_A_EXTI--; //B相低电平为正转,加1,高电平反转减1
	else             Encoder_A_EXTI++;
	}		
}

B相同理

void EXTI1_IRQHandler(void)//外部中断线1服务函数
{			
		EXTI->PR=1<<1;  //清除LINE上的中断标志位
	if(PAin(1)==1) //这里判断检测到的是否是上升沿
	{
	if(PAin(0)==0)  Encoder_A_EXTI++;  //看A相的电平如果是低,电机就是正转则加1,否则就是反转减1 
	else            Encoder_A_EXTI--;
	}
	else
	{
	if(PAin(0)==0)  Encoder_A_EXTI--;
	else            Encoder_A_EXTI++;
	}		
}

通过上数代码我们就可以在Encoder_A_EXTI这个变量中记录A相和B相的脉冲数,然后我们再通过一个函数来读取并清零Encoder_A_EXTI。

int Read_Encoder(u8 TIMX)
{
    int Encoder_TIM;    
   switch(TIMX)
	 {
	   case 2:  Encoder_TIM=(short)Encoder_A_EXTI;  Encoder_A_EXTI=0; break;	
	   default:  Encoder_TIM=0;
	 }
		return Encoder_TIM;
}

然后再一个定时中断中使用此函数即可知道电机的转速。下面是第二种方法的代码
定时器编码器模式

/**************************************************************************
函数功能:把TIM3初始化为编码器接口模式
入口参数:无
返回  值:无
**************************************************************************/
void Encoder_Init_TIM3(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;  
  TIM_ICInitTypeDef TIM_ICInitStructure;  
  GPIO_InitTypeDef GPIO_InitStructure;
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//使能定时器3的时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能PB端口时钟
	
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;	//端口配置
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);					      //根据设定参数初始化GPIOB
  
  TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
  TIM_TimeBaseStructure.TIM_Prescaler = 0x0; // 预分频器 
  TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD-1; //设定计数器自动重装值
  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频:不分频
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM向上计数  
  TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
  TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3
  TIM_ICStructInit(&TIM_ICInitStructure);
  TIM_ICInitStructure.TIM_ICFilter = 10;
  TIM_ICInit(TIM3, &TIM_ICInitStructure);
  TIM_ClearFlag(TIM3, TIM_FLAG_Update);//清除TIM的更新标志位
  TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
  //Reset counter
  TIM_SetCounter(TIM3,0);
  TIM_Cmd(TIM3, ENABLE); 
}

初始化完成后我们可以直接在TIM3的CNT计数器中读取到我们捕获的脉冲数,这里要注意一点应为CNT寄存器的最大只能是65536超过这个值该计数器(CNT)就会溢出,所以我们要即使去读取CNT然后把它清空。同样是利用 Read_Encoder()这个函数

int Read_Encoder(u8 TIMX)
{
    int Encoder_TIM;    
   switch(TIMX)
	 {
	   case 2:  Encoder_TIM=(short)Encoder_A_EXTI;  Encoder_A_EXTI=0; break;	
		 case 3:  Encoder_TIM= (short)TIM3 -> CNT;  TIM3 -> CNT=0;break;		
		 default:  Encoder_TIM=0;
	 }
		return Encoder_TIM;
}

显而易见利用定时器的编码器模式来测速更加快捷简单,并且没有这么多中断占用的MCU资源更少,所以测速我们一般优先使用第二种方法,只有当定时器资源不足时才会使用第一种方法。完整的Keil代码我也会提供给大家。代码链接:https://pan.baidu.com/s/1ESA4myZIz2QCGx0Q6EKPpw
提取码:01kd


版权声明:本文为qhdd123原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
THE END
< <上一篇
下一篇>>