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

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个回答
goyhuan 回答时间:2020-10-20 18:08:19
通用
jtb1111 回答时间:2020-10-27 15:45:37
你这个太麻烦了,当有下降沿触发时直接检测两路信号同是低电平就能判断左右旋转。
小小超 回答时间: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管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版