你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

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

[复制链接]
STMCU 发布时间:2020-10-20 15:41
STM32CubeMX EC11旋转编码器普通IO口外部中断+定时器实现(防干扰电平)


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


EC11旋转编码器
11.png
12.png

从这个数据手册中,我们可以设计出我们的思路,主要就是,以A信号作为一个时钟信号,也就是基准信号,检测到A之后,再去判断B的动作,一个相对的电平。
例如,当检测到A信号下降沿触发,检测B信号此时如果是高电平,那就是逆时针,如果是低电平,那就是顺时针。


  1. ///****************旋转编码开关,版本1*****************************/
  2. uint8_t EC11Direction(void)
  3. {
  4.         while(1)
  5.         {
  6.                 if(A_flag == 1)//A下降沿触发外部中断,A_flag = 1
  7.           {
  8.                   if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1) //检测B信号电平
  9.                         {
  10. ////                                printf("正转\r\n");
  11.                                 Direction_flag = 1;
  12.                                 break;
  13.                         }
  14.                   else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
  15.                         {
  16. ////                                printf("反转\r\n");
  17.                                 Direction_flag = 2;
  18.                                 break;
  19.                         }          
  20.           }       
  21.   return Direction_flag;       

  22. }
复制代码


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


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

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

13.png

在函数中实际代码如下



  1. ///****************旋转编码开关,版本2*****************************/
  2. ////返回值1 正转
  3. ////返回值2 反转
  4. uint8_t EC11Direction_2(void)
  5. {
  6.         char Direction_flag = 0;
  7.         while(1)
  8.         {
  9.                 if(A_flag == 1)//A下降沿触发外部中断
  10.                 {
  11.                         HAL_Delay(1);//延时消抖
  12.                         if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_13) == 0)//A下降沿触发1ms后判断是否稳定在了低电平
  13.                         {
  14.                                 HAL_TIM_Base_Start_IT(&htim2);//开启定时器
  15.                                 while(TIM2_flag <= 10)//定时器的一个周期是1ms,这里是10ms
  16.                                 {
  17.                                         if(B_flag == 1)//10ms内检测是不是有B上/下降沿触发
  18.                                         {
  19.                                                 TIM2_flag = 0;//清除定时器中断标志位
  20.                                             HAL_TIM_Base_Stop_IT(&htim2);//检测到B了直接关闭定时器
  21.                                                 HAL_Delay(1);//延时消抖
  22.                                                 if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)//判断Pin_14的电平,返回旋转方向
  23.                                                 {
  24. //                                                        printf("A\r\n");
  25.                                                     Direction_flag = 1;
  26.                                                         break;
  27.                                                 }
  28.                                                        
  29.                                                 else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
  30.                                                 {
  31. //                                                        printf("B\r\n");
  32.                                                         Direction_flag = 2;
  33.                                                         break;
  34.                                                 }
  35.                                         }
  36.                                 }
  37.                                 HAL_TIM_Base_Stop_IT(&htim2);//定时器一个周期溢出后(TIM2_flag>1),关闭
  38.                                 TIM2_flag = 0;//清除定时器标志位
  39.                         }
  40.                         A_flag = 0;//清除A中断的标志位
  41.                 }       
  42.                
  43.                 if(Direction_flag == 1 | Direction_flag == 2)
  44.                         break;
  45.         }
  46. return Direction_flag;       

  47. }
复制代码


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

  1. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
  2. {

  3.         if (htim->Instance == TIM2)
  4.         {
  5.         HAL_IncTick();
  6.                 TIM2_flag++;
  7.   }

  8. }
复制代码


在tim.c文件中TIM2的配置
14.png
TIM2的时钟输入是75MHZ,所以设置分频和计数分别为 750-1 和 100-1,这样的话一个时间周期就是1ms 频率是1000hz。
在stm32f4xx_hal_gpio.c文件中,我们找到外部中断对应的回调函数HAL_GPIO_EXTI_Callback,直接判断到外部电平触发后返回标志位就可以了。


  1. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
  2. {
  3.   /* Prevent unused argument(s) compilation warning */
  4.   UNUSED(GPIO_Pin);
  5.   /* NOTE: This function Should not be modified, when the callback is needed,
  6.            the HAL_GPIO_EXTI_Callback could be implemented in the user file
  7.    */
  8.         if(GPIO_Pin == A_Pin)
  9.         {
  10.                 A_flag = 1;
  11.         }
  12.         if(GPIO_Pin == B_Pin)
  13.         {
  14.                 B_flag = 1;
  15.         }
  16. }
复制代码


这样写,虽然可以实现对于旋转编码器的检测,但是有一个问题,没有办法很方便的运用到实际工程中,以为进入到这个函数后才能进行编码器的判断,显然我们的编码器要实现的是一个翻页的功能,触发就要有操作的,而不是等着。
虽然可以设计进去超时函数让编码器跳出,但是还是没有办法实现实际项目的需要。于是准备直接写到中断回调函数中。


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

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,也就是都会使用同一个回调函数,也就是无法完成这种操作


  1. if(GPIO_Pin == A_Pin)//A下降沿触发外部中断
  2. {
  3.    if(GPIO_Pin == B_Pin)
  4.    {}
  5. }
复制代码


这里就是举了个例子,因为回调函数的调用逻辑,没有办法在检测了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);函数中,这个也就是我后面的一个方法。
回调函数中想要实现,可以采用这个方法
15.png
  1. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
  2. {
  3.   /* Prevent unused argument(s) compilation warning */
  4.   UNUSED(GPIO_Pin);

  5.                 if(GPIO_Pin == A_Pin)//A下降沿触发外部中断
  6.                 {
  7. //                        printf("A下降沿触发\r\n");
  8.                         HAL_TIM_Base_Start_IT(&htim2);//开始TIM2定时器
  9.                         B_last = HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14);//记录此状态的B状态
  10.                         while(TIM2_flag <= 60)//定时器一个周期1ms,计时20ms内看看B有没有电跳变
  11.                         {
  12. //                                printf("等待B的触发\r\n");
  13.                                 if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) != B_last)//在20ms内,检测到电平变化
  14.                                 {
  15. //                                        printf("B下降沿触发\r\n");
  16.                                         HAL_TIM_Base_Stop_IT(&htim2);
  17. //                                        printf("TIM2定时器关闭\r\n");
  18.                                   TIM2_flag = 0;
  19.                                         if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)
  20.                                         {
  21.                                                 printf("A\r\n");
  22.                                                 break;
  23.                                         }       
  24.                                         else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
  25.                                         {
  26.                                                 printf("B\r\n");
  27.                                                 break;
  28.                                         }
  29.                                         break;
  30.                                 }
  31.                         }
  32.                         HAL_TIM_Base_Stop_IT(&htim2);
  33.                         TIM2_flag = 0;
  34.        
  35.                        
  36.                 }       
  37. }
复制代码


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


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

STM32CubeMX外部中断定时器嵌套问题及实验现象
写在回调函数中的这些实验现象和问题,现在的话就都不存在了。


  1. void EXTI15_10_IRQHandler(void)
  2. {
  3.   /* USER CODE BEGIN EXTI15_10_IRQn 0 */

  4.   /* USER CODE END EXTI15_10_IRQn 0 */
  5. //  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
  6. //  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_14);
  7. //  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15);
  8.   /* USER CODE BEGIN EXTI15_10_IRQn 1 */
  9.         if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_13) != RESET) //A下降沿触发
  10.         {
  11. //                printf("A下降沿触发\r\n");
  12.                 __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_13);
  13.                
  14.                 HAL_TIM_Base_Start_IT(&htim2);//开始TIM2定时器
  15.                 while(TIM2_flag <= 10)//定时器一个周期1ms,计时20ms内看看B有没有电跳变
  16.                 {
  17.                         if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_14) != RESET)
  18.                         {
  19. //                                printf("B下降沿触发\r\n");
  20.                                 __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_14);
  21.                                 HAL_TIM_Base_Stop_IT(&htim2);
  22. //                                printf("TIM2定时器关闭\r\n");
  23.                                 TIM2_flag = 0;
  24.                                 if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)
  25.                                 {
  26.                                         printf("A\r\n");
  27.                                         break;
  28.                                 }       
  29.                                 else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
  30.                                 {
  31.                                         printf("B\r\n");
  32.                                         break;
  33.                                 }
  34.                                 break;
  35.                         }
  36.                 }
  37.                 HAL_TIM_Base_Stop_IT(&htim2);
  38.                 TIM2_flag = 0;
  39.                
  40.         }
  41.        
  42.         if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_15) != RESET)
  43.         {
  44.                 printf("SW按键\r\n");
  45.         }

  46.   /* USER CODE END EXTI15_10_IRQn 1 */
  47. }
复制代码





收藏 评论6 发布时间:2020-10-20 15:41

举报

6个回答
乐天乐 回答时间:2020-10-20 18:08:19
通用
jtb1111 回答时间:2020-10-27 15:45:37
你这个太麻烦了,当有下降沿触发时直接检测两路信号同是低电平就能判断左右旋转。
ts2000 回答时间:2020-10-28 10:07:16
代码又长又臭,其实很简单就可以实现了
STMCU 回答时间:2020-10-29 09:11:53
ts2000 发表于 2020-10-28 10:07
代码又长又臭,其实很简单就可以实现了

这个确实复杂了,大佬有机会做个分享
STMCU 回答时间:2020-10-29 09:12:11
jtb1111 发表于 2020-10-27 15:45
你这个太麻烦了,当有下降沿触发时直接检测两路信号同是低电平就能判断左右旋转。 ...

大佬有机会做个分享
hpdell 回答时间:2020-10-29 16:20:57
STMCU 发表于 2020-10-29 09:12
大佬有机会做个分享

我记得我以前搞过,貌似没有你这么复杂,而且程序实现也比较简单可靠,
不需要定时器,也不需要中断,普通的 io 口就可以了
现在程序一时忘记放在哪里了,有空我找找

所属标签

相似分享

官方最新资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32Cube扩展软件包
意法半导体边缘AI套件
ST - 理想汽车豪华SUV案例
ST意法半导体智能家居案例
STM32 ARM Cortex 32位微控制器
关注我们
st-img 微信公众号
st-img 手机版