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

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

[复制链接]
STMCU-管管 发布时间:2020-9-16 13:13
项目背景是在STM32平台上的 普通IO口PE13 PE14使用外部中断+定时器实现,这里因为设计没有选择可以支持 ENCODE MODE的端口。
# n! ~6 L8 k" J0 h( |

; k( t/ q5 z& Y. p7 R9 r( M
$ ^( v: g' @  o0 F
EC11旋转编码器
4 d/ p( h- H& N- ~/ Z4 w
  @+ e8 n+ G' T4 r
20200916130829.65952b82dc2654750351984d50b3748e.png

9 y! E  P( z  u  B( Z
9 G% Y/ E4 `( B
2 ]2 Y3 ?! y  Z- V
20200916130841.4b1622a2ed1a56c1b6f24e5769f81f91.png

. J6 Z  a/ ?" }  m# W& {9 ~. A; o" \0 T
2 D8 i6 d) x" W# k! z
从这个数据手册中,我们可以设计出我们的思路,主要就是,以A信号作为一个时钟信号,也就是基准信号,检测到A之后,再去判断B的动作,一个相对的电平。8 E# v- v* j% B1 F' X+ S5 c0 T


5 \/ g7 U+ G2 R5 e7 ]9 a

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

$ h5 A, f) F+ C  y5 I

  1. ///****************旋转编码开关,版本1*****************************/9 E9 T3 \* A/ q9 h
  2. uint8_t EC11Direction(void)
    5 i6 \& y1 `+ b) J9 [; U" f6 P
  3. {
    # j. ?' f  e5 J5 G1 y4 d
  4.         while(1)
    7 {# X& q8 q7 n! ^, k
  5.         {
    0 N" W3 n% q# M0 B
  6.                 if(A_flag == 1)//A下降沿触发外部中断,A_flag = 1; M4 d. h) P! p( A
  7.           {+ b: D/ j( k  r+ [
  8.                   if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1) //检测B信号电平
    / C  d& B4 L% V6 ]; U! q  @4 X
  9.                         {
    * N0 |, o! |$ F7 m: b, _( `
  10.                                 printf("正转\r\n");7 Q1 y( ^% z8 o% g: J% o
  11.                                 Direction_flag = 1;
    5 U# w6 c1 N$ G+ Z' ]" ~% _4 l
  12.                                 break;( ^! j0 m8 I, |; x& {6 k" z4 A
  13.                         }
    ; [, E' G0 T- y) _5 g
  14.                   else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
    - E$ W+ e* R' H! N- Q; H  d
  15.                         {5 q! \) ~/ b, t
  16.                                 printf("反转\r\n");6 t3 |7 O- G3 v5 [: j. f2 [, r
  17.                                 Direction_flag = 2;4 o* K  n& ]- _) }5 `8 ^- o
  18.                                 break;
      H1 }# D* M- `* U- r% D
  19.                         }          $ p  i$ E- K4 q$ D5 \) D5 C# _
  20.           }        ( u6 T% ]1 W7 j; r& R
  21.   return Direction_flag;        ' k1 _  t2 H2 X. b3 _/ h% P% X0 Z
  22. ' J  g$ |" k7 F" Y7 x
  23. }
复制代码
0 q) v! V, x/ v$ N- V

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

& k+ b+ s, r7 H* }0 K& P, H, _, x

中断标志位外部函数中实现
* u# |8 f2 U. S

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

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

; d3 O- o8 ^+ Z& N( `' V

软件设计流程图如下

7 w: J# y  ^- ~; S

20200916130850.72f286e352a4e1e1a84420e87205ce9f.png

/ T- p. p& q1 H( \) p* y# g* T7 x0 g1 L3 G) Z- j# t5 h+ ]

) T) x; j# l. b

在函数中实际代码如下


3 M6 H2 ^3 K5 v0 b  j

  1. ///****************旋转编码开关,版本2*****************************/
    % B% p/ Y' e  E8 l' m4 D0 ]8 Q
  2. 返回值1 正转
    0 c6 k* q5 ]+ d, A3 Y, \
  3. 返回值2 反转
    / L( Z5 l# X' E3 d
  4. uint8_t EC11Direction_2(void)
    " u( m+ @  D& x7 I, ^6 i# H
  5. {
    9 I# A4 ~) y/ ~% I8 A
  6.         char Direction_flag = 0;
      F6 i! m9 {0 R4 ]( C" Q1 X, ~5 Z
  7.         while(1)2 Z* Z& E; n$ ?7 u
  8.         {
    , S3 `- \0 `* o# {  y
  9.                 if(A_flag == 1)//A下降沿触发外部中断
    ( e# i# p2 u/ q
  10.                 {
    . x3 V: r+ z+ H
  11.                         HAL_Delay(1);//延时消抖
    : A$ q- p9 ~; B% G% u
  12.                         if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_13) == 0)//A下降沿触发1ms后判断是否稳定在了低电平
    1 c5 Y) y1 q/ ?8 A& d5 z: D7 C
  13.                         {! c6 I7 g6 O9 P1 D& ^9 A$ c2 Z
  14.                                 HAL_TIM_Base_Start_IT(&htim2);//开启定时器: F, I& H- T. A8 r! {
  15.                                 while(TIM2_flag <= 10)//定时器的一个周期是1ms,这里是10ms  I$ ?! Z9 r0 M4 O& M0 h# I
  16.                                 {9 z$ x- Z5 g* B- w' {
  17.                                         if(B_flag == 1)//10ms内检测是不是有B上/下降沿触发1 u  W- o6 H; K) g4 y! P
  18.                                         {0 t+ I& f" G6 m) ~! F, S% L
  19.                                                 TIM2_flag = 0;//清除定时器中断标志位
    . G: k1 M  q3 `+ T" h- f6 ?7 ?* [
  20.                                             HAL_TIM_Base_Stop_IT(&htim2);//检测到B了直接关闭定时器
    ! c- H, u7 I- s% w( L8 q) Q
  21.                                                 HAL_Delay(1);//延时消抖* {2 H. Z/ ?( s# j$ r7 D8 \
  22.                                                 if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)//判断Pin_14的电平,返回旋转方向; B+ C4 |$ M* ]2 v1 u' [7 Q# C
  23.                                                 {; l6 N, ~& C7 t/ O
  24. //                                                        printf("A\r\n");
    + o2 r- D0 x- z' i  z0 S8 d6 f/ {
  25.                                                     Direction_flag = 1;
    % ~8 I/ f( O$ B" E. G. G8 s
  26.                                                         break;
    6 o- _- Y$ [% {' ^3 F
  27.                                                 }
      g$ J$ W6 h, ?. z( m0 Y( P
  28.                                                         . `; _# O( n4 u  M3 H) b0 ^; _; H
  29.                                                 else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)& l& r2 z& b7 O  f
  30.                                                 {/ {, S) G6 J& V5 u6 C
  31. //                                                        printf("B\r\n");( d( F3 y) M1 K6 `" J1 R1 _" i
  32.                                                         Direction_flag = 2;
    8 z3 I! f+ q- j9 A
  33.                                                         break;5 J9 W' d, w5 N* v7 e
  34.                                                 }, i5 W$ F* s1 {- }) a
  35.                                         }  s3 K9 I) \3 ]- m1 i" ~* j
  36.                                 }
    # u* H8 N! o& e
  37.                                 HAL_TIM_Base_Stop_IT(&htim2);//定时器一个周期溢出后(TIM2_flag>1),关闭
    , ~0 P" H/ b7 W" m$ J/ Z9 B" w& b
  38.                                 TIM2_flag = 0;//清除定时器标志位
    " ^. A: Z7 M# G4 R! `7 x) B" a+ j
  39.                         }; O/ ^) B: f! S0 I6 y% l# n# O
  40.                         A_flag = 0;//清除A中断的标志位. s4 Q* u: x, x$ P
  41.                 }        * I/ d) J, `  t
  42.                 5 R7 B/ @+ ]4 |
  43.                 if(Direction_flag == 1 | Direction_flag == 2)+ q9 B$ i. N, J' F
  44.                         break;/ G4 g0 ~8 a. H$ s; D5 P$ P! S
  45.         }
    2 E* f7 w" R6 b. a& \' v2 R
  46. return Direction_flag;        $ V0 a6 Y: t4 w$ N# k

  47. 5 o3 N. n, l6 ?/ e
  48. }
复制代码

* @1 f. Z) I) n& S+ c


" P* |: |  j" T# I6 t$ g6 Q- B) H

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

" }& h. ~2 A: \, L; N" ]

  |+ Z0 N/ Q7 p$ {/ K
  1. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    & [% G1 J: g1 g# u9 _$ }
  2. {# q( M! M+ X% g$ ^; u) e

  3. 0 W( h2 G6 S: E2 j7 O& B! S2 l
  4.         if (htim->Instance == TIM2) & `) B3 j4 A) R) A1 u
  5.         {
    2 y- e/ v4 U, e' j' ]
  6.         HAL_IncTick();
    : O4 S8 U. d" T0 \, L
  7.                 TIM2_flag++;
    , @1 C( N  [9 j" V! r' B0 d
  8.   }
    5 c7 C4 P, p3 U0 V! J8 m6 S

  9. 3 g, F$ g! T! C/ ~- E$ s
  10. }, a, ?" d  M5 F7 P. ^- ~( K' x- h" M0 h
复制代码
: U6 y) B9 w5 T

在tim.c文件中TIM2的配置

5 c' y' v! q- K% a' ]$ N

20200916130859.97b950058a7e3e9f7f324a32a8401021.png
1 L7 [# f7 |& q- Y3 c* j1 U6 W7 R; Q

' Z& f/ l4 Y. f# u) e9 h
TIM2的时钟输入是75MHZ,所以设置分频和计数分别为 750-1 和 100-1,这样的话一个时间周期就是1ms 频率是1000hz。7 o. @- a& V6 C0 k; b5 b

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

+ A' l4 z2 N6 v  i

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


* o; y$ E: X8 N$ _/ u

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


1 v7 \0 N% f' M8 T7 Z

中断回调函数中实现# M+ z# `" ^- I: v0 Z, k

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

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


% _# b5 c1 Q: X4 L* V+ c2 Q; v6 C# |

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


9 N9 F. m7 A0 U' h: t

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

8 @& G) t, z0 s$ H; l! U) P
9 s6 c: |. Y# `& _! U6 Q

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

回调函数中想要实现,可以采用这个方法$ M# s5 R- f  c! t$ E

20200916130916.bd9c04b99b78d32d531ca96e46870abb.png
+ {; N* |1 F$ \8 A5 v5 V

5 m' _7 S- \% Z" D3 d: ?& `
2 T& Q3 w: m# Z7 r7 L9 r
' i$ M# }% n" m9 b

  1. & R/ ^) e5 r- i5 V- b5 c: s
  2. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    + B& }+ v7 a' p1 ~
  3. {
    . x, s: C" H, c1 ]2 D6 l8 F
  4.   /* Prevent unused argument(s) compilation warning *// e% J( F& R- V3 m3 B
  5.   UNUSED(GPIO_Pin);. ~* |+ k, x/ X1 r% K

  6. # U$ f* a( a4 b+ P, Q
  7.                 if(GPIO_Pin == A_Pin)//A下降沿触发外部中断6 b5 I2 f# {" n, T5 G
  8.                 {
      W" P/ ?7 z& S# i1 ^% F: \6 ^+ u* y
  9. //                        printf("A下降沿触发\r\n");
    - h9 m' h& B4 N; n7 p0 K$ p
  10.                         HAL_TIM_Base_Start_IT(&htim2);//开始TIM2定时器
    , {0 V! u& r$ n% o! b( y
  11.                         B_last = HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14);//记录此状态的B状态3 e4 `  P$ {: v' f
  12.                         while(TIM2_flag <= 60)//定时器一个周期1ms,计时20ms内看看B有没有电跳变
    $ N7 ]+ e/ U$ w; m" j/ K
  13.                         {8 [" X3 B; y. I, u# k' J
  14. //                                printf("等待B的触发\r\n");
    ' E+ S2 s5 Q1 u& T7 a1 D: B
  15.                                 if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) != B_last)//在20ms内,检测到电平变化! Z7 n( v  p+ L& T5 ]; K
  16.                                 {
    + R6 D) p4 z& X; u, b# ~
  17. //                                        printf("B下降沿触发\r\n");
    7 P2 P4 u. s4 P6 P" r2 e3 i" _/ J
  18.                                         HAL_TIM_Base_Stop_IT(&htim2);9 Q7 }0 P! |' [$ H7 O2 }
  19. //                                        printf("TIM2定时器关闭\r\n");
    % C" ?. ^& w! H9 r4 I) l9 {, f
  20.                                   TIM2_flag = 0;1 }0 F3 |5 }2 [
  21.                                         if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)
    & n  P# L* T' N" V$ @7 I, Q
  22.                                         {5 v4 h8 u& d* t2 P) m
  23.                                                 printf("A\r\n");
    " Z% m% l7 B+ k. O
  24.                                                 break;2 i4 O+ M- P5 k7 |2 t% ~3 S
  25.                                         }        
    , o# Q: R% W4 Z8 G" x; y
  26.                                         else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
      }4 s+ ^5 Q+ d( j" x
  27.                                         {* C1 p8 q4 J, ~. X
  28.                                                 printf("B\r\n");
    & {* H% t" ~  i/ a7 Q4 g1 c
  29.                                                 break;
    + t: ^: u" R. g" z; R
  30.                                         }
    8 d+ q2 }% _: J* K# A
  31.                                         break;
    + N  {3 i& K. e4 P! i; L' c2 G9 j
  32.                                 }; {  P* p( Y' [4 e0 u4 U
  33.                         }
    - B8 o' E: J: Y& ^
  34.                         HAL_TIM_Base_Stop_IT(&htim2);
    + v+ u% v" x6 o: y8 _
  35.                         TIM2_flag = 0;4 e8 e% _; _6 o, Z' H% n
  36.         * Q1 a, t& X; b- b1 |6 `0 E6 R9 r
  37.                         " l, f. L& V0 |) S, y8 F* v, Q
  38.                 }        1 ^: I: a5 E1 M- x3 m; e
  39. }
复制代码
: ~! v2 F- @7 [) s4 q" {

8 H) E; L+ X. v) T6 l+ u0 z  w  S# S

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


+ X0 S$ n+ B9 B! a$ I- h

中断函数中实现
  I7 M8 [+ Y2 W& m1 `

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


6 }7 v  {$ g5 f( D3 U

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

+ {  n1 w. P' `/ T6 Z$ a7 w

% w( ^7 p2 W$ w% x" D- M3 R
  1. void EXTI15_10_IRQHandler(void)
    9 T0 _4 b/ J' ^% f3 z, D9 T& W
  2. {+ q( h! D8 M! Y
  3.   /* USER CODE BEGIN EXTI15_10_IRQn 0 */
    3 y7 z) G- g1 F/ c
  4. - U  r) T( M! \  Q8 @8 {7 i6 M
  5.   /* USER CODE END EXTI15_10_IRQn 0 */
    ' |' c7 V/ ]- Q7 {: \
  6. //  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
    ! Y  J( S6 w6 P' g" n
  7. //  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_14);
    * r3 O3 e: {. C" ?
  8. //  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15);/ ~/ J  f' w% _. f9 `, Z
  9.   /* USER CODE BEGIN EXTI15_10_IRQn 1 */
    6 F3 G! K1 X9 v- {# [
  10.         if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_13) != RESET) //A下降沿触发
    ; m( N0 v- t5 n8 p. N: @( y2 D
  11.         {
    ' ?+ o: P2 P4 c9 R- J7 J
  12. //                printf("A下降沿触发\r\n");" y7 T0 M4 R/ _" V. n
  13.                 __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_13);" m( S* k) B, V- H& M* {/ S
  14.                
    ; f4 k1 U0 W4 m4 W
  15.                 HAL_TIM_Base_Start_IT(&htim2);//开始TIM2定时器
    . B* c9 r7 N5 @: O
  16.                 while(TIM2_flag <= 10)//定时器一个周期1ms,计时20ms内看看B有没有电跳变1 I" i5 E  Y! M* k: Q" U8 [
  17.                 {
    8 i' E# Y' Y( z0 M3 d
  18.                         if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_14) != RESET)
    : [4 e+ K& e7 N+ G6 o' T8 J- \: f
  19.                         {
    & `# ^$ I/ d" l. u% b& l1 F9 A
  20. //                                printf("B下降沿触发\r\n");
    5 l3 q- |6 Q. G" U* u6 R" r
  21.                                 __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_14);6 }/ T4 |5 D- y5 I4 ]( K
  22.                                 HAL_TIM_Base_Stop_IT(&htim2);/ R9 r9 s7 Y/ Y# Y; d7 l
  23. //                                printf("TIM2定时器关闭\r\n");. H4 R. C& t0 Y/ N
  24.                                 TIM2_flag = 0;! I, o, x$ t& w& o+ r
  25.                                 if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)
    3 h2 w. ?' p9 k9 M  Q7 ]2 D8 {9 g
  26.                                 {2 Q. }7 d4 d8 t
  27.                                         printf("A\r\n");+ W$ ?2 ~# M2 V8 o
  28.                                         break;6 y+ w+ ?# R: }6 ^" T
  29.                                 }          U: u( L& v/ R- \
  30.                                 else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
    8 h1 `' X5 d7 x. U/ x( J
  31.                                 {
      z9 J1 B! u& l5 S7 R# n) c* F
  32.                                         printf("B\r\n");) A+ D5 L( U0 x- M# Z4 H
  33.                                         break;
    / d: `( z; |: O) B" a% g$ U
  34.                                 }
    8 p/ e/ D2 P3 H: K2 l
  35.                                 break;
    * m3 o: _# X7 k& I: H( w* i7 A
  36.                         }4 i. _- j" q) Y" P7 M! w
  37.                 }
    $ u- I9 y0 g4 o$ I" [& O8 ]
  38.                 HAL_TIM_Base_Stop_IT(&htim2);
    % z; `9 I" c+ @6 @/ s! d
  39.                 TIM2_flag = 0;. O- y7 W3 H1 F
  40.                
    # r/ s& U$ d- w
  41.         }/ S6 |2 a5 ]$ d! E
  42.         
      O( e/ l: q+ f( j5 O% w# g
  43.         if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_15) != RESET): O8 W0 M" [- o% `, g0 J% J
  44.         {: Q# |1 X! x' N+ x
  45.                 printf("SW按键\r\n");/ y+ C% G' w4 u" }1 b2 L6 Z
  46.         }/ B, Y4 V' g; Y( F" Q$ m; k9 C4 i7 W
  47.   l# H- x+ M. P. J7 l
  48.   /* USER CODE END EXTI15_10_IRQn 1 */
    ! U9 `- G+ W2 B4 F, p. `& t
  49. }
复制代码

! \# j' Y. R  p
' I9 \0 i; J$ ]* Y; M4 B. Q( {7 x& F2 L6 |" f
/ Y' _. f3 L7 P. s
收藏 评论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
2 }$ u" Q7 U7 Q+ R- y; j有几个错别字

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