STM32CubeMX EC11旋转编码器普通IO口外部中断+定时器实现(防干扰电平)
, a7 j* ?, n& i) b, x- Q. A0 D
3 F* z, O. [: O1 W4 s9 t" o/ B项目背景是在STM32平台上的普通IO口PE13 PE14使用外部中断+定时器实现,这里因为设计没有选择可以支持ENCODE MODE的端口。0 t1 ~/ y! x" x; [: C
; u" |0 n8 o, i% o1 e& u& [6 K; ?
EC11旋转编码器
: P3 Z; @1 {" |
" q- L# P# v4 A& _5 V0 H
+ e( D, O& N* B. l从这个数据手册中,我们可以设计出我们的思路,主要就是,以A信号作为一个时钟信号,也就是基准信号,检测到A之后,再去判断B的动作,一个相对的电平。
. y0 Z5 Q# M3 d' x7 F例如,当检测到A信号下降沿触发,检测B信号此时如果是高电平,那就是逆时针,如果是低电平,那就是顺时针。
[4 R: t4 R4 e* v2 k4 y% ^0 u4 M/ ? P
3 ?; S2 R0 A$ [
- ///****************旋转编码开关,版本1*****************************/3 |3 A- A P& s$ t: Y# G, \
- uint8_t EC11Direction(void)1 t$ L( C) \! Y7 D; C( z- W! |
- {
# Y/ a; l6 c& S4 `" T - while(1)
+ Z2 S, |) B* Z - {# _6 h- p7 O# G, l
- if(A_flag == 1)//A下降沿触发外部中断,A_flag = 17 t6 i8 R8 p7 j- {7 k* v
- {6 o8 w: w1 P3 g- h5 Y6 X# k3 x
- if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1) //检测B信号电平7 R# B. b8 m# h
- {8 W, e7 h* P$ f4 z4 B
- //// printf("正转\r\n");
! C. C4 |$ r- U% K9 T' u$ }" X - Direction_flag = 1;/ d3 @# ]. q' H$ b& Y I. N
- break;
1 j+ Z1 |5 p) u - }& b Z, L* w# h# H/ J6 d1 F
- else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)! F, _8 s7 E; T
- {8 l5 r# }: l0 o
- //// printf("反转\r\n");4 L; u% j8 B2 X* }; b* H# M
- Direction_flag = 2;
7 n' }0 ^9 F& E) u" z N- J. g7 P - break;
, ?& w$ J7 E5 | \$ z& D - } . ~& Y' x) }) g3 m# V
- }
) a: s f5 H- { - return Direction_flag; $ w# w& J4 K, G
- % p' v* c* L/ _3 s
- }
复制代码
# ]: A8 o j# C) E8 q
5 W8 C9 F$ C! H0 z9 z4 s' E这个是最简单的判断方法,这个方法不是特别完善,容易出现干扰和误判断现象。不过整体是思路是这样走的。
`$ z# i5 U! }2 D: N1 w6 F% y1 `0 [6 q; e
* ~+ {9 g4 v6 i+ K% u1 Q中断标志位外部函数中实现* f- _& ~7 p1 ]8 N+ o6 \( K
第一个实现版本,因为起初对于中断的不熟悉,没有直接在中断中直接写,而是只使用了中断产生的标注为来作为判断。6 w2 E$ ]1 v0 F- ^$ g
这个的设计思路主要是,A信号中断,消抖,确定A信号下降沿触发,打开定时器,10ms检测B信号是否上/下降沿触发,关闭定时器,判断B信号的电平高低。6 j3 v+ E: J7 i9 v
软件设计流程图如下- N; z0 a7 H5 I
# C5 j& @9 b1 ~
, q7 v) R) J2 X8 O7 D 4 f6 Z7 n9 `& i3 B
在函数中实际代码如下
5 A# U: w: o* e/ q, T! T4 B6 R6 x# j! ^% }
/ j6 L8 T2 w; P" } Y8 j9 g+ r
- ///****************旋转编码开关,版本2*****************************/
3 E; D$ }2 w- c+ H- F; ]2 y; o; A$ b! I - ////返回值1 正转9 k- w4 {! G: r0 [4 w
- ////返回值2 反转
2 x: ]$ Y5 M; z& j - uint8_t EC11Direction_2(void); c: e+ M% f' ~8 H
- {
! Y: m$ j: U: n9 `$ { - char Direction_flag = 0;
) A' b: u9 R6 i- } - while(1)
( q# S: B3 X; S$ m* @# f: `' j - {& z4 i F$ a+ t
- if(A_flag == 1)//A下降沿触发外部中断
" U8 Z- L! t t - {6 g5 u/ G. R1 T2 y1 ^
- HAL_Delay(1);//延时消抖( D. w3 [1 y. z% U9 Q" s5 ~; M. @$ l
- if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_13) == 0)//A下降沿触发1ms后判断是否稳定在了低电平4 {& ~, G) s" l
- {% f3 x! H4 h( Z9 t( P3 a
- HAL_TIM_Base_Start_IT(&htim2);//开启定时器
5 {9 h z3 b; U! G' T0 I) B - while(TIM2_flag <= 10)//定时器的一个周期是1ms,这里是10ms1 o- x, V# V" ]
- {1 ^+ a. s, g7 ~2 a) x3 v7 E6 u* Y! u
- if(B_flag == 1)//10ms内检测是不是有B上/下降沿触发3 k) B; m+ n+ X& u/ }5 l7 q! p0 v
- {
7 j$ x8 d+ q: a1 r: Y7 { - TIM2_flag = 0;//清除定时器中断标志位/ U, |0 u: z3 w2 o- C8 O: c, _
- HAL_TIM_Base_Stop_IT(&htim2);//检测到B了直接关闭定时器
3 Z. X5 C" c" e - HAL_Delay(1);//延时消抖2 V2 e g) ]# N* h/ g
- if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)//判断Pin_14的电平,返回旋转方向) W! L" c0 H# |( G r+ Y6 [% F7 d S
- {2 l* K, J; f9 n" L5 A
- // printf("A\r\n");! Q, A. ]- ]- r" z* _
- Direction_flag = 1;5 \9 l+ h/ n+ j0 F. h* g+ y
- break;
+ L: ?/ [' U& R8 t, D, x" b - }
) @6 v1 L. I8 O/ X7 m - 8 P+ N/ p- C% D3 O- d$ c
- else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
( V1 K* E5 w: x$ X0 @ - {3 i9 F, s' W6 ~1 `7 x6 F& I y
- // printf("B\r\n");* J% F( Q5 H" g3 V/ i* {- I+ X& j
- Direction_flag = 2;9 z" _* D1 b; @+ a
- break;- v1 O+ S( \0 @) m* p8 ]
- }
/ R. T% Z" H2 a. E$ L - }: y, A8 M6 M( @% A' f# ~
- }) d% O4 J6 L+ e( Q. v6 m
- HAL_TIM_Base_Stop_IT(&htim2);//定时器一个周期溢出后(TIM2_flag>1),关闭
6 ?; V% F; }& y; i$ \ - TIM2_flag = 0;//清除定时器标志位4 Y6 a* P; `1 g0 h3 | w& J* p- g* ^! p
- }& ^% l3 P6 V4 F& p2 d* m8 J
- A_flag = 0;//清除A中断的标志位
: K! @& O' t) @- D- Z7 q6 ^ - } ( l/ | c( Q/ O% r" ^5 h
-
. R! J& m8 l+ F0 P3 T t - if(Direction_flag == 1 | Direction_flag == 2)
0 w+ T1 M9 s9 {0 ^ - break;9 ?" H7 i, ^' J: l) Z" Q
- }
) I9 k$ {0 U+ {; V# K - return Direction_flag; / s/ S: W" E4 N; k
8 Q6 O& m# t! T- }
复制代码 " ^, a" y! ?/ t& s8 r# l0 C6 \0 O
- q6 G6 ^0 y$ W) L; ~0 G在main.c中的定时器的标志位设置,使用了TIM2定时器,溢出就+1
" o0 k4 i/ ?: V; y" Y9 q- a* x. s+ {- c. m# C! q5 E: v$ c0 M
- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)! `& ^- B5 U% s- w$ ^% C
- {
9 j: W4 [- R& Z! K
# [# O$ q- r z, R7 O- if (htim->Instance == TIM2) / K9 ^+ |& p( m
- {8 p+ [ X8 J7 O0 J, L2 ]1 w# P
- HAL_IncTick();
; R% A& f) A+ @( `: F - TIM2_flag++;3 T% J( ? Z# F7 ^4 R) y P; E5 F( K
- }
/ l" S0 F- o: y% `% F" c - 2 w' R7 m" X e9 c
- }# p# f* Z8 _+ [
复制代码
4 ^; x7 z/ [& ]$ t
) _) q& e& Q$ M9 ]5 R+ r# x3 N) r# N在tim.c文件中TIM2的配置6 ~4 G$ W2 s/ U; E; P6 o* x
7 ~. [/ |5 y3 x+ O; C# l TIM2的时钟输入是75MHZ,所以设置分频和计数分别为 750-1 和 100-1,这样的话一个时间周期就是1ms 频率是1000hz。
0 j* Z, C$ H" w* t在stm32f4xx_hal_gpio.c文件中,我们找到外部中断对应的回调函数HAL_GPIO_EXTI_Callback,直接判断到外部电平触发后返回标志位就可以了。
5 ~" {. o2 \7 S# k% b, I
9 o# l- `- |3 m; V# s; [7 e) a2 O0 |
- void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
( l0 Q D, J6 h - {
: y( k! C: H/ A; |* k - /* Prevent unused argument(s) compilation warning */
" @- T1 G( S! @" l3 u2 G - UNUSED(GPIO_Pin);* }! H/ C2 g5 r* |* i
- /* NOTE: This function Should not be modified, when the callback is needed,, {* J4 N9 h* r* f. l3 }. l9 M
- the HAL_GPIO_EXTI_Callback could be implemented in the user file
0 o$ |2 S2 k5 a - */
( V% h- W7 L6 J, {6 _! b( ^# \4 G - if(GPIO_Pin == A_Pin)
- Q! T) `; C A1 N* q! {6 X - {4 Y7 y) t3 x4 H& U6 w
- A_flag = 1;1 f1 s* [5 y% O! f+ S
- }8 i! H% F& J' P7 U8 x4 N- K9 @$ X
- if(GPIO_Pin == B_Pin). n/ H: f& R& {, c- F* i6 j
- {. M8 f$ h1 N# U d9 v
- B_flag = 1;8 ?* L# T/ C: |3 U
- } X# `+ o- T- `6 r! w0 y5 P1 X
- }
复制代码
$ A+ f6 L7 R* k; o$ _9 m$ ^, x8 \0 V% ~- _+ z& J& b) i
这样写,虽然可以实现对于旋转编码器的检测,但是有一个问题,没有办法很方便的运用到实际工程中,以为进入到这个函数后才能进行编码器的判断,显然我们的编码器要实现的是一个翻页的功能,触发就要有操作的,而不是等着。
% U: }/ |! J' S6 t8 {' Y* v6 ~' _虽然可以设计进去超时函数让编码器跳出,但是还是没有办法实现实际项目的需要。于是准备直接写到中断回调函数中。6 }% r) p" S: O6 \4 ?
4 W% O8 G. D& ^+ O2 B% i/ p- S" {& Q8 r4 \/ U
中断回调函数中实现
& H e7 f7 @# i; W0 @按理说直接写到中断回调函数应该挺容易的,直接改就行了 ,逻辑反正是通的,但是遇到了几个问题,一个是延时消抖的问题。. R# m0 l' d: N! W
HAL_Delay本质也是一个中断服务函数,这种延时函数中断的嵌套是非常危险的操作,很容易卡死程序,比较有隐患,所以HAL_Delay函数是不能用了。; I9 j# ?2 t4 b6 }7 H% O7 { ^# q
同时,因为回调函数是这样来使用的void EXTI15_10_IRQHandler(void)中检测到外部中断, 调用HAL_GPIO_EXTI_IRQHandler(GPIO_PIN);函数,然后再调用里面的回调函数void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)。; _! c/ I$ Z5 p# _% w( ?1 R" M: I
我们这个里面用到了两个外部中断,PE13 和 PE14,也就是都会使用同一个回调函数,也就是无法完成这种操作
3 U- C2 p4 S3 E3 V% U, n% f3 v' ~, E0 i' y% r+ e
w' F" R4 z: N0 b
- if(GPIO_Pin == A_Pin)//A下降沿触发外部中断
# J" _5 U( H6 d - {, E: Q3 O: |7 d/ E
- if(GPIO_Pin == B_Pin)
: x" v! Y9 s6 o) L - {}
7 P) @& K6 P/ j2 ~* _1 B ? - }
复制代码
" x4 k5 _% c- f; o9 V& b
6 i$ j+ _' L) M/ R2 l2 g这里就是举了个例子,因为回调函数的调用逻辑,没有办法在检测了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);函数中,这个也就是我后面的一个方法。
7 [9 w' u' t$ P- q! f7 i' |) T- U回调函数中想要实现,可以采用这个方法; x# m8 C: n; c/ i
6 N) b f& D; a' }( L" v+ i
- void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
% T6 w9 J* }0 H2 @+ U* k% E) K - {
3 J" K3 G% j" K [# P( L - /* Prevent unused argument(s) compilation warning */1 I2 c6 T3 q$ C$ D$ E7 r. _, @
- UNUSED(GPIO_Pin);
: P! L( l: f. p) K/ D% }
, I' U' P# ~% H7 h7 G/ @/ z- u- if(GPIO_Pin == A_Pin)//A下降沿触发外部中断
& }6 V. h2 B1 ]" C - {# x% i8 D) J5 h. D
- // printf("A下降沿触发\r\n");
9 x! f4 [, R3 F6 x9 `+ V+ s - HAL_TIM_Base_Start_IT(&htim2);//开始TIM2定时器: x! X$ _& W7 _1 z
- B_last = HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14);//记录此状态的B状态" `* H% l* h$ J
- while(TIM2_flag <= 60)//定时器一个周期1ms,计时20ms内看看B有没有电跳变. `6 N: G1 J6 J& [8 p3 d
- {
3 s9 e* |$ N1 Z$ y% K: r - // printf("等待B的触发\r\n");1 [6 X+ F( g4 V; T' h3 a/ |
- if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) != B_last)//在20ms内,检测到电平变化
; H) m; h& [ I- B/ A* { - {
+ C; I/ U; q+ w* I8 b. ` - // printf("B下降沿触发\r\n");4 z+ m; h* ]' Z
- HAL_TIM_Base_Stop_IT(&htim2);) q$ o' e: q! }5 x
- // printf("TIM2定时器关闭\r\n");
" Q2 E7 Z$ m) t8 D" r - TIM2_flag = 0;
, ~- V6 Z) m" i* r! B' F - if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)
3 z- W+ G" H& _' k! x( J - {
3 C3 N% Z) ~6 h4 G- |1 X; Q* ?2 P; T - printf("A\r\n");
: j5 D% C0 ^' [ - break;
" w/ j$ L" M* \; |8 n, Y6 J - }
1 Q+ }- e# u1 t: W5 `1 ]$ k - else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)4 M/ y. p) [ @, @/ ]; s9 {
- {
* \$ E: C, e& z2 R/ _0 T - printf("B\r\n");
& E3 S" {+ W. x1 O; E5 E - break;
3 [; {+ X& _9 ^1 r3 o) ~ - }) f9 M# S" s- m! I) ^; `
- break;- h5 P. F9 H% _3 f# _$ ~
- }
" ~/ ^5 i0 q7 h, b! D - }7 q8 S; K F; L$ I$ C, ]# g# T
- HAL_TIM_Base_Stop_IT(&htim2);
, I4 {: t' ^2 M - TIM2_flag = 0; f. `+ d( F" f
- - z9 R% q6 i; B2 g' {* B
-
4 Z# A" S0 Z# W3 k1 T4 z9 M- M - }
0 X: e' d- B8 Y7 y- F D - }
复制代码 : f) j+ G! m! g6 g7 t# }: v
1 F( Q: B6 N3 Q4 T- v2 x$ u: x也就是相较于之前,去掉了消抖的函数,然后也不是检测B的边沿触发,而是判断B信号,在一个时间范围内,有没有发生电平的变化,直接检测B信号电平高低的变化,实现了一样的目的。! Y9 i* c' k7 J% d/ b1 S& j4 l. X
7 w2 u0 {4 C, |; j, o& k
Q* J: B; `# `( N4 s! O
中断函数中实现
: T( t+ b e1 }7 X! g) x直接写在void EXTI15_10_IRQHandler(void);函数中无非就是多了步在中断触发之后需要手动清除中断标志位,其他都大同小异的思路,这里就可以检测A中断触发后,然后检测B中断触发,就不会出现什么问题了。
. y. L9 k+ t/ H. TSTM32CubeMX外部中断定时器嵌套问题及实验现象5 f$ _/ C9 ~$ z& ~# r. t" E+ P% o: a
写在回调函数中的这些实验现象和问题,现在的话就都不存在了。
/ y7 M- r6 o/ n4 ~0 e4 E6 W; t/ B. c1 h- x L8 q
. m' W0 d' A0 W H- void EXTI15_10_IRQHandler(void)* g$ P9 x8 f& O) `' k2 o
- {
' @! Z1 n5 \/ Z$ l' G - /* USER CODE BEGIN EXTI15_10_IRQn 0 */
+ @3 ]8 v: @9 \( G1 d
6 S1 ~" G. b( M' I S, p- /* USER CODE END EXTI15_10_IRQn 0 */6 l& o5 W6 W- S# E$ ?/ m
- // HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
' N7 j. x, Q+ i7 t( ?) z - // HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_14);8 J+ `$ V4 A* r; I5 r
- // HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15);
+ n5 b- @; s9 g# } - /* USER CODE BEGIN EXTI15_10_IRQn 1 */3 d8 g5 q' i$ Q. ]. n
- if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_13) != RESET) //A下降沿触发. B9 }. I( c% N% L: p
- {; K' T3 S6 W7 q4 ` X
- // printf("A下降沿触发\r\n");2 O* Z: w1 Y1 ~1 \4 U$ D4 H, z
- __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_13);. E# o3 w1 L. U* p" G, Q3 c5 Q
-
5 G2 u1 E7 ?/ V. P( s4 L, { - HAL_TIM_Base_Start_IT(&htim2);//开始TIM2定时器1 U; N. c, A2 i+ l
- while(TIM2_flag <= 10)//定时器一个周期1ms,计时20ms内看看B有没有电跳变
( u+ D3 d V* {8 j4 v - {! r9 y, ^4 l$ A6 y/ b" ?1 r
- if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_14) != RESET)+ ]( ^2 U4 J1 ?0 b& ^6 \: c! M, e
- {
8 F1 ~& a# @& i: C - // printf("B下降沿触发\r\n");
- b" \+ D2 }; c, L - __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_14);8 ~6 h$ Z) u3 o0 q) g1 M0 d7 B) x4 k9 s
- HAL_TIM_Base_Stop_IT(&htim2);9 m% [' G4 l6 {( w+ D" q5 E
- // printf("TIM2定时器关闭\r\n");
' v5 K8 m l, M4 B; f - TIM2_flag = 0;
1 _, Q$ q \' k7 a1 ?% I1 M* N - if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)
1 ]% r+ p) G7 y5 H+ T! u: S$ A: q7 K - {7 y v* g3 h6 l3 s
- printf("A\r\n");) c+ t3 ^9 [- S& q- u, c. p
- break;2 D0 q3 k8 q/ U! Y9 ~5 E
- } $ O9 r! g: q, C4 G% i6 X
- else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)7 n* h- s9 c9 _. b
- {7 e. o( F- r: a9 g4 I Y) S6 m
- printf("B\r\n");
; p, R [3 J( c7 P' S - break;
! K& I2 N. P$ N7 J - }
1 H& U7 r4 v V: [7 t/ ~ ` - break;
. s/ n, U+ J/ b7 X2 g4 @ - }8 _7 k3 s, D6 \. }- H
- }) j J' [1 n- s% |
- HAL_TIM_Base_Stop_IT(&htim2);
1 @# L+ x' n: V& w* G# | - TIM2_flag = 0;
2 j, `9 l) b) b0 V -
: @4 m2 Q+ W7 u. Q" |; @7 y# g. { - }
2 \+ O* O( Q% V# n% G - ( j: s: C. t$ l; @- x
- if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_15) != RESET)
( B# H' H; A0 K: n - {
2 S* ~0 h' Q' S7 J. y - printf("SW按键\r\n");
' v* W5 v6 M7 m5 K: y0 B5 Z - }
9 y( J/ B8 X" S. @) a5 X! E/ @
9 n3 d( t& T% ?, w- /* USER CODE END EXTI15_10_IRQn 1 */
5 d; S. p7 u% f$ |9 d; p - }
复制代码
' \% M+ S( O9 H; H9 D
) a, b' R% s& o1 E
X2 \( D( d0 _" v# L% O( E6 ~, k/ q8 U
1 k' ~$ b3 V- u3 [8 Z |
这个确实复杂了,大佬有机会做个分享
大佬有机会做个分享
我记得我以前搞过,貌似没有你这么复杂,而且程序实现也比较简单可靠,# d! y! ~/ D0 l- s' Q1 r
不需要定时器,也不需要中断,普通的 io 口就可以了6 ~; @% Q, d9 _: V% P6 v* Y- q* E# J
现在程序一时忘记放在哪里了,有空我找找