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

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

[复制链接]
STMCU-管管 发布时间:2020-9-16 13:13
项目背景是在STM32平台上的 普通IO口PE13 PE14使用外部中断+定时器实现,这里因为设计没有选择可以支持 ENCODE MODE的端口。4 u) F5 d( O. y8 X
! A8 L  v" ?' B

# N) h/ X( z7 i, [# v: d
EC11旋转编码器

0 L/ G5 {( u; o: G9 u8 D  S! l% M, _% U- p5 P
20200916130829.65952b82dc2654750351984d50b3748e.png

/ C; C; `+ g& O: s; x+ |
0 l' y2 G3 @- Y' _' y/ u' V+ \5 B5 \; U; M/ p5 s
20200916130841.4b1622a2ed1a56c1b6f24e5769f81f91.png

' Y9 I% m  ^4 i! E# S; t* |
& v, O1 i6 M. @6 d% D) ?% k
* f' g+ C( T' d  x9 n: z从这个数据手册中,我们可以设计出我们的思路,主要就是,以A信号作为一个时钟信号,也就是基准信号,检测到A之后,再去判断B的动作,一个相对的电平。: m' y& l, w) d) Y

( h1 T2 J" S2 Z/ J4 b. D

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


( H/ N* c  R6 R6 I, D9 s" A

  1. ///****************旋转编码开关,版本1*****************************/
    * E# ?) `8 V+ U, Y
  2. uint8_t EC11Direction(void)' @9 w( A- y" _! S2 R5 a
  3. {( b# V* O9 E" J1 c# V, B
  4.         while(1)
    0 O* b2 J) Q3 z3 K  u
  5.         {' M" ^$ A$ j# B% L
  6.                 if(A_flag == 1)//A下降沿触发外部中断,A_flag = 1, ?/ m/ [/ u; T, ?
  7.           {
    & N0 U* B4 C' \
  8.                   if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1) //检测B信号电平. s1 |8 N. }% H) |- q, M
  9.                         {
    # f! K, X+ q7 o" r9 f& O; a
  10.                                 printf("正转\r\n");. \  B0 s% X3 G; m+ I; m! V
  11.                                 Direction_flag = 1;7 ]1 C7 o* Y9 e4 n$ Y0 S8 p& I/ h
  12.                                 break;
    ( M" U  x  v0 B6 ?& A
  13.                         }- x3 `8 |% X8 m: [. n; |/ ^( r
  14.                   else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0); e4 Y, y7 A" q5 z
  15.                         {
    3 {% ^, Z& H% U1 |
  16.                                 printf("反转\r\n");
    * \( P  y( Y1 |, M0 D( c1 k2 c
  17.                                 Direction_flag = 2;" O' a& I3 w9 S3 c1 X/ B
  18.                                 break;& k4 l. o# S7 G2 w# Y0 H
  19.                         }         
    : i" U; M* x& @- X0 R
  20.           }        
    6 D6 t3 _, u! s) Q- i7 z+ P8 [
  21.   return Direction_flag;        
    9 ?; U  H2 W3 W4 O2 Y& h8 w
  22. 8 ~2 v  H, a" c" m4 x
  23. }
复制代码

$ M6 o* b5 \. R9 c& z

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


4 m- L! b; N- J

中断标志位外部函数中实现4 ?: n. q1 r% G( {

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

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


! a+ t/ }" b5 ?8 W" S) H

软件设计流程图如下

" l$ j' u5 U0 l

20200916130850.72f286e352a4e1e1a84420e87205ce9f.png
0 m# k% |- b8 y0 ]
* X. j+ G: z2 E' ]* E; q

% y+ o8 w+ z+ R" `" `8 O1 N3 A

在函数中实际代码如下


+ j# s! U" {! Z2 A' ~# p

  1. ///****************旋转编码开关,版本2*****************************/
    0 C- i% s- U; b: u  n3 d+ t
  2. 返回值1 正转- }# ^, u) W! |* Y1 ^
  3. 返回值2 反转$ x2 E( P$ P  w  Z# t2 y9 a
  4. uint8_t EC11Direction_2(void)
    : ?, Q% o9 V( t! ?- N
  5. {
    3 \. K0 p, N9 n0 W
  6.         char Direction_flag = 0;- v: {; ?* d6 ?4 r& I, q( J
  7.         while(1)
    . a- p; q. Z# W2 l# h; F& |
  8.         {/ V5 g& w+ E8 N+ G# |6 H  c
  9.                 if(A_flag == 1)//A下降沿触发外部中断' a9 K2 ^9 d. {
  10.                 {
    5 R: u" X- D" N6 T0 u
  11.                         HAL_Delay(1);//延时消抖
    5 M" R6 m+ ~! ~8 c! c4 E& H: p
  12.                         if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_13) == 0)//A下降沿触发1ms后判断是否稳定在了低电平
    ; a: i" Z$ c$ ]6 D( t  Z
  13.                         {/ O) \9 @* F( |. m
  14.                                 HAL_TIM_Base_Start_IT(&htim2);//开启定时器9 U* \; i- R: x; |4 U9 o- h, l
  15.                                 while(TIM2_flag <= 10)//定时器的一个周期是1ms,这里是10ms$ [  x  h" W8 v
  16.                                 {7 m8 V9 k4 }9 z2 ?/ B- P9 y
  17.                                         if(B_flag == 1)//10ms内检测是不是有B上/下降沿触发
    9 C+ R0 f2 g: o
  18.                                         {
    3 M5 T" r6 w' o3 X, _4 Y- O
  19.                                                 TIM2_flag = 0;//清除定时器中断标志位
    2 ~( T8 x3 l) \/ d6 G  P8 ~5 Y
  20.                                             HAL_TIM_Base_Stop_IT(&htim2);//检测到B了直接关闭定时器
    8 W3 P/ Q( c- ]% A# @4 z  P
  21.                                                 HAL_Delay(1);//延时消抖9 Q1 `  k7 Z5 X( f  U
  22.                                                 if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)//判断Pin_14的电平,返回旋转方向$ N7 o$ q7 A2 k
  23.                                                 {
    $ g! d# z. X' u7 T2 x/ [
  24. //                                                        printf("A\r\n");  v2 }9 j; s" J' z
  25.                                                     Direction_flag = 1;
    3 f4 P  q  {- k; J5 d
  26.                                                         break;& C; L1 E5 A# I+ z* _4 F
  27.                                                 }
    6 q1 ?# ]+ |+ r
  28.                                                         
    3 p; z6 V7 x3 v, \% r9 G8 }
  29.                                                 else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)' w2 O; r* u2 K% y
  30.                                                 {
    # d" E  H  S  R
  31. //                                                        printf("B\r\n");
    , H7 w9 |: M5 E( x4 S8 r: P
  32.                                                         Direction_flag = 2;
    ; V2 r& _, b3 D* s$ S8 ~
  33.                                                         break;0 o' }. h' U5 ^$ X7 p- l5 m
  34.                                                 }
    ) O. c& l+ k" B5 g
  35.                                         }
    0 E; X) C7 V3 a$ u, X
  36.                                 }
    : @$ X1 f- ?) u: G5 C, g
  37.                                 HAL_TIM_Base_Stop_IT(&htim2);//定时器一个周期溢出后(TIM2_flag>1),关闭0 g  k. ]7 {  }
  38.                                 TIM2_flag = 0;//清除定时器标志位
    , Q' i0 d' {. ?
  39.                         }
    , @9 D" E, d% [) w' R
  40.                         A_flag = 0;//清除A中断的标志位4 p) Z  K4 J8 i$ c. W8 d& y
  41.                 }        
    + C' \8 t/ {: z- T- B0 Y
  42.                
    . l* P  I* O; A- o& [8 [, N+ m
  43.                 if(Direction_flag == 1 | Direction_flag == 2)& M4 L0 b) |/ f- \: \* b6 z
  44.                         break;
    : {- B% @/ _2 a- G7 i
  45.         }9 p' Z5 m" n/ u
  46. return Direction_flag;        
    $ P7 Y) |; D" b

  47. " g8 z5 F( C  ~
  48. }
复制代码
% a9 z9 L* s$ J$ d


4 t: Y2 P. s9 U1 _: X

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

2 V. n; X5 W3 [9 o1 R

8 p. ?) G! Y  p( D; k; {0 I
  1. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    4 H1 m0 {" o) V4 w8 Q6 Q
  2. {
    6 Q) E" C* o6 Q7 j; V0 D- T

  3. + n- ^( I7 u" Y
  4.         if (htim->Instance == TIM2) ) |2 \2 S6 l# C8 ?
  5.         {+ q8 r; K; F8 W7 l0 S% c
  6.         HAL_IncTick();9 t* R4 q4 F4 h9 B# d! b, H+ T
  7.                 TIM2_flag++;
    2 j( U! c& Z! u0 r1 W9 \  t% c0 Q
  8.   }, k7 j9 F* v/ X* r
  9. 9 R/ x' K' T. v3 R. T; ~$ e8 q
  10. }+ o( m9 \- e4 _8 ^! F5 ]6 u
复制代码

1 U1 `# Y2 `. ?, U

在tim.c文件中TIM2的配置

* y8 J; n2 ^' C7 }1 d

20200916130859.97b950058a7e3e9f7f324a32a8401021.png
, t- }% ~4 T5 J% C: s1 H

( p/ N( k2 w+ ~/ o7 D
TIM2的时钟输入是75MHZ,所以设置分频和计数分别为 750-1 和 100-1,这样的话一个时间周期就是1ms 频率是1000hz。" R; p8 b3 ^. X( C4 E

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


, C  ]( R# f: J5 @; ^( I

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

# v/ f7 S0 I$ ?. h) r" T

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


, {$ @  u, G$ o( T" G

中断回调函数中实现* l* h) P  f9 w& T

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

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


% i1 q0 y+ P! g2 @

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


4 }* @/ x. m8 s. l, E

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


. p# L) @) l0 q/ Q" k5 d9 @) u) ]% V* r" @- D3 |) b

这里就是举了个例子,因为回调函数的调用逻辑,没有办法在检测了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);函数中,这个也就是我后面的一个方法。

回调函数中想要实现,可以采用这个方法. q# m& Q! ]$ s( _) _

20200916130916.bd9c04b99b78d32d531ca96e46870abb.png

. a8 U& M* s) F) Q. _
  E* w4 e& G( v( {5 J. @  ]4 M* f5 C; B0 M+ O( ?# g. ^8 \/ Z

( t7 ]% [7 w1 X# ]* p

  1. 2 X1 e5 K/ q0 ]( c$ @
  2. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)) o' P9 o0 t& `8 @8 j
  3. {: p5 F+ e2 @0 h' n* T
  4.   /* Prevent unused argument(s) compilation warning */' Z4 Q0 _: X- u% D* T0 m
  5.   UNUSED(GPIO_Pin);: S1 P+ O- r1 O( I

  6. 6 j) z2 k1 C! M5 @- @
  7.                 if(GPIO_Pin == A_Pin)//A下降沿触发外部中断" J& z6 F$ y% x, {2 v# y; q
  8.                 {
    2 h& e) r4 Q) W7 T- S
  9. //                        printf("A下降沿触发\r\n");( q$ I, c( k# }
  10.                         HAL_TIM_Base_Start_IT(&htim2);//开始TIM2定时器
    * Q6 G6 j4 o6 T+ R' O* Z! ]
  11.                         B_last = HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14);//记录此状态的B状态
    . P% Y$ x+ k/ [) M) M# s, Z, ~6 X
  12.                         while(TIM2_flag <= 60)//定时器一个周期1ms,计时20ms内看看B有没有电跳变
    * l9 S/ G4 O7 T$ X: `3 k' L
  13.                         {8 b2 z/ P1 \4 j/ ]0 Y
  14. //                                printf("等待B的触发\r\n");5 X" L, i- u; B' }7 {6 I0 z/ [
  15.                                 if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) != B_last)//在20ms内,检测到电平变化- ?) n  ]( b3 @: \' S
  16.                                 {
    # W. M' H$ l% ]8 o- j
  17. //                                        printf("B下降沿触发\r\n");# E( ?7 z4 V) o$ M4 R% m1 k  E
  18.                                         HAL_TIM_Base_Stop_IT(&htim2);  H9 M4 m: F: v, [4 r
  19. //                                        printf("TIM2定时器关闭\r\n");0 e% \/ y0 ^9 F( V- r
  20.                                   TIM2_flag = 0;
    9 @9 S# U" X% i& w+ k( J
  21.                                         if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)* F$ U+ C1 m6 U
  22.                                         {
    # o" D# C: h- O+ \
  23.                                                 printf("A\r\n");" a: j! n& i: h8 W* ?% g! J2 g
  24.                                                 break;& S) k$ G" d- P: l( i' b3 m( S, u
  25.                                         }        
    / Y5 O( ]5 p1 H- h: e; A% j, Q
  26.                                         else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)8 L8 G* F$ X; K( q+ ^+ Q
  27.                                         {& V' Q" C" {( y7 n4 @( W
  28.                                                 printf("B\r\n");
    2 q, P' _0 L: p$ }
  29.                                                 break;. d- e" x5 [2 r
  30.                                         }
    3 c" m  ~; [: J3 E! z" I3 l
  31.                                         break;
    ( F, w& a! J0 r% A
  32.                                 }" B) k/ n2 h: Y3 J/ [+ l, v
  33.                         }  o9 I- |: r; s8 h% X
  34.                         HAL_TIM_Base_Stop_IT(&htim2);
    ( b/ Y! |" O5 z0 A
  35.                         TIM2_flag = 0;$ f6 B" x# [! j3 }8 R$ v* _# P8 {
  36.         
      N/ ]5 S" r/ [  Q9 j
  37.                         
    $ W6 u; o( u* H9 q! a6 ^7 ?
  38.                 }        
    7 ]& d  q% j) F4 b
  39. }
复制代码

; ]) B2 ~# J- \4 G3 L3 j* o1 |9 K3 K! Y& P& N( S

7 m2 e" e) s; C2 B

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


6 Z- z% u9 n1 o" C6 h+ X: x

中断函数中实现) D3 j) a- N* l

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


- i. p  G, S& i/ u8 J/ x

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


; ^$ A+ I( u) u% [7 L' }9 G
, T% ?/ i% Z8 S5 _9 ?, x/ ^$ s
  1. void EXTI15_10_IRQHandler(void)' I* ^7 }  ]. t  u
  2. {
    4 T/ |) F" j5 E5 t6 O/ e6 s
  3.   /* USER CODE BEGIN EXTI15_10_IRQn 0 */% N6 X3 Z5 z% D) X6 q
  4. - B6 W6 ~0 G# f$ x$ k4 _6 {
  5.   /* USER CODE END EXTI15_10_IRQn 0 */( \# p' q/ t2 H" @
  6. //  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);% R' q+ v  D0 [! d+ o
  7. //  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_14);
      N1 a# h2 \. l  R! o1 s3 |
  8. //  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15);
    ) W  `1 k! d6 q2 q4 M
  9.   /* USER CODE BEGIN EXTI15_10_IRQn 1 */: K2 f+ P# M  [- E
  10.         if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_13) != RESET) //A下降沿触发
    / Z0 @  H/ D8 v
  11.         {8 x, H, w/ v9 M0 H/ p8 k
  12. //                printf("A下降沿触发\r\n");; o6 @  k. q/ I; k, V$ O5 Z5 {
  13.                 __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_13);) s; E+ c) |4 V
  14.                 2 L/ i, A5 M' x$ P) l# D( x! E/ {
  15.                 HAL_TIM_Base_Start_IT(&htim2);//开始TIM2定时器
    ) z, w: {, z4 @: I2 `: M4 ~
  16.                 while(TIM2_flag <= 10)//定时器一个周期1ms,计时20ms内看看B有没有电跳变4 F4 _. }" B1 x2 j
  17.                 {4 M4 ~& V: I4 {# {
  18.                         if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_14) != RESET)/ G! t; x2 Z  J2 @8 {
  19.                         {7 O  H) U  d0 U
  20. //                                printf("B下降沿触发\r\n");, `. b+ R' n9 h# p9 T
  21.                                 __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_14);
    * R! S' o3 _$ @. t
  22.                                 HAL_TIM_Base_Stop_IT(&htim2);
      [' Z7 P. S2 B7 x6 z" C; G
  23. //                                printf("TIM2定时器关闭\r\n");) _* n6 K. M( k6 d4 p/ y4 s' B! y
  24.                                 TIM2_flag = 0;$ U/ Y0 Q" H* u5 J" p
  25.                                 if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)
    * b6 T; |4 K1 \" v
  26.                                 {. h6 x' [7 I- P5 l6 t
  27.                                         printf("A\r\n");# Z# U( Z. ~: Z0 s7 f! e2 t+ y* p
  28.                                         break;) l* @, v7 Q3 |4 ~4 l0 m- q0 o
  29.                                 }        , V0 h  h- E: f: [4 Y- ~, O
  30.                                 else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)! V- S) Q# g3 X' [4 \0 c
  31.                                 {
    7 u! @3 R) N, \
  32.                                         printf("B\r\n");
    : Y; }/ Q2 N( D8 x  f. ]7 c: I+ c
  33.                                         break;
    . o# ?# f0 P& M3 {
  34.                                 }7 I" ^& Q% C; \: _
  35.                                 break;; g2 ^+ b" `- q$ L2 l
  36.                         }8 U7 V9 [1 V3 I" P
  37.                 }* N6 }1 D3 G2 r; z: G
  38.                 HAL_TIM_Base_Stop_IT(&htim2);
    1 r2 B. [# O" e5 c
  39.                 TIM2_flag = 0;5 h5 t. w% E; [# R( }  f4 l3 F
  40.                 ' z: H7 N8 R+ }# x
  41.         }
    # y0 @1 H# k# A( n- s& T0 J9 l9 E
  42.         
    & B4 Z: p8 N7 R2 x6 c
  43.         if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_15) != RESET)
    ; B: M$ P% W) t7 A
  44.         {: r- q: S8 _7 E; N! I+ k& Q
  45.                 printf("SW按键\r\n");
    % `0 A# i' k+ u8 D8 i# Z
  46.         }7 r; n! H' b8 e- D  S

  47. * B! N2 h) c9 B! O
  48.   /* USER CODE END EXTI15_10_IRQn 1 */3 {5 g; Y: ^% t/ f% j$ F
  49. }
复制代码

( J  r( ^2 r' C6 G) M6 R
1 Q- K2 m+ ?! P
+ O# S2 A9 j* x, l' [9 @! D6 `; f/ y+ |
收藏 评论3 发布时间:2020-9-16 13:13

举报

3个回答
goyhuan 回答时间:2020-9-16 13:53:43
有几个错别字
STMCU-管管 回答时间:2020-9-16 16:09:22
乐天乐 发表于 2020-9-16 13:53
0 a$ z2 P/ H$ t+ u/ Z% C$ G! o有几个错别字

4 X, q+ Q7 E9 Z* n8 G2 x我看看哈,那个地方啊,我来改
goyhuan 回答时间:2020-9-16 16:51:56
总觉得有点不顺口
e2.jpg
e1.jpg
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版