STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)

文章目录

  • ENCODE模式探索
  • 普通IO口探索
  • 定时器使用
  • 外部中断探索
  • 编码器开关的理解
  • 最后的实现方案

这篇文章主要还是记录整个过程以及想法的不断改进,对于一些实际的操作还有代码,我准备都分开写到不同的文章。

STM32F407 EC11旋转编码器驱动函数,里面写了好多个版本,可以根据需要使用,这个只是实现功能,实际要用,一般都在中断中,按照思路改到中断就可以了。

STM32F407 EC11旋转编码器驱动函数

回到正文

对于旋转编码器,先来上板子的原理图接口
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第1张图片
本次的板子设计是使用了PE13和PE14的IO口,对应的也是定时器TIM1ch3 和TIM1ch4
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第2张图片
编码器开关的脉冲也如图所示,一会儿分析

ENCODE模式探索

接到任务后,就去各大论坛开始搜索关键词 Cube+编码器 ,然后出现了大量的关键词关于encode模式,经过了解发现这是stm32自带的硬件解码功能,需要使用高级定时器的ch1和ch2,然后我去查找了板子上的PE13PE14,发现了PE13 PE14是TIM1ch3和TIM1ch4,这就很尴尬,这里算是就这样卡住了,要是想用encode模式就得修改电路进行飞线的操作。

起初我不太想飞线,看到预留IO可以焊接插针,然后就用插针和杜邦线连接了起来,没想到这个是浪费时间的开始,起初是无论我怎么参照网上的代码,程序的输出都没有任何的反应,我只能去不断地反复的理解参数以及检查接线还有搜索问题的解决方法,很可惜,没有找到任何解决方法,然后师哥这时候提醒我是不是杜邦线连接导致的接触不良,我才恍然大悟,这种边沿的判断,必须要稳定的连接啊。

然后还是飞线,最后试一试这个encode模式
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第3张图片
但是不知道为什么,对于修改后的电路,我依然没有办法跑通encode模式,目前也没有找到问题在哪里,还是把encode模式的调试过程记录一下,以后要是有时间可以继续调试
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第4张图片
首先还是25Mhz的外部晶振先开启
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第5张图片
然后就是SYS配置下载调试接口的模式,DEBUG选择 SW模式
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第6张图片
TIM1定时器直接开启encode mode就可以看到右边对应引脚变成了绿色成功打开,然后进行一些配置
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第7张图片

主要是这两个位置的修改,一个是因为检测了上升沿和下降沿,所以4个脉冲信号最后结果会是4倍,所以要进行4分频,也就是4个脉冲计数一次,也就是4-1 = 3作为分频系数(从0开始计数的)
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第8张图片

TIM Encoder GPIO上拉模式配置,定时器两个引脚,全部改成 Pull-Up,即上拉模式,主要用于没有外部上拉的编码器读取时,可以确定引脚电平,防止出错
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第9张图片

剩下的就是时钟的配置了,属于基本功范畴

按照网上的教程,都是到这里就配置好了,直接生成代码然后往里面写相应的库函数就可以了

在main.c文件中初始化中添加打开定时器的encoder模式:

HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL);

在循环中调用 __HAL_TIM_IS_TIM_COUNTING_DOWN 可以获得当前电机的转向 0为正、1为负

Direction = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim1);  

然后while循环里调用下面这一句函数就可以获取到encoder编码器的计数值:

`enc1 = (uint32_t)(__HA`L_TIM_GET_COUNTER(&htim1));//获取定时器的值

但是很奇怪,在我的板子上没有检测到因该有的现象,无论是正转还是反转都是会增加定时器的值,看网上的正确 现象应该是正转增加反转减少才是,并且无法检测到正转,只能检测到反转。

老师后来提醒测试一下端口的中断是不是有问题,当时的实验结果是PE9无法检测到外部中断,然后encode模式我也只能先放弃了。
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第10张图片

这里还有一个问题,最开始我检测数值变化,使用的方法是开启调试模式,在watch窗口中监测变量的变化,然后有些时候就会出现检测不到的现象,一开始我以为是哪里有问题,后来发现是调试模式的速度问题,没有办法做出这么快的反应。

后来的话统一使用串口printf来打印变量的变化,波特率也调到了115200,这样的话就可以检测到变化了,不会出现检测不到的现象了。

普通IO口探索

这时候我看到了一篇文章

认识EC11旋转编码器&编写驱动程序

这个是用通过普通IO口来判断多组电平,然后判断正转还是反转,很有启发,然后移植到了我的程序中来,这里一开始遇到了很多的理解问题,对于这个检测的逻辑没有一个明确的认知。

最先时候我把PE13 PE14初始化为了输入模式,幻想着直接在while主循环里面捕获就可以了检测,我把上面这个检测程序Encoder_EC11_Scan()放到了while(1)循环的主程序中,但是发现检测不到。

//-------->>>>>>>>--------注意事项:EC11旋转编码器的扫描时间间隔控制在1~4ms之间,否则5ms及以上的扫描时间在快速旋转时可能会误判旋转方向--------<<<<<<<<--------//

这个时候看到了一个这个提示,意识到是不是因为检测的速度问题,然后开始研究定时器的使用,一会儿再说怎么用的,先说一下实验结果以及分析,设置好定时器的时间周期,这时候可以看到有输出结果了。

起初我是用LED灯的变化以及调试模式中监测对应变量的变化,当时看到的结果是正转为0 反转为0xff,以及灯虽然闪烁,但是正转和反转明显两个动作,当时以为自己的结果是正确的,但是因为一开始参考的代码及就是不全的,所以没意识到错误,后来找到了完整的文章,才意识到正转应该是1,反转应该是-1,按键是2。

所以我的实验结果是错误的,需要继续找问题。

后来还看到一篇文章

用STM32对编码开关实现精确计数

定时器使用

对于定时器的使用,也记录一下
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第11张图片

直接在这里开启就好了,使用内部时钟来计数

主要就是这个分频系数还有计数周期的问题
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第12张图片
通过时钟树可以找到TIM对应的是能频率是多少,我这里是75Mhz,也就是75,000,000hz,我要达到的目的是让定时器1ms溢出一次,也就是定时器是1000hz

1000hz = 75,000,000/((750-1) * (100-1)

    • -> 1s/1000hz = 1ms

STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第13张图片
根据实际情况调整可以在tim.c找到对应位置修改
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第14张图片
还有定时器的中断优先级,这个根据自己的需要来设置
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第15张图片
在main.c文件中的下方可以看到HAL_TIM_PeriodElapsedCallback()定时器的回调函数,这里我们要添加上

	if (htim->Instance == TIM2) 
	{
     
    HAL_IncTick();
  }

(在这个里面添加我们上一节中普通IO的检测函数就可以了,虽然我最后的实验结果没成功,但是学会了定时器,为后面埋下了伏笔。)

这里重点还是定时器的配置以及抢占优先级响应优先级的配置。

外部中断探索

这里可以说是陷入了一个僵局,经过老师的指点,可以使用外部中断检测下降沿的变化来进行检测。这时候开始了解外部中断时的使用。

STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第16张图片
外部中断就需要自己手动开启对应位置了
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第17张图片
这里配置成下降沿触发中断。

STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第18张图片
在gpio.c文件中可以找到生成的外部中断的初始化代码
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第19张图片
这里就是中断的函数了,HAL库这里有个特殊的回调函数,可以把很多操作写道回调函数里面,这里我们也要写到回调函数中
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第20张图片
我们在这里可以找到回调函数,HAL都已经写的非常模块化和人性化,这里的操作只需要写在HAL_GPIO_EXTI_Callback函数中就可以了,清除中断的操作自己触发后直接执行了,然后启动回调函数执行操作。
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第21张图片
这个要删除掉,这是弱函数的定义。我们可以看到,PE13 PE14 PE15比较巧合都是在中断线15_10上,并且都是外部中断,所以可以用同一个回调函数。

HAL_GPIO_EXTI_Callback()回调函数在外部中断里可以理解成为,中断触发后,就会进入到HAL_GPIO_EXTI_Callback中来执行定义的操作,然后进来后我们需要判断GPIO_Pin是具体由哪个IO口来触发的。

最简单的就是这种

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
     
  /* Prevent unused argument(s) compilation warning */
  UNUSED(GPIO_Pin);
	
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_GPIO_EXTI_Callback could be implemented in the user file
   */
   	if(GPIO_Pin == A_Pin)
	{
     
		A_flag = 1;
	}
	if(GPIO_Pin == B_Pin)
	{
     
		B_flag = 1;
	}
	if(GPIO_Pin == SW_Pin)
	{
     
	    SW_flag = 1;
	}
}

这个就是如果A_Pin被下降沿触发,就执行A_flag = 1;其他同理

这个是简单的操作,我们显然要判断更多。首先做个测试,看一看旋转编码器的AB电平来到的先后顺序,有个直观的体会

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
     
  /* Prevent unused argument(s) compilation warning */
  UNUSED(GPIO_Pin);
	
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_GPIO_EXTI_Callback could be implemented in the user file
   */
   	if(GPIO_Pin == A_Pin)
	{
     
		printf("A\r\n");
	}
	if(GPIO_Pin == B_Pin)
	{
     
		printf("B\r\n");
	}
}

串口助手中可以观察到的现象是正转先后显示A B,反转是先后显示 B A,这也能测试到我们的编码器工作正常。

到了这里,其实就可以中断中写出来一个最简单的判断函数

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
     
  /* Prevent unused argument(s) compilation warning */
  UNUSED(GPIO_Pin);
	
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_GPIO_EXTI_Callback could be implemented in the user file
   */
   	if(GPIO_Pin == A_Pin)
	{
     
	    if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)
		  printf("A\r\n");
		else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
		  printf("B\r\n");
	}
	
}

把A的波形当作一个时钟信号,当检测到下降沿时候,读取B的高低电平就可以判断出来正转还是反转了。

这样虽然可以实现,但是也有自己的局限性,因为这样无法判断是不是误检测到电平的变化,没有消抖,也没有判断到B是否有下降沿的变化。

编码器开关的理解

对于编码器开关,起初我的理解是把A信号当作时钟信号为基准,来判断B的状态,按照这个思路是没有办法做到一个比较高的稳定性。
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第22张图片
回头重新阅读数据手册,认真考虑了相位差的问题,想要稳定的判断,就要测到A B均变化,波形图中可以清晰的看到A B信号相位差的时间要大于4ms,通过老师的指导,确定了下面的执行方案
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第23张图片
翻译到流程图我的理解如下
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第24张图片
这时候我还没有完全理解老师的思路想法,好多地方还是按照了自己的理解,并且我没有直接写在中断中,而是写成了 函数的形式,通过中断产生的标志位来进行状态的判断

///****************旋转编码开关,版本2*****************************/
返回值1 正转
返回值2 反转
//uint8_t EC11Direction_2(void)
//{
     
//	char Direction_flag = 0;
//	while(1)
//	{
     
//		if(A_flag == 1)//A下降沿触发外部中断
//		{
     
//			HAL_Delay(1);//延时消抖
//			if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_13) == 0)//A下降沿触发1ms后判断是否稳定在了低电平
//			{
     
//				HAL_TIM_Base_Start_IT(&htim2);//开启定时器
//				while(TIM2_flag <= 1)//定时器的一个周期,这里是10ms
//				{
     
//					if(B_flag == 1)//10ms内检测是不是有B下降沿触发
//					{
     
						HAL_Delay(1);//延时消抖
//						TIM2_flag = 0;//清除定时器中断标志位
//					  HAL_TIM_Base_Stop_IT(&htim2);//检测到B了直接关闭定时器
//						HAL_Delay(1);//延时消抖
//						if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)//判断Pin_14的电平,返回旋转方向
//						{
     
							printf("A\r\n");
//							Direction_flag = 1;
//							break;
//						}
//							
//						else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
//						{
     
							printf("B\r\n");
//							Direction_flag = 2;
//							break;
//						}
//					}
//				}
//				HAL_TIM_Base_Stop_IT(&htim2);//定时器一个周期溢出后(TIM2_flag>1),关闭
//				TIM2_flag = 0;//清除定时器标志位
//			}
//			A_flag = 0;//清除A中断的标志位
//		}	
//		
//		if(Direction_flag == 1 | Direction_flag == 2)
//			break;
//	}
//  return Direction_flag;	

//}

有了这个函数,核心还是A下降沿触发了之后,需要开启定时器,在10ms内检测B是不是有下降沿的变化(这里当时没考虑好,应该检测B的边沿变化,上下沿都要检测最好),这里的话放到主函数中是可以正常的执行以及返回结果的。

这里我要说一下这个等待B变化的问题,这里使用了定时器的定时操作,在使用前开启定时器的中断,然后记定时器的溢出次数来判断时间,然后在这个时间内判断是否由B发生了边沿的触发。

但是要考虑到旋转编码开关的实际使用场景,我这个函数虽然可以正常驱动,但是无法有效的使用,因为不在中断里,没有办法做到一个高优先级的触发。

最后的实现方案

本来以为可以直接移植到外部中断中,没想到还是出现了很多问题,当写到中断中时候 ,出现了外部中断中要使用定时器,中断嵌套的现象。

STM32CubeMX外部中断定时器嵌套问题及实验现像

这篇文章我详细记录了问题的出现,这里就不说这么多了。

中断中程序的软件设计流程图如下
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第25张图片
代码实现如图所示
STM32CubeMX EC11旋转编码器开发心路历程(encode模式 外部中断模式 普通IO口模式 定时器模式探索)_第26张图片
到了这里就算是使用 外部中断+定时器 实现了编码器开关的识别。

你可能感兴趣的:(STM32,嵌入式,stm32,单片机,旋转编码器,EC11)