STM32 编码器驱动/旋转编码器旋钮encoder

本文已比较纯粹的方式介绍编码器和驱动的编写
在这里插入图片描述
编码器最少有两个输出信号,一种典型的结构如上图所示。AB是编码器的输出引脚。当触点和黄色的金属片接触的时候信号发生跳变沿,可以上上升沿也可以是下降沿,具体根据AB引脚默认的电平状态,和金属片电平状态。
在这里插入图片描述
当编码器转过一段角度后就会出现上图的波形。AB信号交替出现脉冲。上图所示的状态。B还在金属片上B是高电平,A已经不再金属片上恢复默认电平低。逆时针转动时,A触点比B触点先接触到金属片。所以A的高电平超前B的高电平。超前多少,根据金属片的长度和AB触点的间距决定。所以当顺时针转动的时候,B触点比A触点先接触到金属片。
根据这样的特点可以,从AB出现脉冲的先后可以判别出旋转的方向。脉冲的个数可以判断出转了多少角度。

实际波形
上通道为A,下通道为B
下图为正逆时针旋转
在这里插入图片描述
下图为顺时针旋转
在这里插入图片描述

对于数字输入输出的MCU来说,可以使用三种方式来实现编码器的驱动

1.GPIO抽样法

按照一定的时间间隔去采集AB触点的电平,根据电平的状态去判别脉冲计数。
上图所示,I:A出现低电平时,B是高电平。出现第一张图顺时针的状态。
II: B是低电平的时候,A是高电平,说明出现第二张图的状态。
抽样法关键在于测量到上述I,II的特征电平。抽样的时间需要根据旋转编码器的最快速度来进行设定,已保证能够采样到特征电平。

一般来说手工扭动的旋转编码器的采样间隔可以是1ms
代码如下,输入参数gpio1,gpio2为AB项的电平值。需要1ms为周期进行采样
encoder_poision_cnt 为脉冲计数器
encoder_dir 为方向
实验结果还不错,虽然看上去简单,但是其稳定性不错。


void task2_task(void *pvParameters)
{

    while(1)
    {
		encoder_gpio_in =    GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_10);
		encoder_gpio_in2 = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_11);
		encoder_dir =  app_encoder_gpio_mes_test(encoder_gpio_in,encoder_gpio_in2);
        vTaskDelay(1);
    }
    
}
uint16_t app_encoder_gpio_mes_test(uint16_t gpio1,uint16_t gpio2)
{
	static char lock=0;
	static uint16_t knob_dir =0;
	 
    if((gpio1 == 0 && gpio2 ==1)&(!lock))
    {
    	lock =1;
    	knob_dir= 1;
		encoder_poision_cnt++;
    }
	else if((gpio2 ==0 && gpio1==1)&(!lock))
	{
		lock =1;
		knob_dir = 2;
		if(encoder_poision_cnt>0)
			encoder_poision_cnt--;
	}
	else if((gpio2 ==1 && gpio1==1))
	{
		lock =0;
		knob_dir= 0;
		
	
	}

	return knob_dir;

}

2.中断法

由于编码器是金属触点,所以必然会出现抖动。抖动对于响应快速的外部中断来说是一个比较麻烦的问题。
根据抖动的持续时间,可以做一个简单的延时滤波。
原理是AB接触到触点时,会发生电平的跳边沿,跳边沿触发中断后,判断电平的状态。从而按照1的方法判别

void app_encoder_it_init()
{
	/*PA10  PA11  encoder In*/
	NVIC_InitTypeDef   NVIC_InitStruct;
	EXTI_InitTypeDef   EXTI_InitStruct;

	/*exti line config*/
	EXTI_InitStruct.EXTI_Line =EXTI_Line10;
	EXTI_InitStruct.EXTI_LineCmd =ENABLE ;
	EXTI_InitStruct.EXTI_Mode =EXTI_Mode_Interrupt;
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
	EXTI_Init(&EXTI_InitStruct);
	EXTI_InitStruct.EXTI_Line =EXTI_Line11;
	EXTI_Init(&EXTI_InitStruct);
	GPIO_EXTILineConfig(GPIOA,GPIO_PinSource10);
	GPIO_EXTILineConfig(GPIOA,GPIO_PinSource11);

	/*NVIC config*/
	NVIC_InitStruct.NVIC_IRQChannel =EXTI15_10_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd =ENABLE ;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 14;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;


	NVIC_Init( &NVIC_InitStruct);

}
static void do_things(void)
{
	int i =2000;
	while (i>0)
		{
		i--;
		}
}
void EXTI15_10_IRQHandler(void)
{
	static char lock=0;
	static uint16_t knob_dir =0;

	do_things(); /* simple filter */

	if(EXTI_GetITStatus(EXTI_Line10))
	{
	
		/*Rising or Falling or noise*/
		  if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_10))/*Rising*/
		  {
		  	
		  }
		  else/*Falling*/ 	
		  {	
		 
			if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_11))
				{
					encoder_poision_cnt++;
					knob_dir = FORWORD;
				}
		  
		  }

		EXTI_ClearITPendingBit(EXTI_Line10);
	}
	else if (EXTI_GetITStatus(EXTI_Line11))
	{
		
		 /*Rising or Falling or noise*/
		  if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_11))/*Rising*/
		  {		
		
		  		
		  		
		  }
		  else /*Falling*/
		  {
		  	
		  if((GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_10))& (encoder_poision_cnt>0))
		  	{
				encoder_poision_cnt--;
				knob_dir = BACKWORD;
		  	}
		  }
		  
		EXTI_ClearITPendingBit(EXTI_Line11);
	}

3.使用TIM 的编码器模块

STM32 的TIM模块自己带encoder的接口,可以不在CPU的参与下自己动进行方向,脉冲计数。
需要用户去读取CNT和DIR的数值已获取计数值和旋转方向。
TIM模块庞大复杂,如果不熟悉该模块可以使用上面的基础方法。
代码如下

void app_encoder_tim_gpio_init(void)
{

	GPIO_InitTypeDef   GPIO_InitStruct;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);


	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStruct.GPIO_Pin  =GPIO_Pin_8;
	GPIO_InitStruct.GPIO_Speed =GPIO_Speed_10MHz;

	GPIO_Init(GPIOA, &GPIO_InitStruct);

	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStruct.GPIO_Pin  =GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed =GPIO_Speed_10MHz;

	GPIO_Init(GPIOA, &GPIO_InitStruct);

	app_encoder_tim();


}
void app_encoder_tim(void)
{	

	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_ICInitTypeDef    TIM_ICInitStructure;


	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
	TIM_TimeBaseStructure.TIM_Prescaler = 0x0; // No prescaling 
	
	TIM_TimeBaseStructure.TIM_Period = 60000; 
	
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 
	
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
	 

	TIM_ICStructInit(&TIM_ICInitStructure);
	
	TIM_ICInitStructure.TIM_ICFilter = 4;//ICx_FILTER;
	
	TIM_ICInit(TIM1, &TIM_ICInitStructure);

	TIM1->CNT = 0;

	
	TIM_EncoderInterfaceConfig(TIM1, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);

	TIM_Cmd(TIM1,ENABLE);

}

版权声明:本文为CSDN博主「dengjingg」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/denghuajing/article/details/122683502

本文已比较纯粹的方式介绍编码器和驱动的编写
在这里插入图片描述
编码器最少有两个输出信号,一种典型的结构如上图所示。AB是编码器的输出引脚。当触点和黄色的金属片接触的时候信号发生跳变沿,可以上上升沿也可以是下降沿,具体根据AB引脚默认的电平状态,和金属片电平状态。
在这里插入图片描述
当编码器转过一段角度后就会出现上图的波形。AB信号交替出现脉冲。上图所示的状态。B还在金属片上B是高电平,A已经不再金属片上恢复默认电平低。逆时针转动时,A触点比B触点先接触到金属片。所以A的高电平超前B的高电平。超前多少,根据金属片的长度和AB触点的间距决定。所以当顺时针转动的时候,B触点比A触点先接触到金属片。
根据这样的特点可以,从AB出现脉冲的先后可以判别出旋转的方向。脉冲的个数可以判断出转了多少角度。

实际波形
上通道为A,下通道为B
下图为正逆时针旋转
在这里插入图片描述
下图为顺时针旋转
在这里插入图片描述

对于数字输入输出的MCU来说,可以使用三种方式来实现编码器的驱动

1.GPIO抽样法

按照一定的时间间隔去采集AB触点的电平,根据电平的状态去判别脉冲计数。
上图所示,I:A出现低电平时,B是高电平。出现第一张图顺时针的状态。
II: B是低电平的时候,A是高电平,说明出现第二张图的状态。
抽样法关键在于测量到上述I,II的特征电平。抽样的时间需要根据旋转编码器的最快速度来进行设定,已保证能够采样到特征电平。

一般来说手工扭动的旋转编码器的采样间隔可以是1ms
代码如下,输入参数gpio1,gpio2为AB项的电平值。需要1ms为周期进行采样
encoder_poision_cnt 为脉冲计数器
encoder_dir 为方向
实验结果还不错,虽然看上去简单,但是其稳定性不错。


void task2_task(void *pvParameters)
{

    while(1)
    {
		encoder_gpio_in =    GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_10);
		encoder_gpio_in2 = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_11);
		encoder_dir =  app_encoder_gpio_mes_test(encoder_gpio_in,encoder_gpio_in2);
        vTaskDelay(1);
    }
    
}
uint16_t app_encoder_gpio_mes_test(uint16_t gpio1,uint16_t gpio2)
{
	static char lock=0;
	static uint16_t knob_dir =0;
	 
    if((gpio1 == 0 && gpio2 ==1)&(!lock))
    {
    	lock =1;
    	knob_dir= 1;
		encoder_poision_cnt++;
    }
	else if((gpio2 ==0 && gpio1==1)&(!lock))
	{
		lock =1;
		knob_dir = 2;
		if(encoder_poision_cnt>0)
			encoder_poision_cnt--;
	}
	else if((gpio2 ==1 && gpio1==1))
	{
		lock =0;
		knob_dir= 0;
		
	
	}

	return knob_dir;

}

2.中断法

由于编码器是金属触点,所以必然会出现抖动。抖动对于响应快速的外部中断来说是一个比较麻烦的问题。
根据抖动的持续时间,可以做一个简单的延时滤波。
原理是AB接触到触点时,会发生电平的跳边沿,跳边沿触发中断后,判断电平的状态。从而按照1的方法判别

void app_encoder_it_init()
{
	/*PA10  PA11  encoder In*/
	NVIC_InitTypeDef   NVIC_InitStruct;
	EXTI_InitTypeDef   EXTI_InitStruct;

	/*exti line config*/
	EXTI_InitStruct.EXTI_Line =EXTI_Line10;
	EXTI_InitStruct.EXTI_LineCmd =ENABLE ;
	EXTI_InitStruct.EXTI_Mode =EXTI_Mode_Interrupt;
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
	EXTI_Init(&EXTI_InitStruct);
	EXTI_InitStruct.EXTI_Line =EXTI_Line11;
	EXTI_Init(&EXTI_InitStruct);
	GPIO_EXTILineConfig(GPIOA,GPIO_PinSource10);
	GPIO_EXTILineConfig(GPIOA,GPIO_PinSource11);

	/*NVIC config*/
	NVIC_InitStruct.NVIC_IRQChannel =EXTI15_10_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd =ENABLE ;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 14;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;


	NVIC_Init( &NVIC_InitStruct);

}
static void do_things(void)
{
	int i =2000;
	while (i>0)
		{
		i--;
		}
}
void EXTI15_10_IRQHandler(void)
{
	static char lock=0;
	static uint16_t knob_dir =0;

	do_things(); /* simple filter */

	if(EXTI_GetITStatus(EXTI_Line10))
	{
	
		/*Rising or Falling or noise*/
		  if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_10))/*Rising*/
		  {
		  	
		  }
		  else/*Falling*/ 	
		  {	
		 
			if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_11))
				{
					encoder_poision_cnt++;
					knob_dir = FORWORD;
				}
		  
		  }

		EXTI_ClearITPendingBit(EXTI_Line10);
	}
	else if (EXTI_GetITStatus(EXTI_Line11))
	{
		
		 /*Rising or Falling or noise*/
		  if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_11))/*Rising*/
		  {		
		
		  		
		  		
		  }
		  else /*Falling*/
		  {
		  	
		  if((GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_10))& (encoder_poision_cnt>0))
		  	{
				encoder_poision_cnt--;
				knob_dir = BACKWORD;
		  	}
		  }
		  
		EXTI_ClearITPendingBit(EXTI_Line11);
	}

3.使用TIM 的编码器模块

STM32 的TIM模块自己带encoder的接口,可以不在CPU的参与下自己动进行方向,脉冲计数。
需要用户去读取CNT和DIR的数值已获取计数值和旋转方向。
TIM模块庞大复杂,如果不熟悉该模块可以使用上面的基础方法。
代码如下

void app_encoder_tim_gpio_init(void)
{

	GPIO_InitTypeDef   GPIO_InitStruct;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);


	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStruct.GPIO_Pin  =GPIO_Pin_8;
	GPIO_InitStruct.GPIO_Speed =GPIO_Speed_10MHz;

	GPIO_Init(GPIOA, &GPIO_InitStruct);

	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStruct.GPIO_Pin  =GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed =GPIO_Speed_10MHz;

	GPIO_Init(GPIOA, &GPIO_InitStruct);

	app_encoder_tim();


}
void app_encoder_tim(void)
{	

	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_ICInitTypeDef    TIM_ICInitStructure;


	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
	TIM_TimeBaseStructure.TIM_Prescaler = 0x0; // No prescaling 
	
	TIM_TimeBaseStructure.TIM_Period = 60000; 
	
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 
	
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
	 

	TIM_ICStructInit(&TIM_ICInitStructure);
	
	TIM_ICInitStructure.TIM_ICFilter = 4;//ICx_FILTER;
	
	TIM_ICInit(TIM1, &TIM_ICInitStructure);

	TIM1->CNT = 0;

	
	TIM_EncoderInterfaceConfig(TIM1, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);

	TIM_Cmd(TIM1,ENABLE);

}

版权声明:本文为CSDN博主「dengjingg」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/denghuajing/article/details/122683502

生成海报
点赞 0

dengjingg

我还没有学会写个人说明!

暂无评论

相关推荐

2021年终总结

年终总结 CSDN的评委好,各位同仁好! 2021年,我担任嵌入式软件开发工程师一职,具体汇报如下: 一、2021年度工作完成情况 在现有的TDOA定位基站和标签的基础上

Verilog实现按键消抖

Verilog实现按键消抖 一、简介 我们在进行按键的时候往往会发生抖动的现象。 通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地