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

STM32经验分享 第13章 GPIO—按键中断

[复制链接]
STMCU小助手 发布时间:2022-8-30 18:46
13.1 关于STM32的EXTI- m" Y% D: i6 l/ o
前面“第10章 基础重点—中断系统”介绍了STM32的中断和中断优先级,知道了所有外设中断都由NVIC管理,比如USART、ADC、I2C、SPI等。GPIO产生的中断也不例外,但在给NVIC管理之前,还有一个EXTI(External interrupt/event controller,外部中断/事件控制器)先处理一下,如图 13.1.1 所示。# ^. x( g, n! x: P3 m" @9 D
}VMV7JY631V${X]NZZO9MB7.png

9 i& V; {, E# K  C* M. o
0 y1 D( Y! U; Y5 r: U/ t2 c: W
图 13.1.1 STM32中断处理机制

9 f4 X9 I* n4 i7 v3 M. _
# k  k0 [, P5 l$ m4 x! f
6 |& m# [4 q6 a9 c% h* E$ O& k) V0 ESTM32F103系列的EXTI支持19个外部中断/事件请求(互联型系列的STM32支持20个),每个中断/事件都有独立的触发和屏蔽设置,支持中断模式和事件模式。0 K- D8 s/ k  N

4 y# ]$ ~6 a& m, o: }* d/ R; s! [& o! J中断模式是指外部信号产生电平变化时,EXTI将该信号给NVIC处理,从而触发中断,执行中断服务函数,完成对应操作。# l3 P; O" B* m6 p9 @/ U" g

' E! p# O" ~* c: B事件模式是指外部信号产生电平变化时,EXTI根据配置,联动ADC或TIM执行相关操作。
5 N$ ?% V5 y* g7 R2 q$ U1 n1 `  [1 }
中断和事件的产生源是一样的,中断需要软件实现相应功能,而事件是由硬件触发后执行相应操作。前者需要CPU参与功能实现,可以实现的功能更多,后者无需CPU参与,具有更高的响应速度。
" U/ I  o5 z  y) U7 Y5 J
# M: Y3 k& g2 k& K: R: s" vEXTI的结构如图 13.1.2 所示,图中画斜线“/”的信号线表示这样的线共有19根。外部信号输入后,首先经过边缘检测电路,可以实现对上升沿或下降沿信号进行检测,从而得到硬件触发,也可由软件中断事件寄存器产生软件触发信号。无论是硬件触发还是软件触发,如果中断屏蔽寄存器允许,则产生中断给NVIC处理(绿色路线);如果事件屏蔽寄存器允许,则产生事件,脉冲发生器产生脉冲供其它模块使用(黄色路线)。
: |+ P9 Q5 S% e* M1 s% y, F4 l, B7 l6 {1 j
STM32F103的GPIO挂载APB总线上,如果要使用GPIO引脚作为外部中断/事件功能,则必须使能APB总线上该引脚对应端口的时钟和AFIO复用功能。
# s% p( Q" Q- w5 Q! M8 u* O0 m
: ^$ J9 R) x% R; P' R. s7 _  K5 Y- E
I$PM7K%0YD`_L7CL7O0A857.png
8 X1 U/ p; c- D: T# Z

) p6 o5 G# R4 R% P, }. Y6 P/ \
图 13.1.2 STM32F103系列EXTI结构框图

8 t8 H4 @4 V+ @8 ]STM32F103C8T6有2组GPIO,每组16个引脚,即32个GPIO引脚,但EXTI只支持19个外部中断/事件请求,因此需要将多个GPIO合成一组,共用一个中断线,STM32F103系列中断线分组如表 13.1.1 所示。0 k$ V' r. R+ b: R" H# O" b' U
! |7 [5 l0 U0 G4 A) Q& F
3LGEH(R9{{Z}3P4J{{NFMOW.png

( V! f, [" S6 @! z# C& h
  c2 q# S" B: @% N+ w' U& s
表 13.1.1 EXIT中断线分组
3 k" \# u7 i- l) {7 {- B
) S5 [5 z7 |8 J5 F7 f0 ?

  M9 l0 `: B7 q2 O/ t% g结合图 13.1.1 所示,EXTI0~EXTI15作为GPIO中断线使用,同组的GPIO共享一条中断线,比如EXTI0组,PA0作为了中断源,则此时PB0~PG0不能作为中断源。! [7 O7 D& @1 ]5 o* Q& y& K4 {

8 U( E6 b  I0 h  k
4 v7 b- ~* j6 M  X: R% N8 ?' }: F) ~5 v9 z% H8 [- O! J) D4 l+ L
【总结】) t/ Q6 `" u9 ^

7 a3 l+ ?5 i$ k* k' k5 YSTM32有众多异常和中断,其中内部中断源(USART、ADC等)直接由NVIC处理。GPIO引脚可以产生外部中断或事件,如是中断则交由NVIC处理,如果是事件则产生脉冲信号联动其它模块工作。+ i5 [0 n) V5 j5 v
* K  C, Z! B  V' P2 c9 m  B: T' H7 ]
无论是内部中断源,还是GPIO产生的中断,都由NVIC管理分组,然后根据中断优先级分组确定抢占优先级级数和子优先级级数。1 @/ ^  s" d) O1 q# H7 {- x! v* {
& M! C: W% Y6 ^4 x7 R) e
GPIO引脚众多,将引脚数字相同的作为一组,共享一个中断线。1 a( N* Y$ R' ?

& t$ d2 E$ y7 N, E0 \8 s# O( {, n- V' W6 c% h  e

: S; O3 ]! z$ s: O4 Z: S13.2 硬件设计
7 ~: r) y. E- X& g; q, ]4 f
同“12.2 硬件设计”小结内容。
& u* r, _$ O0 E. R5 C7 V& f/ v  ~) n( A: B" x2 b5 P

% C, t- t1 g0 |5 ?3 T13.3 软件设计
: t5 t% \# @. W! l! Z13.3.1 软件设计思路( a' o: `+ R4 W
实验目的:本实验通过使用外部中断功能去判断按键的状态,通过中断的形式能够更加灵敏的读取到GPIO的电平,让用户更加直观的感受到STM32F103的中断,并学会如何使用和开发其中断功能。" p/ z; s" Q( f2 `) L, s5 ~  G

; s9 ^- _. b" R1) 按键初始化:GPIO端口时钟使能、AFIO复用功能时钟使能、GPIO引脚设置为下降沿触发中断(PA0);# A! @1 ]8 q3 _; z" x7 i( M! Y
% R0 b/ A' O! t
2) 填充按键中断处理函数:读取按键GPIO状态,操作对应LED灯亮灭;
: ^3 F- T1 {( {4 s, w" [. W, G% i
) T) {0 n3 i8 ]/ Y3) 主函数调用LED和按键初始化后,无需任何操作;8 I5 E7 U1 D' o1 {" ]- E0 W

1 X: G: o7 z. w0 h0 w) G: C1 H4 q本实验配套代码位于“5_程序源码\6_GPIO—按键中断\”。
1 F: w/ D4 [3 A* ?/ F* @# ^
! @! I. F4 G. r* R  P4 i4 b9 Q/ n6 ~$ P. l1 M

5 E3 `# @3 n/ V; v8 X: g3 f7 q13.3.2 软件设计讲解
- n& V/ o2 K; m$ t; k  k$ f! o! {1) GPIO宏定义与接口宏定义
! X( D! B+ Y3 E8 ?8 A6 ?# o. Y% F8 t' h. H: t
代码段 13.3.1 引脚宏定义(driver_key.h)
2 @' j5 O, ^# H$ C) i* R- q: l( Z0 L
  1. /*********************
    5 ~% w& Z* d4 x! B+ I2 w$ ^! N
  2. * 按键引脚状态定义
    9 f+ ~3 C% D% n. K8 k9 H# I9 D% k
  3. **********************/
    5 M& V2 ^8 g6 i1 L" x% h2 q3 f
  4. #define PUSH_DOWN                       GPIO_PIN_RESET
    5 l  h( s5 D. `0 T) h4 U
  5. #define SPRING_UP                       GPIO_PIN_SET* f' j" @/ C) T# g9 ~$ w  A

  6. / e9 N7 g6 E5 l) b" C
  7. /*********************
    # Y$ s# C5 @( ?% t3 R. f
  8. * 引脚宏定义9 b2 k9 _9 ?+ X( V% C
  9. **********************/
    6 \5 R  ~# Q/ A: E( ]- T, w
  10. #define KEY_GPIO_PIN                 GPIO_PIN_0! S- z# _8 ?2 q' ^- @
  11. #define KEY_GPIO_PORT                GPIOA
    ! s$ E0 [9 M: I6 r
  12. #define KEY_GPIO_CLK_EN()            __HAL_RCC_GPIOA_CLK_ENABLE()
    5 s* C& Y9 }! D7 O+ O
  13. 1 k, K1 L  B  K+ ~' P& [: A
  14. /*********************
    - a, P+ i2 a2 K) _1 `1 }
  15. * 函数宏定义2 E0 y( X# v, a& M
  16. **********************/
    " ^/ D6 C/ o3 J. A1 [! n. t& a
  17. /*8 t1 Q# t- q5 |- h3 `& r' J. ~
  18. * 按键状态读取函数宏定义
    2 ~- k4 K, T$ Y1 Q& w
  19. */
    9 J5 K7 P3 E; ]; l4 D0 C2 a+ J" \
  20. #define KEY                           HAL_GPIO_ReadPin(KEY_GPIO_PORT, KEY_UP_GPIO_PIN)
复制代码
$ _. b, y9 N  ~2 ?/ k6 n
此处与上一实验一致,不再赘述。: ~9 u( A/ B! `, u8 U( n& R8 P
! Y" m% V0 A9 ^

: s3 G- l, k) `5 V/ u
4 }  H! e$ c1 E/ y7 A8 Q: ^2) GPIO初始化7 V: l0 i, [1 ]) A0 ?# q" d7 l

( c% L0 `3 B- d# o4 Q代码段 13.3.2 按键初始化(driver_key.c)
+ ^5 A3 T% k5 K' A& E4 ~1 r3 P
# U! ]/ Q3 N1 _: d9 T
  1. /*8 a* U+ W$ }! U' K) Z7 X
  2. *  函数名:void KeyInit(void)
    $ I: v$ M& |  B% y8 W
  3. *  输入参数:无
    ! L8 B7 N9 N$ E
  4. *  输出参数:无
    * n4 i' Q& Q1 U5 b: e8 p# ^  k
  5. *  返回值:无) U' `: O0 Y8 W% W
  6. *  函数作用:初始化按键的引脚,配置为下降沿触发外部中断$ D' P- E' P# M' ]3 n2 O( m$ Z

  7. ' g$ H1 g7 u( O* d6 U$ ~
  8. */# G7 Z2 M! `9 R8 I1 ~
  9. # m! l( z& m% r$ `$ P2 s4 m) \: D
  10. void KeyInit(void)$ m' a! M! Q6 A- f. M3 b. g
  11. {+ `6 ]2 E' e* ^
  12.     // 定义GPIO的结构体变量$ Z5 [. t6 H1 Q+ r4 L0 T
  13.     GPIO_InitTypeDef GPIO_InitStruct = {0};' s  P. R+ F; \$ }0 V6 T

  14. 3 J9 k) r1 r9 t& K) O
  15.     // 使能按键的GPIO对应的时钟8 w) Q- i3 }' r: S. z5 J/ t
  16.     KEY_GPIO_CLK_EN();# J' Q/ u1 d2 S
  17. 1 V1 Q( V" c! Y% |. s) P
  18.     GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;        // 设置为下降沿触发外部中断
    ; g" m& U, v2 N6 ?9 ?8 w; V
  19.     GPIO_InitStruct.Pull = GPIO_PULLUP;                 // 默认上拉( |/ @# E8 Z: i+ u
  20.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;       // 引脚反转速度设置为快
    ! y, O+ l6 y* v

  21. # Q6 w; `  I3 H/ ^8 k$ P
  22.     // 初始化按键引脚配置
    7 j, ?4 w3 g- h" K0 x% K8 m8 I. f
  23.     GPIO_InitStruct.Pin = KEY_GPIO_PIN;                 // 选择按键的引脚
    & v: R6 V6 a# X2 l& v& F
  24.     HAL_GPIO_Init(KEY_GPIO_PORT, &GPIO_InitStruct);% q2 c8 u6 E0 s$ r1 j
  25. ) M1 }0 |. k9 f# ?0 ^
  26.     /* EXTI interrupt init*/
    % e2 R  c9 }; D3 |3 O6 C
  27.     HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
    + J/ e3 {" Z# I3 U
  28.     HAL_NVIC_EnableIRQ(EXTI0_IRQn);* ^- q7 a9 x+ L, r& ?

  29. $ U5 O( L$ a+ i; l/ R5 r
  30. }
复制代码
. m& ~( ^1 g. h
13行:使能按键对应GPIO端口时钟;8 i, c( D& }) K6 w
! C6 V) ]. b0 {# I7 ^, z( f2 o
15行:设置为下降沿触发外部中断,即按键按下瞬间触发中断。可根据需求设置为上升沿触发,即松开按键触发中断,双边缘触发,即按下松开都触发中断;8 G6 r5 d8 J% N! k$ G6 _

! [% [6 y5 F- ~9 ~; d6 v5 m16~21行:初始化按键对应的GPIO,“HAL_GPIO_Init()”里会判断该引脚是否为EXTI模式,如果是则调用“__HAL_RCC_AFIO_CLK_ENABLE()”使能AFIO时钟;0 i3 q- U7 L! Q+ |- ]$ X& N. V5 d

4 d6 f. Q6 K) b9 [25~25行:设置EXTI中断线0的优先级和使能,即PA0所在中断线;, F# A  n' {! Y: x4 p1 X

( K+ R. Y: G2 Q( L( X! S 3) 中断处理函数
8 {8 x9 l, ~. `  C, N  I, P0 X/ `* q: b/ N2 {6 M9 y- ~
当中断发生后,则自动跳到代码段 10.2.1 中所对应的中断函数所在位置,执行中断函数的内容,因此我们需要编写中断函数的内容,如代码段 13.3.3 所示。. h/ B+ N$ a; A2 V6 G8 F+ K7 M' v
' p3 H* l8 l  ?9 H8 S" T0 X
代码段 13.3.3 按键中断处理函数(driver_key.c)1 w( j- W7 t/ ^

8 F' }9 d# ^- S) Y7 r
  1. /*: j# p* `; a; z1 Z2 [# W8 E
  2. *  函数名:void EXTI0_IRQHandler(void)0 e3 e7 O/ P& [( @+ R# @  ^
  3. *  输入参数:无+ G2 ?$ G3 v" t' o! d6 q) i
  4. *  输出参数:无
    . W0 d4 K$ y7 W7 D8 M; _1 ]
  5. *  返回值:无5 |& ~& [! |. a! |. j5 }
  6. *  函数作用:外部中断0的中断处理函数
    * N+ _  Z" e0 w- Z+ \! d
  7. */
    5 w' W7 m1 q1 W: Q" n0 Z+ M
  8. 3 O5 {6 c; C0 ^- u, f* }0 T# g# T
  9. void EXTI0_IRQHandler(void)5 h& ^# [* M+ e" |0 Y4 R
  10. {
    8 Z' N% w  _* }, k
  11.     HAL_GPIO_EXTI_IRQHandler(KEY_GPIO_PIN);
    7 X$ d2 D; u" @4 n" i8 B$ S. w) b
  12. }
复制代码
; H1 r3 W$ w7 u
注意这里的中断处理函数的实现,可以放在“stm32f1xx_it.c”中,同时注意在“stm32f1xx_it.h”中声明。本示例放在“driver_key.c”中,方便读者理解。
; K! p  [/ X5 {7 H$ b1 Z, X$ k# x* u: e3 q0 i
每个中断处理函数里,都调用的“HAL_GPIO_EXTI_IRQHandler()”准备后续处理,传入参数为外部中断的引脚号,该函数原型如代码段 13.3.4 所示。
! }5 h3 u) N5 b
1 Y) T4 t' n1 U: G% H代码段 13.3.4 外部中断处理函数(stm32f1xx_hal_gpio.c)/ t2 ~, n; z6 `% Z* e4 P
, W9 i7 T' F; T
  1. /**
    ! A! y5 }! v7 M. G7 ~% w+ G1 W, |3 P
  2.   * @brief  This function handles EXTI interrupt request.5 I9 N6 Z! ]- R4 C, Q# }. ?7 ~
  3.   * @param  GPIO_Pin: Specifies the pins connected EXTI line: E0 ^0 G2 g/ `9 ^9 C
  4.   * @retval None
    : O2 b5 Y8 M" V
  5.   */3 U2 t( X) a; F; h, C
  6. 4 g5 x1 @: q% e2 E& m
  7. void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
    5 f  K  H1 b* `7 }" c9 k7 i
  8. {
    * w' O( C* }8 N! B5 i1 ^
  9.   /* EXTI line interrupt detected */
    $ a2 K( p" S0 N. d9 ?
  10.   if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)2 g, d3 W* F% _5 ~8 [
  11.   {8 G, ~" Z& P! {! I- s! Z1 `7 a' s
  12.     __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
    * O- p. \0 _8 o
  13.     HAL_GPIO_EXTI_Callback(GPIO_Pin);. z( A4 g) p0 d8 ^5 }6 E/ {5 F
  14.   }4 N7 f  ~& b  `1 D8 x- E
  15. }
复制代码
) Q. d# T  t, K# ~+ i! J& @
该函数先判断传入引脚是否产生了外部中断,确认该引脚产生了中断,则清理中断标志,再调用“HAL_GPIO_EXTI_Callback()”回调函数。在该回调函数,通过判断输入的引脚,完成对应的用户操作,如代码段 13.3.5 所示。
# h" H3 Y! z% p% q3 e0 J0 [/ v8 G9 t: z# b7 h
代码段 13.3.5 外部中断处理函数回调函数(driver_key.c)
/ {$ @: d5 x* [
  A% [' _' h0 s- R& w
  1. /*
    8 e% n2 N! }" P, W, ^6 {9 |2 C$ X7 i% i
  2. *  函数名:void HAL_GPIO_EXTI_Callback(void)$ [' Q, V& k4 G9 F) K: ^! u
  3. *  输入参数:无
    ! s& S, I' i2 P. \7 r
  4. *  输出参数:无
    : z6 i5 z  H' O' n- n) c
  5. *  返回值:无/ k& K/ j& m' `; v8 ^' P% h
  6. *  函数作用:外部中断处理函数的回调函数,用以处理不同引脚触发的中断服务最终函数
    9 [9 [2 b. r: l) h* |) Z
  7. */6 M1 C2 r, [* O
  8. static volatile bool  key_flag = false;           // 定义一个全局静态标志,用以判断按键按下的次数
      F8 A- Q, c& K, D0 `* h" W, o6 O
  9. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)/ M( m: t9 s! _: d- [" J* H  v' Q: u
  10. {6 x* v* L& R- |, N$ ]% ~/ }4 R
  11.     switch(GPIO_Pin)                    // 判断是哪个按键
    4 m0 S7 A2 s3 o
  12.     {
    ; r; ]* ]  n% \
  13.         case KEY_GPIO_PIN:              // 如果是用户按键) o& F7 z' y# W) h
  14.         {; W% o$ E; [( b4 d2 W
  15.             key_flag = !key_flag;       // 按下一次标志翻转一次
    8 C7 C) ]. c/ t( k; T
  16.             BLED(key_flag?OFF:ON);      // 根据标志控制蓝灯的亮灭7 ?$ x; t9 v3 y2 e/ _" B$ _$ O% P
  17.             break;
    6 t1 t4 Y3 Z# S# ^" @
  18.         }
    $ n8 W  t" D8 B0 S
  19.         default:break;8 r, s  e) |5 [" V8 [
  20.     }2 T  z4 i% l+ T  g) ]* N0 z. D% v" l: C
  21. }
复制代码

* @& {$ Q% P0 y8行:定义了全局变量标志位,用于记录按键按下状态;
5 k: t6 o1 g8 C) D2 p' W: [0 l! ]: H3 f  ?: h/ u! k9 n! ^
9~21行:根据传入的引脚号,得知是哪一个按键按下,从而控制对应LED灯亮灭;! D$ q/ c0 W, l! g! }- o" e; Q# n
# W& ?1 E1 q& @3 O0 |  C
4) 主函数测试7 Y5 q1 Q, S% @

1 `4 p0 @$ H  m5 f+ K代码段 13.3.6 按键中断主函数(main.h)
" h6 b4 I$ a$ z
  x% {0 O; f+ E  k% n" u
  1. // 初始化按键
    % u* }( p9 \) O4 D( \" O& [- |, `
  2. KeyInit();3 c/ b: P( R3 j& O! w

  3. 1 W, i# E7 u9 ~- @2 C
  4. //初始化LED  B# D' O) R# z( B
  5. LedGpioInit();
    " o, |  `/ {8 J6 N5 z3 P7 Q5 y

  6. 1 ~, G8 O3 o0 [% q6 {: ~0 {
  7. while(1)
    " O& t8 Z$ {. T2 R& ]& l  V8 b
  8. {
    2 U) Z  V& U  N+ z) W7 l
  9. }
复制代码
' B9 s9 E5 U( H* |
  主函数只需初始化LED和按键,无需任何操作。一旦按键按下产生中断,将自动跳转到对应中断向量位置,调用该位置的中断处理函数。读者需要自行填充中断处理函数内容,这里设置中断处理函数最终都调用外部中断回调函数“HAL_GPIO_EXTI_Callback()”,在该函数里判断是哪个引脚按下,执行相应操作。
. A# ^" [1 W6 o4 i; \) _$ A
: H. x) H/ O2 Q, a9 B13.4 实验效果# F; o% p. _0 c
本实验对应配套资料的“5_程序源码\6_GPIO—按键中断\”。打开工程后,编译,下载。按下按键,用户LEDd灯亮/灭。 & c. ]' M- B* r

. E+ c3 q: O0 l作者:攻城狮子黄 + f; v* T" r) G& G$ Z
9 ^2 i$ b) X  }, z# |: }

' p6 z1 N- k7 C8 |, }
收藏 评论0 发布时间:2022-8-30 18:46

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版