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

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

[复制链接]
STMCU小助手 发布时间:2022-8-30 18:46
13.1 关于STM32的EXTI/ s0 h9 \2 G8 n' f6 l: k- x8 C7 B
前面“第10章 基础重点—中断系统”介绍了STM32的中断和中断优先级,知道了所有外设中断都由NVIC管理,比如USART、ADC、I2C、SPI等。GPIO产生的中断也不例外,但在给NVIC管理之前,还有一个EXTI(External interrupt/event controller,外部中断/事件控制器)先处理一下,如图 13.1.1 所示。
9 G: {, u- u+ C
}VMV7JY631V${X]NZZO9MB7.png
- t# f, M. J7 i$ G/ L( V4 M

, _6 a+ |! I* J) Q
图 13.1.1 STM32中断处理机制
' j$ O, a7 s; T/ D) [0 G

4 m( @* s9 d1 S
' h4 `/ P9 }: |8 g5 `- u. tSTM32F103系列的EXTI支持19个外部中断/事件请求(互联型系列的STM32支持20个),每个中断/事件都有独立的触发和屏蔽设置,支持中断模式和事件模式。6 E8 I0 P' v  b
; s  ]: H; u1 ?2 q- r' B7 v0 c. f
中断模式是指外部信号产生电平变化时,EXTI将该信号给NVIC处理,从而触发中断,执行中断服务函数,完成对应操作。
* p3 [& M+ K1 w
$ E7 b$ a, u1 l4 E' I" G事件模式是指外部信号产生电平变化时,EXTI根据配置,联动ADC或TIM执行相关操作。$ ]% a# d4 L7 d8 P! M5 p# K! j  `
: f. a0 \9 R) E* L, w5 k1 J
中断和事件的产生源是一样的,中断需要软件实现相应功能,而事件是由硬件触发后执行相应操作。前者需要CPU参与功能实现,可以实现的功能更多,后者无需CPU参与,具有更高的响应速度。: L1 L! l. I  l# D- y! A6 I/ ?

: i+ b- {! A5 ?# kEXTI的结构如图 13.1.2 所示,图中画斜线“/”的信号线表示这样的线共有19根。外部信号输入后,首先经过边缘检测电路,可以实现对上升沿或下降沿信号进行检测,从而得到硬件触发,也可由软件中断事件寄存器产生软件触发信号。无论是硬件触发还是软件触发,如果中断屏蔽寄存器允许,则产生中断给NVIC处理(绿色路线);如果事件屏蔽寄存器允许,则产生事件,脉冲发生器产生脉冲供其它模块使用(黄色路线)。3 X. h+ I0 z9 U2 J* R1 F

4 j# @1 a8 u. r7 U2 [' m* b! P0 mSTM32F103的GPIO挂载APB总线上,如果要使用GPIO引脚作为外部中断/事件功能,则必须使能APB总线上该引脚对应端口的时钟和AFIO复用功能。
+ d1 c8 u, \2 A4 ^3 B
! @9 S4 Q4 X- B" [" D
I$PM7K%0YD`_L7CL7O0A857.png
# v0 Z! f" [' f! I

' J$ @( x& E- a4 K* P: a' w" _" U- g' s
图 13.1.2 STM32F103系列EXTI结构框图

( j9 {) ]" M+ [STM32F103C8T6有2组GPIO,每组16个引脚,即32个GPIO引脚,但EXTI只支持19个外部中断/事件请求,因此需要将多个GPIO合成一组,共用一个中断线,STM32F103系列中断线分组如表 13.1.1 所示。
# [: T; e/ ]# Q+ F0 ^
- R7 `. E, ?1 U& D, e
3LGEH(R9{{Z}3P4J{{NFMOW.png
. J6 e4 v1 h6 \  j5 l
; |% T* H$ C0 ]& ]$ b8 ^
表 13.1.1 EXIT中断线分组

+ R/ R( k2 V- i. M2 m
2 h5 E, M: j& j) x( K( W/ N3 C1 A
7 ]" V- f7 }' T1 V9 x结合图 13.1.1 所示,EXTI0~EXTI15作为GPIO中断线使用,同组的GPIO共享一条中断线,比如EXTI0组,PA0作为了中断源,则此时PB0~PG0不能作为中断源。
, T: ]6 W& p" x/ c6 z( K
8 H; @# x0 K/ B, V$ h& N2 A
( I6 v) @# s6 s" v1 u# W$ P
9 h1 {' [. z1 S9 W) k' v$ q- i【总结】
& \, M6 v; w' K! r. o7 L" ~7 M
: c% o8 t$ u: c; ]9 i6 USTM32有众多异常和中断,其中内部中断源(USART、ADC等)直接由NVIC处理。GPIO引脚可以产生外部中断或事件,如是中断则交由NVIC处理,如果是事件则产生脉冲信号联动其它模块工作。$ b6 d) A8 ]; t- `& ]& d1 U

4 V# z& Y* }& G' q2 U5 e无论是内部中断源,还是GPIO产生的中断,都由NVIC管理分组,然后根据中断优先级分组确定抢占优先级级数和子优先级级数。+ z' ?  w- Q0 r0 l' D, W

( b7 ~0 O+ _' c% C' z. oGPIO引脚众多,将引脚数字相同的作为一组,共享一个中断线。5 `) d3 [: c0 Z  w$ _+ C
) {# F. G4 j. Q8 v1 ]( G4 X

# [/ A2 D0 I' J+ O  k- {
* O+ [* G& i8 a% W/ V" l13.2 硬件设计
! J2 s4 l1 b3 B) p* R; P( C
同“12.2 硬件设计”小结内容。
& }$ e! C' k# p- R8 H  r- A
0 t+ i$ L, N, |- r' L( V. q& E* |/ \/ ^- O2 J; v
13.3 软件设计& J: c7 m& y( N& k, n2 [" y9 V
13.3.1 软件设计思路
5 e. E' R9 W% j2 f3 }9 I/ u实验目的:本实验通过使用外部中断功能去判断按键的状态,通过中断的形式能够更加灵敏的读取到GPIO的电平,让用户更加直观的感受到STM32F103的中断,并学会如何使用和开发其中断功能。
7 N- G& }3 z* G6 y3 I$ N9 }( K5 e% [; E; N
1) 按键初始化:GPIO端口时钟使能、AFIO复用功能时钟使能、GPIO引脚设置为下降沿触发中断(PA0);
7 Z9 R$ ~2 U# [2 \
# `7 u  l3 g9 U3 @2) 填充按键中断处理函数:读取按键GPIO状态,操作对应LED灯亮灭;  ]7 w3 Q5 h7 d% Y
9 N% _- D% h+ X+ B; v& ?
3) 主函数调用LED和按键初始化后,无需任何操作;7 |! h+ z' x; ?6 R. j
# P8 Y$ [6 `: c* S( p/ o% y+ J
本实验配套代码位于“5_程序源码\6_GPIO—按键中断\”。
9 S: M5 Y0 i+ b: u' V, w0 q2 i$ B4 s2 q) g
4 d3 a9 j. W+ d) q$ n

7 |9 G& L* ?7 Q2 t7 U13.3.2 软件设计讲解. o5 l% X+ V- t4 m& l, D
1) GPIO宏定义与接口宏定义
  U+ ?% E6 e5 d6 D  b7 v
0 R7 J0 `9 X- l% S# a  g代码段 13.3.1 引脚宏定义(driver_key.h)
4 q4 Y- d5 r$ T. l4 o# ]- ~
+ Y  F1 s; _# X6 O
  1. /*********************
    $ m+ |" [4 B. D
  2. * 按键引脚状态定义
    0 u. Y5 y' i; P* N% U
  3. **********************/
    ' L* J8 q+ D) T* D* k+ u% j
  4. #define PUSH_DOWN                       GPIO_PIN_RESET
    4 ^" N; G% i& C& f; j; I
  5. #define SPRING_UP                       GPIO_PIN_SET2 C1 F! E' ?7 l$ ]1 z0 _

  6. ' P# w9 k/ Z! i5 {( n+ C
  7. /*********************2 r/ k* M7 Q2 @: s& E
  8. * 引脚宏定义6 c& l0 q+ Q1 T, L% t" }4 K7 u
  9. **********************/
    . q! R& k0 H9 i0 d% [
  10. #define KEY_GPIO_PIN                 GPIO_PIN_0& v  r" `4 Q/ T( w' x+ _7 v
  11. #define KEY_GPIO_PORT                GPIOA) `6 l1 O! i8 G" d
  12. #define KEY_GPIO_CLK_EN()            __HAL_RCC_GPIOA_CLK_ENABLE()+ j  t2 d& t; h. o

  13. 3 ?" ^5 S8 i2 e! B* z
  14. /*********************
    " }8 g) U; a( Y. I5 M- ?
  15. * 函数宏定义
    $ d* \5 w, N8 ~+ ~2 v
  16. **********************/
    + g2 o$ h5 D4 `' ]2 W" `
  17. /*
    4 \- D) ?5 P/ E# F
  18. * 按键状态读取函数宏定义
    ) E+ B  _. z4 g0 V) _
  19. */& Q4 `! r( R5 k( ]) g0 c, m( g
  20. #define KEY                           HAL_GPIO_ReadPin(KEY_GPIO_PORT, KEY_UP_GPIO_PIN)
复制代码
8 c! T  F9 V, f1 }$ x5 A9 Y6 W' R* B
此处与上一实验一致,不再赘述。
4 P, i, r$ u  z+ `  F) b
7 I- t+ `' U7 p8 l
/ E& X, h' k5 s' E9 {2 s, y
! h$ B4 }( b& H0 h  F: ]2) GPIO初始化: [- t. D1 k: w! }1 }4 a. _

2 }* {. b9 i4 [0 X代码段 13.3.2 按键初始化(driver_key.c)' e% \$ r1 H) Y+ X5 q( D$ W

! W1 k2 s  `5 |$ \& [
  1. /*
    * N+ @3 n; m  M5 Y- w
  2. *  函数名:void KeyInit(void)
    * T, @; \0 U0 I" k
  3. *  输入参数:无4 S- r& n4 A  o2 M0 T! F4 t
  4. *  输出参数:无6 B9 L" [2 B. c/ `; e4 `
  5. *  返回值:无. O8 p7 g1 b% f2 e
  6. *  函数作用:初始化按键的引脚,配置为下降沿触发外部中断
    & ]+ T+ _! K+ G

  7. 9 ~( v7 T- \5 m+ f+ H5 H
  8. */
    " s# k& s; T. s* ]
  9. 3 ~2 b+ _, V' C# b: o$ c7 ~- R& O7 ^
  10. void KeyInit(void)
    5 y6 m& e% F) G. S+ m3 p
  11. {
    7 B) i, h# _4 J
  12.     // 定义GPIO的结构体变量7 J" a( `/ @" q1 [/ g6 Z
  13.     GPIO_InitTypeDef GPIO_InitStruct = {0};" v" f# X! u* Z; M, f( R
  14. 1 W9 {+ t: v' T! ^
  15.     // 使能按键的GPIO对应的时钟
    / ?5 \( Z6 P; K1 G9 v& L1 w
  16.     KEY_GPIO_CLK_EN();/ g1 P! m3 w2 D' C' i; b

  17. ! c2 F) D' Y/ W- d7 g' s% k) `
  18.     GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;        // 设置为下降沿触发外部中断
    : t/ x! W/ q/ h. B) @5 ~+ L
  19.     GPIO_InitStruct.Pull = GPIO_PULLUP;                 // 默认上拉
    3 A9 Q; r: s" A2 Y* \
  20.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;       // 引脚反转速度设置为快
    5 }' r" z& ^# F' [$ o$ `" R- m; e- ]

  21. , I2 U5 q" l1 I* E- o6 j5 o8 y
  22.     // 初始化按键引脚配置
    6 ~: |0 F6 R: [4 Z; ~0 i
  23.     GPIO_InitStruct.Pin = KEY_GPIO_PIN;                 // 选择按键的引脚
    2 P8 T3 b( ~" N1 N$ t: W; f5 s  a
  24.     HAL_GPIO_Init(KEY_GPIO_PORT, &GPIO_InitStruct);
    : H1 n1 l% ]$ H) f. f, E+ h

  25. & Q8 x% |) X! V4 P' F9 N
  26.     /* EXTI interrupt init*/
    & u: ^% M3 T$ w; p  @: G
  27.     HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);7 Q: u. X( X7 Q; w
  28.     HAL_NVIC_EnableIRQ(EXTI0_IRQn);
    ) I' U1 t; h: j4 ^/ @; T" q" x

  29. % \( q- R& V  ]! O9 r
  30. }
复制代码
2 r( b, K5 z. P8 }% o6 k: \
13行:使能按键对应GPIO端口时钟;
/ h# g/ b' T7 ~9 y/ w& ^2 J1 E& Y5 F6 s, |) K# Q
15行:设置为下降沿触发外部中断,即按键按下瞬间触发中断。可根据需求设置为上升沿触发,即松开按键触发中断,双边缘触发,即按下松开都触发中断;- S; ^* @: v  V+ k

9 ]6 w+ w' Y, \  t# V% o) b) u2 ]; Q16~21行:初始化按键对应的GPIO,“HAL_GPIO_Init()”里会判断该引脚是否为EXTI模式,如果是则调用“__HAL_RCC_AFIO_CLK_ENABLE()”使能AFIO时钟;
% j0 I  S  H$ Q' t5 H3 P/ {# C0 _4 t9 J" B. k3 u
25~25行:设置EXTI中断线0的优先级和使能,即PA0所在中断线;
$ E2 a$ R  r& t% l' z+ _- E5 c
' u  L& n9 d3 V 3) 中断处理函数
0 H! I4 s. _- r" O% h
( w/ a& e0 ~: g- ?" Y0 B  u( }当中断发生后,则自动跳到代码段 10.2.1 中所对应的中断函数所在位置,执行中断函数的内容,因此我们需要编写中断函数的内容,如代码段 13.3.3 所示。
2 m8 I9 Q: b* f2 i
0 @$ Y) }- f7 `; ?+ z代码段 13.3.3 按键中断处理函数(driver_key.c)
, ]! \% K6 x  N3 a1 H! G' u* D( z/ _+ ]6 K
  1. /*! J% x& U# ]8 I% ?8 `' S2 |
  2. *  函数名:void EXTI0_IRQHandler(void)
    # R8 a' K. a. x2 q! t
  3. *  输入参数:无; ^. g" J* q2 X2 o9 \- i
  4. *  输出参数:无
    ( S7 c3 r7 G1 q
  5. *  返回值:无8 s! [' f( i6 L
  6. *  函数作用:外部中断0的中断处理函数2 p! [4 ]( s2 l4 K$ c/ B
  7. */9 ~+ ^  X5 Z  |" x; K) a
  8. 6 j2 G; f! j" t3 J9 c
  9. void EXTI0_IRQHandler(void)2 {8 ?. s9 ~1 L' q4 J) `2 H
  10. {
    4 r% }( Q4 F0 X& O$ \
  11.     HAL_GPIO_EXTI_IRQHandler(KEY_GPIO_PIN);+ A% \# A' F9 n: e* U
  12. }
复制代码

7 H9 {4 ]4 `) J+ k  t4 v1 x' w6 ]注意这里的中断处理函数的实现,可以放在“stm32f1xx_it.c”中,同时注意在“stm32f1xx_it.h”中声明。本示例放在“driver_key.c”中,方便读者理解。) {# M0 \. y* ]8 N: S" u+ s
  z1 \# \& B' b2 E3 [) ]
每个中断处理函数里,都调用的“HAL_GPIO_EXTI_IRQHandler()”准备后续处理,传入参数为外部中断的引脚号,该函数原型如代码段 13.3.4 所示。1 f" @: m# F4 S- A  @1 E. R

4 r* e- P+ }; B7 u代码段 13.3.4 外部中断处理函数(stm32f1xx_hal_gpio.c)
+ f9 m( m1 ?& l" P7 k
  }  H  f2 X; {0 @2 L5 u
  1. /**- a5 t8 j% j! m8 A  s
  2.   * @brief  This function handles EXTI interrupt request.
    ' \' Y, {9 ~( b& [* S5 E  P
  3.   * @param  GPIO_Pin: Specifies the pins connected EXTI line# B. `  z8 y+ H
  4.   * @retval None: B$ X5 x# a' d* m. r
  5.   */
    & ]8 K/ U0 O  }+ ^) S
  6. * u% c5 N1 u5 d0 O: O
  7. void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
    & L) q& G: V0 {7 j4 F, k$ M
  8. {0 }( s$ @& x9 }! c3 i2 y! e
  9.   /* EXTI line interrupt detected */
    * Q3 F; Z2 Q1 r! t* k! ?( q  R
  10.   if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)8 f# Z/ J0 H# N* d$ R
  11.   {  _. P, _9 ]8 {  m+ b# l
  12.     __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
    : P; q, ^  M1 x% D
  13.     HAL_GPIO_EXTI_Callback(GPIO_Pin);! [6 ]$ s0 `: K) x. B
  14.   }3 ?' ?+ L/ ]1 F4 g9 @
  15. }
复制代码

. V6 P- R" D! W1 P该函数先判断传入引脚是否产生了外部中断,确认该引脚产生了中断,则清理中断标志,再调用“HAL_GPIO_EXTI_Callback()”回调函数。在该回调函数,通过判断输入的引脚,完成对应的用户操作,如代码段 13.3.5 所示。
. h: B- |4 ?! Y) n. R2 ^. r+ I/ C' E4 L
代码段 13.3.5 外部中断处理函数回调函数(driver_key.c)* v* M8 a' B0 {
3 ^  P7 d( K, ]% Q) H) h  q
  1. /*
    7 C# {3 \0 P4 l* G; B+ [
  2. *  函数名:void HAL_GPIO_EXTI_Callback(void)
    , O  x) p0 C# ^, }. `: c; C1 U
  3. *  输入参数:无1 W0 C# c' |7 u: q" _  V4 P
  4. *  输出参数:无) u6 |1 R/ c- Q! t# p
  5. *  返回值:无
    9 {) E* B0 _: t
  6. *  函数作用:外部中断处理函数的回调函数,用以处理不同引脚触发的中断服务最终函数" ]7 }* i: L; A) p5 H
  7. */
    * M9 o. B* T. V0 e9 t# }5 |
  8. static volatile bool  key_flag = false;           // 定义一个全局静态标志,用以判断按键按下的次数
    % B9 v% I7 F' `9 m
  9. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    / j; H) f# E( n  t
  10. {
    % x% ^. q8 z. n) j# p% O/ g
  11.     switch(GPIO_Pin)                    // 判断是哪个按键! _8 Z# h  O$ b9 {, v
  12.     {  d& K0 @3 y* P) J5 j. W
  13.         case KEY_GPIO_PIN:              // 如果是用户按键
    / ~5 I% Q" _4 F6 {5 |7 d- q
  14.         {
    7 A% s8 b2 \) \: v6 _% {" ?( B
  15.             key_flag = !key_flag;       // 按下一次标志翻转一次2 Q- r4 X0 A( |! J% V3 d
  16.             BLED(key_flag?OFF:ON);      // 根据标志控制蓝灯的亮灭
    ) @+ t- _1 r& s% u" i
  17.             break;
    / F; D4 s4 }4 h! Z2 i( u. N
  18.         }( `& b4 E& I. O% D& @; c  B
  19.         default:break;7 H6 F7 r' D( u; a
  20.     }% k) G5 c# {2 m# ?! D* A
  21. }
复制代码

# g: [- O; h( C0 y! d2 ~8行:定义了全局变量标志位,用于记录按键按下状态;
, g; Q2 f* u9 \4 |+ |* ?
$ I$ w- W; l6 p; o3 @$ ?7 O! j9~21行:根据传入的引脚号,得知是哪一个按键按下,从而控制对应LED灯亮灭;
: [9 E9 Q7 H4 n$ r+ K0 n8 O9 F; s! E! Q, ?7 g
4) 主函数测试
* Y+ Y, K/ K; f7 y& T9 r- G" n. H3 k5 B! k+ t
代码段 13.3.6 按键中断主函数(main.h)
  Z) [/ F* @% \2 ]" Y3 @" M4 N% r1 `& x' L0 F# F& D8 t6 L
  1. // 初始化按键
    * r$ t& p% t/ G+ N
  2. KeyInit();
    1 d9 @& [# n1 u- ]5 n4 c3 q
  3. ; G8 ~9 Z5 c. e1 O( C' T% I
  4. //初始化LED( f! W' t/ s4 c2 U9 |  |
  5. LedGpioInit();
      S# X0 u6 Z# V* z* I3 V

  6. - c" d0 w! x" E( q9 X
  7. while(1)
    3 _: m/ m& b$ {4 Y
  8. {
    + ~. B/ b6 W% W
  9. }
复制代码
) Z/ \+ r; _/ y" O8 ~
  主函数只需初始化LED和按键,无需任何操作。一旦按键按下产生中断,将自动跳转到对应中断向量位置,调用该位置的中断处理函数。读者需要自行填充中断处理函数内容,这里设置中断处理函数最终都调用外部中断回调函数“HAL_GPIO_EXTI_Callback()”,在该函数里判断是哪个引脚按下,执行相应操作。; `/ R' ^8 Q& B0 n

$ k( J+ @) ]/ i; N  G0 }13.4 实验效果* B6 L6 i' j. D
本实验对应配套资料的“5_程序源码\6_GPIO—按键中断\”。打开工程后,编译,下载。按下按键,用户LEDd灯亮/灭。 * n  k2 p$ G2 H' j
: m4 r$ v, ]& V8 [6 n' a
作者:攻城狮子黄
4 ?8 }  q! ?7 ^0 b+ x- z
* F- E- u( y, E' h  {0 E! ]/ A1 \$ F% j8 A
赞 收藏 评论0 发布时间:2022-8-30 18:46

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版