项目背景是在STM32平台上的 普通IO口PE13 PE14使用外部中断+定时器实现,这里因为设计没有选择可以支持 ENCODE MODE的端口。
# n! ~6 L8 k" J0 h( |
; k( t/ q5 z& Y. p7 R9 r( M
$ ^( v: g' @ o0 FEC11旋转编码器4 d/ p( h- H& N- ~/ Z4 w
@+ e8 n+ G' T4 r
9 y! E P( z u B( Z
9 G% Y/ E4 `( B
2 ]2 Y3 ?! y Z- V
. 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*****************************/9 E9 T3 \* A/ q9 h
- uint8_t EC11Direction(void)
5 i6 \& y1 `+ b) J9 [; U" f6 P - {
# j. ?' f e5 J5 G1 y4 d - while(1)
7 {# X& q8 q7 n! ^, k - {
0 N" W3 n% q# M0 B - if(A_flag == 1)//A下降沿触发外部中断,A_flag = 1; M4 d. h) P! p( A
- {+ b: D/ j( k r+ [
- if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1) //检测B信号电平
/ C d& B4 L% V6 ]; U! q @4 X - {
* N0 |, o! |$ F7 m: b, _( ` - printf("正转\r\n");7 Q1 y( ^% z8 o% g: J% o
- Direction_flag = 1;
5 U# w6 c1 N$ G+ Z' ]" ~% _4 l - break;( ^! j0 m8 I, |; x& {6 k" z4 A
- }
; [, E' G0 T- y) _5 g - else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
- E$ W+ e* R' H! N- Q; H d - {5 q! \) ~/ b, t
- printf("反转\r\n");6 t3 |7 O- G3 v5 [: j. f2 [, r
- Direction_flag = 2;4 o* K n& ]- _) }5 `8 ^- o
- break;
H1 }# D* M- `* U- r% D - } $ p i$ E- K4 q$ D5 \) D5 C# _
- } ( u6 T% ]1 W7 j; r& R
- return Direction_flag; ' k1 _ t2 H2 X. b3 _/ h% P% X0 Z
- ' J g$ |" k7 F" Y7 x
- }
复制代码 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
/ 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
- ///****************旋转编码开关,版本2*****************************/
% B% p/ Y' e E8 l' m4 D0 ]8 Q - 返回值1 正转
0 c6 k* q5 ]+ d, A3 Y, \ - 返回值2 反转
/ L( Z5 l# X' E3 d - uint8_t EC11Direction_2(void)
" u( m+ @ D& x7 I, ^6 i# H - {
9 I# A4 ~) y/ ~% I8 A - char Direction_flag = 0;
F6 i! m9 {0 R4 ]( C" Q1 X, ~5 Z - while(1)2 Z* Z& E; n$ ?7 u
- {
, S3 `- \0 `* o# { y - if(A_flag == 1)//A下降沿触发外部中断
( e# i# p2 u/ q - {
. x3 V: r+ z+ H - HAL_Delay(1);//延时消抖
: A$ q- p9 ~; B% G% u - if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_13) == 0)//A下降沿触发1ms后判断是否稳定在了低电平
1 c5 Y) y1 q/ ?8 A& d5 z: D7 C - {! c6 I7 g6 O9 P1 D& ^9 A$ c2 Z
- HAL_TIM_Base_Start_IT(&htim2);//开启定时器: F, I& H- T. A8 r! {
- while(TIM2_flag <= 10)//定时器的一个周期是1ms,这里是10ms I$ ?! Z9 r0 M4 O& M0 h# I
- {9 z$ x- Z5 g* B- w' {
- if(B_flag == 1)//10ms内检测是不是有B上/下降沿触发1 u W- o6 H; K) g4 y! P
- {0 t+ I& f" G6 m) ~! F, S% L
- TIM2_flag = 0;//清除定时器中断标志位
. G: k1 M q3 `+ T" h- f6 ?7 ?* [ - HAL_TIM_Base_Stop_IT(&htim2);//检测到B了直接关闭定时器
! c- H, u7 I- s% w( L8 q) Q - HAL_Delay(1);//延时消抖* {2 H. Z/ ?( s# j$ r7 D8 \
- if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)//判断Pin_14的电平,返回旋转方向; B+ C4 |$ M* ]2 v1 u' [7 Q# C
- {; l6 N, ~& C7 t/ O
- // printf("A\r\n");
+ o2 r- D0 x- z' i z0 S8 d6 f/ { - Direction_flag = 1;
% ~8 I/ f( O$ B" E. G. G8 s - break;
6 o- _- Y$ [% {' ^3 F - }
g$ J$ W6 h, ?. z( m0 Y( P - . `; _# O( n4 u M3 H) b0 ^; _; H
- else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)& l& r2 z& b7 O f
- {/ {, S) G6 J& V5 u6 C
- // printf("B\r\n");( d( F3 y) M1 K6 `" J1 R1 _" i
- Direction_flag = 2;
8 z3 I! f+ q- j9 A - break;5 J9 W' d, w5 N* v7 e
- }, i5 W$ F* s1 {- }) a
- } s3 K9 I) \3 ]- m1 i" ~* j
- }
# u* H8 N! o& e - HAL_TIM_Base_Stop_IT(&htim2);//定时器一个周期溢出后(TIM2_flag>1),关闭
, ~0 P" H/ b7 W" m$ J/ Z9 B" w& b - TIM2_flag = 0;//清除定时器标志位
" ^. A: Z7 M# G4 R! `7 x) B" a+ j - }; O/ ^) B: f! S0 I6 y% l# n# O
- A_flag = 0;//清除A中断的标志位. s4 Q* u: x, x$ P
- } * I/ d) J, ` t
- 5 R7 B/ @+ ]4 |
- if(Direction_flag == 1 | Direction_flag == 2)+ q9 B$ i. N, J' F
- break;/ G4 g0 ~8 a. H$ s; D5 P$ P! S
- }
2 E* f7 w" R6 b. a& \' v2 R - return Direction_flag; $ V0 a6 Y: t4 w$ N# k
5 o3 N. n, l6 ?/ e- }
复制代码
* @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- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
& [% G1 J: g1 g# u9 _$ } - {# q( M! M+ X% g$ ^; u) e
0 W( h2 G6 S: E2 j7 O& B! S2 l- if (htim->Instance == TIM2) & `) B3 j4 A) R) A1 u
- {
2 y- e/ v4 U, e' j' ] - HAL_IncTick();
: O4 S8 U. d" T0 \, L - TIM2_flag++;
, @1 C( N [9 j" V! r' B0 d - }
5 c7 C4 P, p3 U0 V! J8 m6 S
3 g, F$ g! T! C/ ~- E$ s- }, 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
1 L7 [# f7 |& q- Y3 c* j1 U6 W7 R; Q
' Z& f/ l4 Y. f# u) e9 hTIM2的时钟输入是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
+ {; 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
& R/ ^) e5 r- i5 V- b5 c: s- void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
+ B& }+ v7 a' p1 ~ - {
. x, s: C" H, c1 ]2 D6 l8 F - /* Prevent unused argument(s) compilation warning *// e% J( F& R- V3 m3 B
- UNUSED(GPIO_Pin);. ~* |+ k, x/ X1 r% K
# U$ f* a( a4 b+ P, Q- if(GPIO_Pin == A_Pin)//A下降沿触发外部中断6 b5 I2 f# {" n, T5 G
- {
W" P/ ?7 z& S# i1 ^% F: \6 ^+ u* y - // printf("A下降沿触发\r\n");
- h9 m' h& B4 N; n7 p0 K$ p - HAL_TIM_Base_Start_IT(&htim2);//开始TIM2定时器
, {0 V! u& r$ n% o! b( y - B_last = HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14);//记录此状态的B状态3 e4 ` P$ {: v' f
- while(TIM2_flag <= 60)//定时器一个周期1ms,计时20ms内看看B有没有电跳变
$ N7 ]+ e/ U$ w; m" j/ K - {8 [" X3 B; y. I, u# k' J
- // printf("等待B的触发\r\n");
' E+ S2 s5 Q1 u& T7 a1 D: B - if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) != B_last)//在20ms内,检测到电平变化! Z7 n( v p+ L& T5 ]; K
- {
+ R6 D) p4 z& X; u, b# ~ - // printf("B下降沿触发\r\n");
7 P2 P4 u. s4 P6 P" r2 e3 i" _/ J - HAL_TIM_Base_Stop_IT(&htim2);9 Q7 }0 P! |' [$ H7 O2 }
- // printf("TIM2定时器关闭\r\n");
% C" ?. ^& w! H9 r4 I) l9 {, f - TIM2_flag = 0;1 }0 F3 |5 }2 [
- if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)
& n P# L* T' N" V$ @7 I, Q - {5 v4 h8 u& d* t2 P) m
- printf("A\r\n");
" Z% m% l7 B+ k. O - break;2 i4 O+ M- P5 k7 |2 t% ~3 S
- }
, o# Q: R% W4 Z8 G" x; y - else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
}4 s+ ^5 Q+ d( j" x - {* C1 p8 q4 J, ~. X
- printf("B\r\n");
& {* H% t" ~ i/ a7 Q4 g1 c - break;
+ t: ^: u" R. g" z; R - }
8 d+ q2 }% _: J* K# A - break;
+ N {3 i& K. e4 P! i; L' c2 G9 j - }; { P* p( Y' [4 e0 u4 U
- }
- B8 o' E: J: Y& ^ - HAL_TIM_Base_Stop_IT(&htim2);
+ v+ u% v" x6 o: y8 _ - TIM2_flag = 0;4 e8 e% _; _6 o, Z' H% n
- * Q1 a, t& X; b- b1 |6 `0 E6 R9 r
- " l, f. L& V0 |) S, y8 F* v, Q
- } 1 ^: I: a5 E1 M- x3 m; e
- }
复制代码 : ~! 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- void EXTI15_10_IRQHandler(void)
9 T0 _4 b/ J' ^% f3 z, D9 T& W - {+ q( h! D8 M! Y
- /* USER CODE BEGIN EXTI15_10_IRQn 0 */
3 y7 z) G- g1 F/ c - - U r) T( M! \ Q8 @8 {7 i6 M
- /* USER CODE END EXTI15_10_IRQn 0 */
' |' c7 V/ ]- Q7 {: \ - // HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
! Y J( S6 w6 P' g" n - // HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_14);
* r3 O3 e: {. C" ? - // HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15);/ ~/ J f' w% _. f9 `, Z
- /* USER CODE BEGIN EXTI15_10_IRQn 1 */
6 F3 G! K1 X9 v- {# [ - if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_13) != RESET) //A下降沿触发
; m( N0 v- t5 n8 p. N: @( y2 D - {
' ?+ o: P2 P4 c9 R- J7 J - // printf("A下降沿触发\r\n");" y7 T0 M4 R/ _" V. n
- __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_13);" m( S* k) B, V- H& M* {/ S
-
; f4 k1 U0 W4 m4 W - HAL_TIM_Base_Start_IT(&htim2);//开始TIM2定时器
. B* c9 r7 N5 @: O - while(TIM2_flag <= 10)//定时器一个周期1ms,计时20ms内看看B有没有电跳变1 I" i5 E Y! M* k: Q" U8 [
- {
8 i' E# Y' Y( z0 M3 d - if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_14) != RESET)
: [4 e+ K& e7 N+ G6 o' T8 J- \: f - {
& `# ^$ I/ d" l. u% b& l1 F9 A - // printf("B下降沿触发\r\n");
5 l3 q- |6 Q. G" U* u6 R" r - __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_14);6 }/ T4 |5 D- y5 I4 ]( K
- HAL_TIM_Base_Stop_IT(&htim2);/ R9 r9 s7 Y/ Y# Y; d7 l
- // printf("TIM2定时器关闭\r\n");. H4 R. C& t0 Y/ N
- TIM2_flag = 0;! I, o, x$ t& w& o+ r
- if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)
3 h2 w. ?' p9 k9 M Q7 ]2 D8 {9 g - {2 Q. }7 d4 d8 t
- printf("A\r\n");+ W$ ?2 ~# M2 V8 o
- break;6 y+ w+ ?# R: }6 ^" T
- } U: u( L& v/ R- \
- else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
8 h1 `' X5 d7 x. U/ x( J - {
z9 J1 B! u& l5 S7 R# n) c* F - printf("B\r\n");) A+ D5 L( U0 x- M# Z4 H
- break;
/ d: `( z; |: O) B" a% g$ U - }
8 p/ e/ D2 P3 H: K2 l - break;
* m3 o: _# X7 k& I: H( w* i7 A - }4 i. _- j" q) Y" P7 M! w
- }
$ u- I9 y0 g4 o$ I" [& O8 ] - HAL_TIM_Base_Stop_IT(&htim2);
% z; `9 I" c+ @6 @/ s! d - TIM2_flag = 0;. O- y7 W3 H1 F
-
# r/ s& U$ d- w - }/ S6 |2 a5 ]$ d! E
-
O( e/ l: q+ f( j5 O% w# g - if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_15) != RESET): O8 W0 M" [- o% `, g0 J% J
- {: Q# |1 X! x' N+ x
- printf("SW按键\r\n");/ y+ C% G' w4 u" }1 b2 L6 Z
- }/ B, Y4 V' g; Y( F" Q$ m; k9 C4 i7 W
- l# H- x+ M. P. J7 l
- /* USER CODE END EXTI15_10_IRQn 1 */
! U9 `- G+ W2 B4 F, p. `& t - }
复制代码
! \# j' Y. R p
' I9 \0 i; J$ ]* Y; M4 B. Q( {7 x& F2 L6 |" f
/ Y' _. f3 L7 P. s
|
我看看哈,那个地方啊,我来改