|
STM32CubeMX EC11旋转编码器普通IO口外部中断+定时器实现(防干扰电平) ( O6 h! q& k) t. q7 g: O
) w- g/ i" p6 M) v+ ?. ^) K, Z% L- K项目背景是在STM32平台上的普通IO口PE13 PE14使用外部中断+定时器实现,这里因为设计没有选择可以支持ENCODE MODE的端口。
5 j7 D% L" U6 y7 r) o( e- I/ [! U3 o+ o! {8 C# d1 Z9 J, p* N8 }
! Z2 V# L/ o; O! C1 r9 e; s5 s
EC11旋转编码器* O6 K6 b, V% w/ B
, M: G/ q3 d* Z! R1 U
; I9 w+ E7 G( W {: R h从这个数据手册中,我们可以设计出我们的思路,主要就是,以A信号作为一个时钟信号,也就是基准信号,检测到A之后,再去判断B的动作,一个相对的电平。7 a8 n5 `, Q( q/ h; ?# H
例如,当检测到A信号下降沿触发,检测B信号此时如果是高电平,那就是逆时针,如果是低电平,那就是顺时针。
4 ^, ~- v5 y, O& ^1 s8 q. l
( p; s9 z% R2 M. h
5 g3 Z+ m( F7 u- ///****************旋转编码开关,版本1*****************************/6 j3 P0 e- |% Y0 Q! R# _$ e
- uint8_t EC11Direction(void)7 O0 d4 T5 E# V( O$ g3 E
- {$ E* k3 X& q& s, W1 \8 O
- while(1)+ l/ ?/ D4 E' S" V6 M* k/ G
- {
8 D, @1 V- H7 K! `9 Z - if(A_flag == 1)//A下降沿触发外部中断,A_flag = 1
0 } U& A* f: l - {
0 o K2 R4 u, B. r5 Q+ [/ C; X9 ? - if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1) //检测B信号电平0 Q2 q* a! |; u2 ? ]
- {
' @! v* D0 q k - //// printf("正转\r\n");, f5 k& v% q1 Z5 _/ b
- Direction_flag = 1;
& n, ?! b7 C, S: O# }3 K9 b - break;
5 D K; q" \, i7 g5 p ]4 o9 u9 j3 D - }, E! }3 N3 @3 e/ U& F8 F. }; J
- else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)- w) ?- F4 f7 M/ b2 O _
- {4 Y: `, H% I, m+ G4 W; X$ W# n
- //// printf("反转\r\n");
6 M, r) x2 O, } - Direction_flag = 2;% L$ g' W" W1 ?$ D, N2 G
- break;: l) p: `, {7 m7 T
- }
: A" o! o/ ?) K0 |: ]" z - } 6 m4 ]! O7 w) x# g8 h
- return Direction_flag;
9 B3 `9 y! ~; ^5 a. A - 0 B4 ~4 _. q+ ~* X7 [
- }
复制代码 4 N4 Z; w7 n1 d- n
8 G0 P- Y5 R8 k% v1 d& Y6 F9 \
这个是最简单的判断方法,这个方法不是特别完善,容易出现干扰和误判断现象。不过整体是思路是这样走的。
7 ]6 {, P, M Z' v# v. J' b
3 j) t4 I* ~0 s* @2 N1 Y/ v" G
& w; F$ L9 d% ^" x中断标志位外部函数中实现# {1 d+ R" O: t* g; v( X
第一个实现版本,因为起初对于中断的不熟悉,没有直接在中断中直接写,而是只使用了中断产生的标注为来作为判断。, i- f" J- Q! g+ [; _& L5 h" c
这个的设计思路主要是,A信号中断,消抖,确定A信号下降沿触发,打开定时器,10ms检测B信号是否上/下降沿触发,关闭定时器,判断B信号的电平高低。
6 a% {1 }$ |( E/ _软件设计流程图如下
) B3 Q3 O/ c! l- z# H' X1 R$ A0 h- |
* h+ g6 r D) G' a! }
. i, ~+ @( h% Z7 o
t2 S" l' A# X6 `! |在函数中实际代码如下0 P+ i# F0 _& B4 t: H9 L l6 ^
8 U6 t: ^! A, M0 k5 S: s
; k' E8 A# h( S) f0 y- ///****************旋转编码开关,版本2*****************************/
" D3 c8 L: c+ q - ////返回值1 正转
5 n8 f% S+ m/ }: G" F8 r7 n2 b - ////返回值2 反转
' h! w8 g, o2 p3 h. o - uint8_t EC11Direction_2(void). v& r' q Z7 p
- {
0 N2 v9 \- ~' q4 B - char Direction_flag = 0;) j( @! u5 m8 P4 h/ ^ J
- while(1)6 R) u: G; ^ }$ ?* P8 O+ f" U
- {
3 O) v2 t( A& ]3 K( A - if(A_flag == 1)//A下降沿触发外部中断/ S5 z' V! y7 X2 s8 l7 W
- {2 u+ g2 t( h4 J" I
- HAL_Delay(1);//延时消抖( r: W+ H1 V$ m2 |, v3 b9 l
- if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_13) == 0)//A下降沿触发1ms后判断是否稳定在了低电平
" g. T- p1 g; i u: i) A - {
; ^% F) W2 d* @1 \1 L/ P5 v - HAL_TIM_Base_Start_IT(&htim2);//开启定时器! E; i- t- G/ [
- while(TIM2_flag <= 10)//定时器的一个周期是1ms,这里是10ms/ e1 {8 T* ~ m' X
- {6 U6 \! z# u9 u6 q
- if(B_flag == 1)//10ms内检测是不是有B上/下降沿触发/ ?7 u$ H8 m4 E- Q1 c. z# w4 N
- {
5 i* H, h7 B9 w - TIM2_flag = 0;//清除定时器中断标志位
1 x x& x1 v+ m: b* x& D - HAL_TIM_Base_Stop_IT(&htim2);//检测到B了直接关闭定时器( n! p9 z5 ?9 C! z
- HAL_Delay(1);//延时消抖# n+ y7 z) g: g6 B4 l6 ^1 T
- if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)//判断Pin_14的电平,返回旋转方向% U7 F$ R3 U3 E8 T ~
- {. d# N/ \$ W1 R9 A
- // printf("A\r\n");
+ d, [8 N/ Z# v- S9 e8 l8 B - Direction_flag = 1;3 \* o$ Z' [) w% J7 p% G% |, z
- break;8 c6 d5 @' V. J# L$ u
- }2 m- {' C- r8 `4 U" Y! D
- / u) a0 h8 K+ v
- else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)+ n+ N& @' s% U; r: o* @' C( Q
- {
# U% O @$ s3 J# I$ f$ q6 t" N - // printf("B\r\n");
2 F: E/ _1 z2 J - Direction_flag = 2;# r# S/ P0 q6 J8 z) I4 b' H
- break;
# c6 M6 }7 i( q - }( w/ e& O& M5 D- _& l T' I4 \% l
- }) [, [) J2 P" [0 k2 l
- }! Q' n* \& i2 j. D7 P
- HAL_TIM_Base_Stop_IT(&htim2);//定时器一个周期溢出后(TIM2_flag>1),关闭
! A& b' y3 X" j - TIM2_flag = 0;//清除定时器标志位
! ~$ K8 W1 U5 F6 A% D - }. I+ F3 l* M4 |7 X# B& M
- A_flag = 0;//清除A中断的标志位; v. G2 P9 i% A+ I) ^6 x
- } # D4 m8 L" \# u8 l/ {$ L! |
- # H7 [$ a5 Q. x% B. p
- if(Direction_flag == 1 | Direction_flag == 2)7 [# D! o) W g8 C- s3 L c8 G
- break;; z& t) D2 k7 Z1 u6 `& P( |
- }
; m. `+ k# w$ d0 L U7 h3 c - return Direction_flag;
7 {$ } _9 L C& h - ' O0 o9 k" [: q; K5 s
- }
复制代码
# M1 o5 o- F& Y3 l9 C$ V
4 n& Z+ [3 M- c* o1 `在main.c中的定时器的标志位设置,使用了TIM2定时器,溢出就+1
2 h4 w& D1 ?: B* S- p7 ~' M0 c
. g8 O' `3 q) ^6 ]' z1 m1 B
- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)7 e/ ?7 s/ K7 P9 R1 T# l
- {- j% [ n9 S3 h8 E- _, E6 @, p
" y9 }' y( F' v& w- if (htim->Instance == TIM2) + L7 ]3 d2 s% y. s# N1 _! M
- {7 ~- ], ^( B& x" p. t8 @0 a4 J
- HAL_IncTick();" u5 L( o! R) v7 d7 s' P8 T
- TIM2_flag++;
" u9 {* f, Y4 F) h, W1 ^- t - }
1 f3 l# g! f4 q8 X9 E - : s7 [% P, ^& U" f
- }
! Q( X* O3 o" ?$ i" t0 x9 {
复制代码
3 ^* F( K% I p. n- A; F- p' P, A! f$ _( `
在tim.c文件中TIM2的配置
& v8 p+ p; @' ^! q Q5 f9 Z e% A) {
" a+ F k% i% }7 q! ? L TIM2的时钟输入是75MHZ,所以设置分频和计数分别为 750-1 和 100-1,这样的话一个时间周期就是1ms 频率是1000hz。
- x3 T$ ~9 t) J% J$ M8 }# D在stm32f4xx_hal_gpio.c文件中,我们找到外部中断对应的回调函数HAL_GPIO_EXTI_Callback,直接判断到外部电平触发后返回标志位就可以了。
- |7 _8 w: `* K% L# r. s0 i: Y& a9 ]" `6 k0 e/ @* F9 \9 S* P
6 |) i. N' @% D" p! R8 Y% U
- void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
6 B) T" t$ D0 m- K' G# T$ K4 W/ W - {6 c* I" g! g1 ~ l! l
- /* Prevent unused argument(s) compilation warning */
, X8 K- i1 G0 k/ y4 a& @" a' X - UNUSED(GPIO_Pin);6 q, p0 C( U% G8 ^, b0 t
- /* NOTE: This function Should not be modified, when the callback is needed,
_8 y# r& n1 J8 a4 A0 _; ?2 v - the HAL_GPIO_EXTI_Callback could be implemented in the user file
: s8 |( F7 f, F9 x - */2 A! E% d1 M r/ {9 r$ |% i
- if(GPIO_Pin == A_Pin)
/ o# `" \" y) U. Z - {4 p5 n7 P/ M% R6 w+ p3 M) r
- A_flag = 1;
( j E, C9 K0 h4 @4 q0 Q - }
; H0 M% p9 Y( P - if(GPIO_Pin == B_Pin)
$ {) n% S! E# h# F: r0 D - {2 N4 l a. z" O
- B_flag = 1;* }. d% P2 I3 H2 H$ C, f8 O* l
- }
. D( {. @5 a6 k+ ^ - }
复制代码 ! ?1 \5 j8 `/ `) r, Q7 j$ r B7 W
& `- _! w* M4 {; \这样写,虽然可以实现对于旋转编码器的检测,但是有一个问题,没有办法很方便的运用到实际工程中,以为进入到这个函数后才能进行编码器的判断,显然我们的编码器要实现的是一个翻页的功能,触发就要有操作的,而不是等着。
1 n" S% t8 [: {2 D! ?8 }虽然可以设计进去超时函数让编码器跳出,但是还是没有办法实现实际项目的需要。于是准备直接写到中断回调函数中。: Y. F# ]# G9 q
: a. S4 }* w9 q( A* Q5 c, S( M3 M0 n& M
中断回调函数中实现) e$ b$ }( Y& g) E# r c& x" X# L
按理说直接写到中断回调函数应该挺容易的,直接改就行了 ,逻辑反正是通的,但是遇到了几个问题,一个是延时消抖的问题。) v% _2 ^- n6 V9 p$ I) p
HAL_Delay本质也是一个中断服务函数,这种延时函数中断的嵌套是非常危险的操作,很容易卡死程序,比较有隐患,所以HAL_Delay函数是不能用了。 K/ |7 ?* B) u* g" m9 t
同时,因为回调函数是这样来使用的void EXTI15_10_IRQHandler(void)中检测到外部中断, 调用HAL_GPIO_EXTI_IRQHandler(GPIO_PIN);函数,然后再调用里面的回调函数void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)。
" k8 M& g3 c) e5 _我们这个里面用到了两个外部中断,PE13 和 PE14,也就是都会使用同一个回调函数,也就是无法完成这种操作
2 p) `0 \# Q: y7 n$ I ^4 L& g: h; g4 c, W, e' h1 W$ v
$ }0 A7 H$ u- {, Y- \) @' ]7 ^
- if(GPIO_Pin == A_Pin)//A下降沿触发外部中断* W! ]" k4 x6 a' H& [6 ?
- {* {9 g% i* ]) b3 E5 K
- if(GPIO_Pin == B_Pin)
$ n, m; P9 E4 N$ L% \& i1 C - {}
. U& F- |6 I: Q# s' W! ^ - }
复制代码
9 R* J1 o$ x2 w8 g
- |) I# c2 m8 H5 x9 X( G9 x这里就是举了个例子,因为回调函数的调用逻辑,没有办法在检测了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);函数中,这个也就是我后面的一个方法。
- h. l& r8 i6 q1 Y1 P7 a! W! @: A回调函数中想要实现,可以采用这个方法
( M8 G7 b' w! R4 {: R3 X
- p3 j0 ?+ g% z+ _ _1 B. b9 ^ - void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)+ @# J; l0 w- I" \5 X1 x
- {- Y- P/ m9 @6 T1 u& ?7 ^2 f
- /* Prevent unused argument(s) compilation warning */
( N3 f- E$ M$ S; s - UNUSED(GPIO_Pin);8 o( D+ q1 B1 @% Y; K7 w
- & e' K5 i. `% ^# e1 S" ?; K' S
- if(GPIO_Pin == A_Pin)//A下降沿触发外部中断 m6 T" m9 O4 m3 ~6 C5 U
- {* |. V$ j4 X/ {5 [5 b' A! R
- // printf("A下降沿触发\r\n");
3 ?' F" J/ n$ c1 [% V& ]6 l - HAL_TIM_Base_Start_IT(&htim2);//开始TIM2定时器
- [4 G( B8 q \* ] - B_last = HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14);//记录此状态的B状态
, o G1 f/ p. ^5 I - while(TIM2_flag <= 60)//定时器一个周期1ms,计时20ms内看看B有没有电跳变
; |! I1 b$ B" N+ B6 J0 _ - {' E- y' `# g" H1 u
- // printf("等待B的触发\r\n");
8 c% @; _! g* x0 r0 ~4 w - if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) != B_last)//在20ms内,检测到电平变化 N- T7 O5 _5 j# L P- Q7 M; L
- { 6 g9 R, I! s/ K" G- E
- // printf("B下降沿触发\r\n");4 |7 ~0 N. P. o* |1 g3 T- l# F1 y
- HAL_TIM_Base_Stop_IT(&htim2);: |/ N; H" a" t, N3 z
- // printf("TIM2定时器关闭\r\n");" f; m) w1 w, V4 m; O
- TIM2_flag = 0;
3 N" x/ [. S2 N- @) K) z. P1 S w - if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)
0 V P5 f( N; ~* S& k) ~( B* O* b) R - {
* R6 e% a8 Y/ ^ E: S5 ~# \7 [ - printf("A\r\n");5 e/ G' @8 [6 d! C- S
- break;
1 m0 D+ ^: Q# I# Y - }
+ H, m& o5 ^- X5 b3 ?! v - else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)7 F/ ?- k" ]% U' z8 H/ s2 i
- {% \; i1 z# u& m" y% i
- printf("B\r\n");
; L/ y" |7 d" h# p6 X- U, \ - break;
3 Y3 _: {# ~, i |% J - }) U0 y2 b# w: a% O t8 D
- break;
! Z- p' @ S0 V) D/ v - }
8 F% |( e' o4 a$ R: G, m) ~ - }
2 a8 F( _4 V A' j& x7 A' { - HAL_TIM_Base_Stop_IT(&htim2);' }" C/ R# K' p0 Y# H( k, b; ]9 C
- TIM2_flag = 0;3 h: K" F9 R" T# ]% m
- 4 L/ Y \5 N4 C7 j9 D
-
7 y1 o/ u1 h: M - }
$ [0 P5 ^' i6 }# U+ K0 i - }
复制代码 % N1 I1 N: g% S
9 k3 a: }* [* r4 \. ]% z0 v1 i! _2 l
也就是相较于之前,去掉了消抖的函数,然后也不是检测B的边沿触发,而是判断B信号,在一个时间范围内,有没有发生电平的变化,直接检测B信号电平高低的变化,实现了一样的目的。1 W; Z) E V; j8 H8 |& M
# \5 t% y( N0 A$ X6 Z% ]
; W, Y. i: f4 l$ u0 b# w中断函数中实现
7 x$ @3 F* G/ S p( K直接写在void EXTI15_10_IRQHandler(void);函数中无非就是多了步在中断触发之后需要手动清除中断标志位,其他都大同小异的思路,这里就可以检测A中断触发后,然后检测B中断触发,就不会出现什么问题了。3 C% O |0 O; [
STM32CubeMX外部中断定时器嵌套问题及实验现象8 f x, g+ R. _: P" P8 g- K
写在回调函数中的这些实验现象和问题,现在的话就都不存在了。+ c( Z' W8 {$ T6 h$ N
* |% a1 `; Z+ z2 e- y) R2 Y2 V" s. g( o# U; N, m% Y# `+ {* \
- void EXTI15_10_IRQHandler(void)$ ]. |0 Q6 A5 J+ ~! G& K+ k
- {
1 g L8 C2 b& y5 a/ j - /* USER CODE BEGIN EXTI15_10_IRQn 0 */$ o$ r: r M, d& L
& d* K ]" b; W0 `* z( q' k+ k- /* USER CODE END EXTI15_10_IRQn 0 */7 }3 z- E) U. a; d3 R! S5 V
- // HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
- |! N6 z( H6 ~, H3 Q& {. ]" b, T - // HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_14);
6 |! ^" u% {1 C6 W" `* b, U( H' s* Z$ g. ^ - // HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15);
1 I _2 `/ J" U' u - /* USER CODE BEGIN EXTI15_10_IRQn 1 */
' c" z1 ~% T. X" f* ? - if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_13) != RESET) //A下降沿触发
- S) @3 v- T/ H8 L# Z; D - {
3 ]8 A* @- D+ o2 Q0 k* d - // printf("A下降沿触发\r\n"); k+ k" [5 b4 e
- __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_13);
' w) Y$ A& A, g B3 X" J -
+ s) _% L! S; O! }! M. G - HAL_TIM_Base_Start_IT(&htim2);//开始TIM2定时器* ~/ ^, \, n! p; r8 c: A e
- while(TIM2_flag <= 10)//定时器一个周期1ms,计时20ms内看看B有没有电跳变
5 s- K5 b2 i$ _( C - {1 Y& d; p% Z/ p4 {6 o
- if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_14) != RESET)8 S5 `$ v4 |7 t
- {
3 Z, D$ D; t& _7 D; E8 C$ Q6 ~. j8 c - // printf("B下降沿触发\r\n");
/ q. A$ |8 Q# F5 o; o. a, V; W7 I6 P* Y - __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_14);6 s( ~. x+ ~6 \7 q
- HAL_TIM_Base_Stop_IT(&htim2);
9 d! I* X: @7 A6 J+ Y; l3 ] - // printf("TIM2定时器关闭\r\n");
( O# h3 s( N- I- i - TIM2_flag = 0;. _) O _: {9 L
- if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)
) s& w5 ?- z9 C% R- C6 [: P - {
1 i$ z) G! O' b; W2 N/ `, u - printf("A\r\n");; C, V6 ~! {# Q/ K; l
- break;
2 t, c/ Z: o/ E1 w4 r+ n - } : z; p5 Q. w i/ }/ _ Z
- else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
R8 U4 B) x5 r7 }( a1 W1 Q+ P! p - {
8 y. ]+ K- S" g: ?# I' H - printf("B\r\n");$ W& E7 n: m1 s5 S# ~
- break;
7 {" d& P2 E' m- A6 u - }5 Z5 n5 `6 b' t+ ?+ _* d4 |9 n$ w1 a
- break;
; x7 W5 ~* r4 l& u - }4 p" L3 |- z; ]6 L7 c- n
- }) |' y/ x3 |& F1 M
- HAL_TIM_Base_Stop_IT(&htim2);
; E6 t b" D+ E - TIM2_flag = 0;9 L3 r9 v2 f* @0 p
- . R# r% v) `! A- {
- }
; r0 [9 V" W5 o0 ~& [/ E2 L - 1 e/ s3 ~* z: ~) U4 g2 E' N7 J
- if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_15) != RESET)" F! _ B7 @" Z& v7 [
- {, G# Z$ c L7 k
- printf("SW按键\r\n");
: l" M# H9 a: B, r: R" Z - }- C* T# P; c9 e7 T7 z
- & Q: k# M% G( E- E
- /* USER CODE END EXTI15_10_IRQn 1 */, t3 ^; F' _( ~: \1 k+ @# D
- }
复制代码
& Z7 b1 B. y* ?6 n9 r$ U0 [) Q8 R9 ]. z h4 Y' w
3 L3 {, z' ]3 p3 K, G5 S
6 c9 ]0 R, T( O3 z
% E! l4 G5 E3 ^# { |
这个确实复杂了,大佬有机会做个分享
大佬有机会做个分享
我记得我以前搞过,貌似没有你这么复杂,而且程序实现也比较简单可靠,
不需要定时器,也不需要中断,普通的 io 口就可以了8 E0 ]. Q& `; \, G
现在程序一时忘记放在哪里了,有空我找找