STM32CubeMX EC11旋转编码器普通IO口外部中断+定时器实现


项目背景是在STM32平台上的 普通IO口PE13 PE14使用外部中断+定时器实现,这里因为设计没有选择可以支持 ENCODE MODE的端口。

EC11旋转编码器

在这里插入图片描述
在这里插入图片描述
从这个数据手册中,我们可以设计出我们的思路,主要就是,以A信号作为一个时钟信号,也就是基准信号,检测到A之后,再去判断B的动作,一个相对的电平。

例如,当检测到A信号下降沿触发,检测B信号此时如果是高电平,那就是逆时针,如果是低电平,那就是顺时针。

///****************旋转编码开关,版本1*****************************/
uint8_t EC11Direction(void)
{
	while(1)
	{
		if(A_flag == 1)//A下降沿触发外部中断,A_flag = 1
	  {
		  if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1) //检测B信号电平
			{
				printf("正转\r\n");
				Direction_flag = 1;
				break;
			}
		  else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
			{
				printf("反转\r\n");
				Direction_flag = 2;
				break;
			}	  
	  }	
  return Direction_flag;	

}

这个是最简单的判断方法,这个方法不是特别完善,容易出现干扰和误判断现象。不过整体是思路是这样走的。

中断标志位外部函数中实现

第一个实现版本,因为起初对于中断的不熟悉,没有直接在中断中直接写,而是只使用了中断产生的标注为来作为判断。

这个的设计思路主要是,A信号中断,消抖,确定A信号下降沿触发,打开定时器,10ms检测B信号是否上/下降沿触发,关闭定时器,判断B信号的电平高低。

软件设计流程图如下
在这里插入图片描述

在函数中实际代码如下

///****************旋转编码开关,版本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 <= 10)//定时器的一个周期是1ms,这里是10ms
				{
					if(B_flag == 1)//10ms内检测是不是有B上/下降沿触发
					{
						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;	

}

在main.c中的定时器的标志位设置,使用了TIM2定时器,溢出就+1

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{

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

}

在tim.c文件中TIM2的配置
在这里插入图片描述
TIM2的时钟输入是75MHZ,所以设置分频和计数分别为 750-1 和 100-1,这样的话一个时间周期就是1ms 频率是1000hz。

在stm32f4xx_hal_gpio.c文件中,我们找到外部中断对应的回调函数HAL_GPIO_EXTI_Callback,直接判断到外部电平触发后返回标志位就可以了。

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;
	}
}

这样写,虽然可以实现对于旋转编码器的检测,但是有一个问题,没有办法很方便的运用到实际工程中,以为进入到这个函数后才能进行编码器的判断,显然我们的编码器要实现的是一个翻页的功能,触发就要有操作的,而不是等着。

虽然可以设计进去超时函数让编码器跳出,但是还是没有办法实现实际项目的需要。于是准备直接写到中断回调函数中。

中断回调函数中实现

按理说直接写到中断回调函数应该挺容易的,直接改就行了 ,逻辑反正是通的,但是遇到了几个问题,一个是延时消抖的问题。

HAL_Delay本质也是一个中断服务函数,这种延时函数中断的嵌套是非常危险的操作,很容易卡死程序,比较有隐患,所以HAL_Delay函数是不能用了。

同时,因为回调函数是这样来使用的void EXTI15_10_IRQHandler(void)中检测到外部中断, 调用HAL_GPIO_EXTI_IRQHandler(GPIO_PIN);函数,然后再调用里面的回调函数void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)。

我们这个里面用到了两个外部中断,PE13 和 PE14,也就是都会使用同一个回调函数,也就是无法完成这种操作

if(GPIO_Pin == A_Pin)//A下降沿触发外部中断
{
   if(GPIO_Pin == B_Pin)
   {}
}

这里就是举了个例子,因为回调函数的调用逻辑,没有办法在检测了A信号触发后在操作里面检测B信号的触发。这是做不到的,这是回调函数限制了操作。为了避免这种,最好的方法还是直接写在void EXTI15_10_IRQHandler(void)函数中,HAL_GPIO_EXTI_IRQHandler(GPIO_PIN);函数和void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)都不使用,把他们实现的服务函数还有中断标志位清除操作全都直接写在AL_GPIO_EXTI_IRQHandler(GPIO_PIN);函数中,这个也就是我后面的一个方法。

回调函数中想要实现,可以采用这个方法
在这里插入图片描述


void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(GPIO_Pin);

		if(GPIO_Pin == A_Pin)//A下降沿触发外部中断
		{
//			printf("A下降沿触发\r\n");
			HAL_TIM_Base_Start_IT(&htim2);//开始TIM2定时器
			B_last = HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14);//记录此状态的B状态
			while(TIM2_flag <= 60)//定时器一个周期1ms,计时20ms内看看B有没有电跳变
			{
//				printf("等待B的触发\r\n");
				if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) != B_last)//在20ms内,检测到电平变化
				{ 
//					printf("B下降沿触发\r\n");
					HAL_TIM_Base_Stop_IT(&htim2);
//					printf("TIM2定时器关闭\r\n");
				  TIM2_flag = 0;
					if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)
					{
						printf("A\r\n");
						break;
					}	
					else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
					{
						printf("B\r\n");
						break;
					}
					break;
				}
			}
			HAL_TIM_Base_Stop_IT(&htim2);
			TIM2_flag = 0;
	
			
		}	
}

也就是相较于之前,去掉了消抖的函数,然后也不是检测B的边沿触发,而是判断B信号,在一个时间范围内,有没有发生电平的变化,直接检测B信号电平高低的变化,实现了一样的目的。

中断函数中实现

直接写在void EXTI15_10_IRQHandler(void);函数中无非就是多了步在中断触发之后需要手动清除中断标志位,其他都大同小异的思路,这里就可以检测A中断触发后,然后检测B中断触发,就不会出现什么问题了。

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

写在回调函数中的这些实验现象和问题,现在的话就都不存在了。

void EXTI15_10_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI15_10_IRQn 0 */

  /* USER CODE END EXTI15_10_IRQn 0 */
//  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
//  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_14);
//  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15);
  /* USER CODE BEGIN EXTI15_10_IRQn 1 */
	if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_13) != RESET) //A下降沿触发
	{
//		printf("A下降沿触发\r\n");
		__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_13);
		
		HAL_TIM_Base_Start_IT(&htim2);//开始TIM2定时器
		while(TIM2_flag <= 10)//定时器一个周期1ms,计时20ms内看看B有没有电跳变
		{
			if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_14) != RESET)
			{
//				printf("B下降沿触发\r\n");
				__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_14);
				HAL_TIM_Base_Stop_IT(&htim2);
//				printf("TIM2定时器关闭\r\n");
				TIM2_flag = 0;
				if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)
				{
					printf("A\r\n");
					break;
				}	
				else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
				{
					printf("B\r\n");
					break;
				}
				break;
			}
		}
		HAL_TIM_Base_Stop_IT(&htim2);
		TIM2_flag = 0;
		
	}
	
	if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_15) != RESET)
	{
		printf("SW按键\r\n");
	}

  /* USER CODE END EXTI15_10_IRQn 1 */
}
  • 7
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
您好!对于使用STM32CubeMX配置定时器驱动编码器电机转动,以下是一些基本的步骤: 1. 打开STM32CubeMX并创建一个新的工程。 2. 选择您的目标STM32微控制器型号,并设置所需的时钟配置。 3. 在"Pinout & Configuration"选项卡中,配置您的编码器引脚。通常,编码器包含两个信号线(A相和B相)和一个指示方向的线(Z相)。 4. 在"Configuration"选项卡中,选择并启用定时器定时器的选择取决于您的STM32微控制器型号和可用的定时器。 5. 在定时器配置中,启用编码器模式。选择正确的计数模式(例如,上升沿、下降沿或两者)和计数方向(正向或反向)。 6. 配置编码器模式的输入引脚。通常,A相信号连接到定时器的输入通道1,B相信号连接到输入通道2,Z相信号连接到输入通道3(如果有的话)。 7. 根据需要,设置定时器的预分频器和自动重载值。这将取决于您的应用要求和编码器的工作频率。 8. 生成代码,并将其导出到您的开发环境中。 9. 在您的代码中,使用生成的HAL库函数来初始化和启动定时器。您还可以使用HAL库函数来读取编码器的计数值。 请注意,以上步骤是基于使用STM32CubeMX和HAL库的方法。如果您使用其他开发工具或库,具体的配置步骤可能会有所不同。此外,还可以根据您的应用需求进行更高级的配置,如编码器计数模式、滤波器设置等。 希望这些信息对您有所帮助!如果您还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值