项目背景是在STM32平台上的 普通IO口PE13 PE14使用外部中断+定时器实现,这里因为设计没有选择可以支持 ENCODE MODE的端口。& k+ B9 Z' f' l* U9 U8 N. V( F3 ^+ V
% o9 N- a, k9 F1 h3 l: U
- m0 l ]$ [# z. R1 ]EC11旋转编码器
# l& S. k' Z3 L. ~! w9 M6 y, X
- u/ }" y$ l' r2 {/ }& B' N! f2 F* u; h# H* o5 {9 t6 [' \
3 w5 K0 L( N0 V
2 H; L' s) H9 c( ?: [. _/ T8 b& f% m# V* H
/ `! G; C, \. T$ V$ A8 o* H/ C从这个数据手册中,我们可以设计出我们的思路,主要就是,以A信号作为一个时钟信号,也就是基准信号,检测到A之后,再去判断B的动作,一个相对的电平。. u9 C9 v3 ?1 G
7 n3 e$ |% L$ B; O
例如,当检测到A信号下降沿触发,检测B信号此时如果是高电平,那就是逆时针,如果是低电平,那就是顺时针。
7 R/ E: \) T1 w2 {
- ///****************旋转编码开关,版本1*****************************/. U% E$ _! k) \! ?4 u3 r
- uint8_t EC11Direction(void): R7 _5 Q1 {4 R' N# _
- {
4 A7 N8 M' B) i5 L# P0 T - while(1)* Z, x* [8 ?' a% G( y0 N& }
- {' ^( i9 G+ u" i. a' Y: s- l& l6 p3 }
- if(A_flag == 1)//A下降沿触发外部中断,A_flag = 1
8 m5 d& i6 ]! V# R( h" f - { v( I f: q0 d x# P% i4 l
- if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1) //检测B信号电平
; K. D( ?- Z3 }8 |$ b8 B3 t6 E - {) ?, l' A7 q5 m. y: N3 E
- printf("正转\r\n");7 B3 A2 m9 A+ h& K# ]# O% }
- Direction_flag = 1;; |6 T W: \* \" v. d& J" _
- break;% a5 X0 v6 O3 B2 i
- }! T. O; o! m# J2 O" n
- else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
! V- H( e4 I+ x - {
* \: X- ^$ ?) |0 b5 Z; \ - printf("反转\r\n");. _' x; @* A: J6 z2 j! O& J7 Y
- Direction_flag = 2;+ z3 L3 T# v8 {, h1 M1 g- C
- break;* Z: o0 O3 L" k' L. c* p
- }
9 X3 o- G; i+ }4 a/ O - } 9 k; ~9 v* A0 g
- return Direction_flag;
1 }7 m( r4 x X G* T9 M
: @7 h( e; y# X6 L5 f$ Y: O, V H- }
复制代码
% ~# h4 K4 T3 b! `( b0 X" w这个是最简单的判断方法,这个方法不是特别完善,容易出现干扰和误判断现象。不过整体是思路是这样走的。
$ K, U' T1 W7 N% M2 Z y5 c3 k- i( o 中断标志位外部函数中实现: V% q& ^3 j' H
第一个实现版本,因为起初对于中断的不熟悉,没有直接在中断中直接写,而是只使用了中断产生的标注为来作为判断。 这个的设计思路主要是,A信号中断,消抖,确定A信号下降沿触发,打开定时器,10ms检测B信号是否上/下降沿触发,关闭定时器,判断B信号的电平高低。 % k- A+ k: F6 d! L+ B" l5 h7 E
软件设计流程图如下 : p3 Z* p1 @# Q. S. ~4 e& f. ]6 [$ B
, j( G) h+ f6 n! i
2 {7 s5 ^1 V1 ?! L1 I3 f" z+ i1 X
在函数中实际代码如下
7 l1 ^7 [) [1 Z6 `& s4 X: d
- ///****************旋转编码开关,版本2*****************************/2 D$ A$ n& Q/ `( @" W
- 返回值1 正转
. `; }) x+ J' \' S - 返回值2 反转
% }2 E8 `. o6 k' F& x - uint8_t EC11Direction_2(void)
% ]/ B& J& m5 l" C - {# Y( k# g9 q* {. B2 Z F+ p, d1 y
- char Direction_flag = 0;* G- A: ?* \) ?# d- e
- while(1)
& u: n, o: d6 G, w0 \$ K - {
, S* Z9 r: A, E u7 _+ \2 B - if(A_flag == 1)//A下降沿触发外部中断/ a5 d, x( i5 _+ f1 A T% U v2 I
- {
8 \9 F1 l- V7 d' M& D% @ - HAL_Delay(1);//延时消抖
# L4 Q+ _8 f; L4 z3 ?8 K W - if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_13) == 0)//A下降沿触发1ms后判断是否稳定在了低电平
, X: y0 B9 _& }6 R) w - {& k1 k1 l1 i/ m E4 L
- HAL_TIM_Base_Start_IT(&htim2);//开启定时器
* u7 v, d( c; o. L4 u0 }% Y) N; I I4 X - while(TIM2_flag <= 10)//定时器的一个周期是1ms,这里是10ms
: Y3 `9 D( U% g- k0 Q/ o - {
7 B3 O) S3 h8 ^% k% ~: n - if(B_flag == 1)//10ms内检测是不是有B上/下降沿触发) X$ r8 Y, m1 `: J
- {- K7 e" d/ \: ^6 X8 H
- TIM2_flag = 0;//清除定时器中断标志位
u4 s' l& R1 g' J, i; T - HAL_TIM_Base_Stop_IT(&htim2);//检测到B了直接关闭定时器
0 E' a/ a2 w$ {5 t6 D - HAL_Delay(1);//延时消抖
5 @! A6 c0 s* C4 E - if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)//判断Pin_14的电平,返回旋转方向
' o4 P% d8 d, s& ^$ X+ N8 s. T, Y - {
" j2 L- |' _/ q' o. @5 ^& a a - // printf("A\r\n");4 v& M' Q% m8 t/ k. J$ z8 b. {5 f
- Direction_flag = 1;
4 A1 b p' T- n3 H7 u' R - break;
; q( X2 t; R7 L4 G4 b3 \ - }2 J1 ^/ ]% ]- d
-
* r$ N. n |) p2 [; w: p - else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)) [+ _: s: P7 t4 D1 o% j5 _3 t
- {
3 i# R4 a0 }7 y; C# z; N2 ] - // printf("B\r\n");
- P2 |: ^" H5 i - Direction_flag = 2;
, d# g% D! f+ S3 Q. k! Q% ]" l - break;
2 x7 {* h4 ?2 G0 Y5 u! u4 Y - }" k' S) h( D' G- T: D. v
- }" r- Z7 B+ v G* V. M* C
- }
9 ]5 ]) M0 P4 E" v( ]4 V - HAL_TIM_Base_Stop_IT(&htim2);//定时器一个周期溢出后(TIM2_flag>1),关闭
" o0 Z/ [4 B9 } m3 d2 d9 ]0 v - TIM2_flag = 0;//清除定时器标志位
2 n$ Z: g6 T+ w) Q# U* G - }- ~1 G7 o9 z* Y5 X6 Y
- A_flag = 0;//清除A中断的标志位
! m4 X; q5 |. I1 H5 n% Q8 T9 x - } ) ^. \6 u3 l0 k+ N: j
- : x0 B7 z& W; i/ B4 v* \
- if(Direction_flag == 1 | Direction_flag == 2)
3 O& L0 s0 u1 z - break;
' a0 P/ P( a2 J- K' w' J, \. ^ - }2 I8 U/ P- Q7 e3 T3 q
- return Direction_flag; # O7 B( E$ q, i1 S5 c3 r
- % }1 p6 R8 U: D- X( x& |
- }
复制代码 - }8 |/ x2 f% [* z( N$ _2 h
9 l) b8 I5 d& V0 I
在main.c中的定时器的标志位设置,使用了TIM2定时器,溢出就+1 6 d7 V3 e+ h: p$ L% E& B; M! A9 l3 K
' K( k. A) G- u- M; ~: o% h- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)* i, N, S0 P: H) W% U& V0 R6 D$ _: S
- {. g+ _1 J8 ?4 f; T6 B% R- ]3 C
) [+ e3 x# h' C9 u6 h- if (htim->Instance == TIM2)
' s. l v r x& W8 l! p6 t - {
6 M8 H7 M3 r1 L1 U0 H - HAL_IncTick();' p: m0 t1 I4 G
- TIM2_flag++;( r2 n- q3 `) M* ~
- }
* F4 {" U% o: }& f: U$ J- _4 p - 5 H% Y2 C, G; m; {6 w' u
- }, z- b0 A6 ?. L2 l+ X
复制代码 ( f/ X" ~- V$ H
在tim.c文件中TIM2的配置
3 Y5 a0 G2 C2 o5 p# s& `( |9 d
% q" \( J; e! ?8 S9 E" W, K" }& n8 V. A" Q! Q$ Y! q& T% U; C( I D
TIM2的时钟输入是75MHZ,所以设置分频和计数分别为 750-1 和 100-1,这样的话一个时间周期就是1ms 频率是1000hz。
0 X y n5 J% [; V3 y( \- d在stm32f4xx_hal_gpio.c文件中,我们找到外部中断对应的回调函数HAL_GPIO_EXTI_Callback,直接判断到外部电平触发后返回标志位就可以了。 ; x) ~+ L6 b, V4 F- y
这样写,虽然可以实现对于旋转编码器的检测,但是有一个问题,没有办法很方便的运用到实际工程中,以为进入到这个函数后才能进行编码器的判断,显然我们的编码器要实现的是一个翻页的功能,触发就要有操作的,而不是等着。 . l' ~; r. L( A3 z" h
虽然可以设计进去超时函数让编码器跳出,但是还是没有办法实现实际项目的需要。于是准备直接写到中断回调函数中。 ! j( `: S z4 s( O" J& @9 i- W% X
中断回调函数中实现
- Y, U, \8 h2 x4 f按理说直接写到中断回调函数应该挺容易的,直接改就行了 ,逻辑反正是通的,但是遇到了几个问题,一个是延时消抖的问题。 HAL_Delay本质也是一个中断服务函数,这种延时函数中断的嵌套是非常危险的操作,很容易卡死程序,比较有隐患,所以HAL_Delay函数是不能用了。 9 j' o0 e* L2 f: Y0 l5 ~
同时,因为回调函数是这样来使用的void EXTI15_10_IRQHandler(void)中检测到外部中断, 调用HAL_GPIO_EXTI_IRQHandler(GPIO_PIN);函数,然后再调用里面的回调函数void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)。
) J, a' S0 l1 q+ R) ]
我们这个里面用到了两个外部中断,PE13 和 PE14,也就是都会使用同一个回调函数,也就是无法完成这种操作
6 ^3 n% H* l5 F) [) J; G" L, W+ U7 \
这里就是举了个例子,因为回调函数的调用逻辑,没有办法在检测了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);函数中,这个也就是我后面的一个方法。 回调函数中想要实现,可以采用这个方法 o( B9 s9 E5 w% d
: q e0 G% _ M/ D4 s9 l: u2 h2 @2 Z1 q+ m! v) a; j; Y& K
; \' X9 ~3 s4 c- o$ @; t. j/ T' ~; U) M9 D5 z
- 8 t& X B+ N& z; }1 d
- void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
/ f) E) x+ p4 E; u: b# Q - {, ~ ~% d9 B: b; U& K
- /* Prevent unused argument(s) compilation warning */+ N" \9 d* \& e; m2 Y% X5 L% _& J' a
- UNUSED(GPIO_Pin);
' V( }6 I* H3 Y
) X4 H- M6 U2 O- if(GPIO_Pin == A_Pin)//A下降沿触发外部中断
( k t" P, h; |" b7 G" Q( B - {" E; v% a9 F" C
- // printf("A下降沿触发\r\n");4 g/ e& S+ Y: B* U' G! y" z: \
- HAL_TIM_Base_Start_IT(&htim2);//开始TIM2定时器- t1 `) x5 E' }# _% m2 ]/ J+ D% t
- B_last = HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14);//记录此状态的B状态
% l6 n, [3 ^" b# U. e& r* S- J - while(TIM2_flag <= 60)//定时器一个周期1ms,计时20ms内看看B有没有电跳变0 b" e4 _) V9 l
- {, ]1 X1 }$ {$ X9 o
- // printf("等待B的触发\r\n");
/ d! T: y) U+ R9 l" R( Z6 X - if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) != B_last)//在20ms内,检测到电平变化
$ ~4 \3 ], n# p- ~: W3 X7 j - { : q) F, F( s* ]8 B# q- R
- // printf("B下降沿触发\r\n");: X. k- p8 _+ N3 T9 `" l8 O) h) \
- HAL_TIM_Base_Stop_IT(&htim2);9 z- w1 j }+ o
- // printf("TIM2定时器关闭\r\n");
5 r4 v3 c7 B7 E) j - TIM2_flag = 0;5 V4 o5 c" N% K, U
- if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)3 {% M" x3 g# b: d& W" K3 t
- {
: {0 X7 r5 p% h - printf("A\r\n");! F" O \0 i9 `2 i
- break;& I- q; X- U) J2 a9 M% E& }( Q
- }
6 \( h8 ?$ D0 u( [ - else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)
# Q& N3 j9 \: B) I0 l - {
+ Q9 y) W; s( x7 {$ [' j7 Z6 T - printf("B\r\n");
" f1 X* @. K1 Y& c) F/ G2 [ - break;
% ^' y& g3 q4 F% ^ - }5 R7 \. B& h* w7 c4 }
- break;5 M9 J$ Z' l$ L! e7 c
- }: |2 P. [( ?* A0 s# U/ Z& P
- }
' s0 ^6 ` d2 [3 P - HAL_TIM_Base_Stop_IT(&htim2);8 k4 H7 P$ j t; h" w& {
- TIM2_flag = 0;: j0 J3 z; o6 J+ N( V& q5 O& y5 Q. a
- # u0 ?* o1 N! y5 f3 u i! S
-
. d& A8 G% m5 v3 N4 c - } , I/ W. A( X7 X- y/ D
- }
复制代码
1 E1 J7 r; A; M5 Y( V
4 q, A% }8 u' @4 U4 E( p
6 N. p9 _2 U5 A1 W# }6 z也就是相较于之前,去掉了消抖的函数,然后也不是检测B的边沿触发,而是判断B信号,在一个时间范围内,有没有发生电平的变化,直接检测B信号电平高低的变化,实现了一样的目的。 ( ]8 h0 J. m! g6 f/ n
中断函数中实现- t/ A, i8 X3 r' o" p+ ^ G
直接写在void EXTI15_10_IRQHandler(void);函数中无非就是多了步在中断触发之后需要手动清除中断标志位,其他都大同小异的思路,这里就可以检测A中断触发后,然后检测B中断触发,就不会出现什么问题了。
0 v. k0 ~' t+ a2 B/ M" Y% U写在回调函数中的这些实验现象和问题,现在的话就都不存在了。
; g) |$ E) @0 N( [# w
$ g- q4 @: j3 ~' S( h- void EXTI15_10_IRQHandler(void) m9 [/ ^7 W0 J e+ i2 @/ r( N
- {/ {2 C5 e" C' w/ f& S, L0 T8 o
- /* USER CODE BEGIN EXTI15_10_IRQn 0 */
. L" F& H, f+ l3 G" {: h# t! e
7 A) |' c6 s! J9 l, H$ c# x. N, x- /* USER CODE END EXTI15_10_IRQn 0 */
" @; B% g: i* n0 B) h - // HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
" d, d2 C, ?% v - // HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_14);
$ n1 h4 @2 z$ ?, s0 k- u - // HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15);
* }/ m) c& s! H' ?! a - /* USER CODE BEGIN EXTI15_10_IRQn 1 */
; |5 r* T' a" I0 r. V; n6 ^& e - if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_13) != RESET) //A下降沿触发+ ~4 M8 J4 F7 K& Y4 K8 g* |. l* z2 R" {
- {
1 i5 @3 o+ m5 W" D2 r" ?8 D/ v - // printf("A下降沿触发\r\n");
9 v" p! \3 S9 v4 R. C5 P# R - __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_13);
+ e1 G- u Z+ z' r" A - 1 w/ {8 Q! Y a- S( w t$ P$ T, l
- HAL_TIM_Base_Start_IT(&htim2);//开始TIM2定时器7 f/ | d7 b$ s7 P3 |5 j J
- while(TIM2_flag <= 10)//定时器一个周期1ms,计时20ms内看看B有没有电跳变7 @( T7 m/ J8 T. E# ~. ]
- {2 q- r& ]* o" z) c: r
- if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_14) != RESET)
- F+ D& l) Y k7 j: K5 a! S6 [ - {$ s# A' S- f# y, O! d1 J
- // printf("B下降沿触发\r\n");
- }* |& n, o9 K d$ x; J - __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_14);
& b: b$ J" M0 z) n: U - HAL_TIM_Base_Stop_IT(&htim2);/ e1 b6 j' F$ f; h/ Y
- // printf("TIM2定时器关闭\r\n");
1 d. z J) x& } - TIM2_flag = 0;
3 t" L% c- O1 _' o4 N - if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 1)5 K2 n% P/ ?0 N
- {
. ?: H' T6 t+ b+ W) f# @) ]. @ - printf("A\r\n");
. u7 r e( g& _; e+ X# Q - break;
# f- D' ~) l% N5 \; g - } ! _" q$ d# @3 `& e8 M
- else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_14) == 0)( k# \! v- f) L6 J1 L$ Y
- {& `6 Q! p# Y. t; J4 R
- printf("B\r\n");
5 D' M" ~: u2 }0 o2 r - break;
/ s- ^0 P# t+ ]% Z- \# g' c. T - }
3 u1 i3 l2 T. b- b9 p) B* G) ^ - break;
& ^1 n% \: L7 P, ` - }
, m7 E+ p/ i0 D5 m - }$ R0 p( V+ w) R/ J6 v4 } ]
- HAL_TIM_Base_Stop_IT(&htim2);
' A& E" Q2 I. y, r5 z* f - TIM2_flag = 0;
1 y' E$ s& n: |5 Z( q* W -
( I9 P$ ~1 y7 ?4 [% b4 q# E - }
4 ?% O, U b5 K; G; k% r3 x -
0 _+ V( ^$ z, u" U( [5 U - if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_15) != RESET)( [6 }7 j A' ^9 D$ K3 K
- {2 O0 ~. E, B/ p) v# [
- printf("SW按键\r\n");
1 H% E/ H1 x! I- L - }! J6 }# _! y. i9 }9 W/ r6 U8 T
5 ]' }3 M; @( M+ [- /* USER CODE END EXTI15_10_IRQn 1 */
D% U& w ? ?- x - }
复制代码
- g ~. A* T( i- R* {
% }; T" `5 ~* E
& Z, g/ [4 _9 ?1 f& C" H; Y
6 n, _' x. l& ~4 S9 ?% ]5 Q |
我看看哈,那个地方啊,我来改