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
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- E8 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
( 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
- /*********************
5 ~% w& Z* d4 x! B+ I2 w$ ^! N - * 按键引脚状态定义
9 f+ ~3 C% D% n. K8 k9 H# I9 D% k - **********************/
5 M& V2 ^8 g6 i1 L" x% h2 q3 f - #define PUSH_DOWN GPIO_PIN_RESET
5 l h( s5 D. `0 T) h4 U - #define SPRING_UP GPIO_PIN_SET* f' j" @/ C) T# g9 ~$ w A
/ e9 N7 g6 E5 l) b" C- /*********************
# Y$ s# C5 @( ?% t3 R. f - * 引脚宏定义9 b2 k9 _9 ?+ X( V% C
- **********************/
6 \5 R ~# Q/ A: E( ]- T, w - #define KEY_GPIO_PIN GPIO_PIN_0! S- z# _8 ?2 q' ^- @
- #define KEY_GPIO_PORT GPIOA
! s$ E0 [9 M: I6 r - #define KEY_GPIO_CLK_EN() __HAL_RCC_GPIOA_CLK_ENABLE()
5 s* C& Y9 }! D7 O+ O - 1 k, K1 L B K+ ~' P& [: A
- /*********************
- a, P+ i2 a2 K) _1 `1 } - * 函数宏定义2 E0 y( X# v, a& M
- **********************/
" ^/ D6 C/ o3 J. A1 [! n. t& a - /*8 t1 Q# t- q5 |- h3 `& r' J. ~
- * 按键状态读取函数宏定义
2 ~- k4 K, T$ Y1 Q& w - */
9 J5 K7 P3 E; ]; l4 D0 C2 a+ J" \ - #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- /*8 a* U+ W$ }! U' K) Z7 X
- * 函数名:void KeyInit(void)
$ I: v$ M& | B% y8 W - * 输入参数:无
! L8 B7 N9 N$ E - * 输出参数:无
* n4 i' Q& Q1 U5 b: e8 p# ^ k - * 返回值:无) U' `: O0 Y8 W% W
- * 函数作用:初始化按键的引脚,配置为下降沿触发外部中断$ D' P- E' P# M' ]3 n2 O( m$ Z
' g$ H1 g7 u( O* d6 U$ ~- */# G7 Z2 M! `9 R8 I1 ~
- # m! l( z& m% r$ `$ P2 s4 m) \: D
- void KeyInit(void)$ m' a! M! Q6 A- f. M3 b. g
- {+ `6 ]2 E' e* ^
- // 定义GPIO的结构体变量$ Z5 [. t6 H1 Q+ r4 L0 T
- GPIO_InitTypeDef GPIO_InitStruct = {0};' s P. R+ F; \$ }0 V6 T
3 J9 k) r1 r9 t& K) O- // 使能按键的GPIO对应的时钟8 w) Q- i3 }' r: S. z5 J/ t
- KEY_GPIO_CLK_EN();# J' Q/ u1 d2 S
- 1 V1 Q( V" c! Y% |. s) P
- GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 设置为下降沿触发外部中断
; g" m& U, v2 N6 ?9 ?8 w; V - GPIO_InitStruct.Pull = GPIO_PULLUP; // 默认上拉( |/ @# E8 Z: i+ u
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 引脚反转速度设置为快
! y, O+ l6 y* v
# Q6 w; ` I3 H/ ^8 k$ P- // 初始化按键引脚配置
7 j, ?4 w3 g- h" K0 x% K8 m8 I. f - GPIO_InitStruct.Pin = KEY_GPIO_PIN; // 选择按键的引脚
& v: R6 V6 a# X2 l& v& F - HAL_GPIO_Init(KEY_GPIO_PORT, &GPIO_InitStruct);% q2 c8 u6 E0 s$ r1 j
- ) M1 }0 |. k9 f# ?0 ^
- /* EXTI interrupt init*/
% e2 R c9 }; D3 |3 O6 C - HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
+ J/ e3 {" Z# I3 U - HAL_NVIC_EnableIRQ(EXTI0_IRQn);* ^- q7 a9 x+ L, r& ?
$ U5 O( L$ a+ i; l/ R5 r- }
复制代码 . 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- /*: j# p* `; a; z1 Z2 [# W8 E
- * 函数名:void EXTI0_IRQHandler(void)0 e3 e7 O/ P& [( @+ R# @ ^
- * 输入参数:无+ G2 ?$ G3 v" t' o! d6 q) i
- * 输出参数:无
. W0 d4 K$ y7 W7 D8 M; _1 ] - * 返回值:无5 |& ~& [! |. a! |. j5 }
- * 函数作用:外部中断0的中断处理函数
* N+ _ Z" e0 w- Z+ \! d - */
5 w' W7 m1 q1 W: Q" n0 Z+ M - 3 O5 {6 c; C0 ^- u, f* }0 T# g# T
- void EXTI0_IRQHandler(void)5 h& ^# [* M+ e" |0 Y4 R
- {
8 Z' N% w _* }, k - HAL_GPIO_EXTI_IRQHandler(KEY_GPIO_PIN);
7 X$ d2 D; u" @4 n" i8 B$ S. w) b - }
复制代码 ; 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
- /**
! A! y5 }! v7 M. G7 ~% w+ G1 W, |3 P - * @brief This function handles EXTI interrupt request.5 I9 N6 Z! ]- R4 C, Q# }. ?7 ~
- * @param GPIO_Pin: Specifies the pins connected EXTI line: E0 ^0 G2 g/ `9 ^9 C
- * @retval None
: O2 b5 Y8 M" V - */3 U2 t( X) a; F; h, C
- 4 g5 x1 @: q% e2 E& m
- void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
5 f K H1 b* `7 }" c9 k7 i - {
* w' O( C* }8 N! B5 i1 ^ - /* EXTI line interrupt detected */
$ a2 K( p" S0 N. d9 ? - if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)2 g, d3 W* F% _5 ~8 [
- {8 G, ~" Z& P! {! I- s! Z1 `7 a' s
- __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
* O- p. \0 _8 o - HAL_GPIO_EXTI_Callback(GPIO_Pin);. z( A4 g) p0 d8 ^5 }6 E/ {5 F
- }4 N7 f ~& b `1 D8 x- E
- }
复制代码 ) 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- /*
8 e% n2 N! }" P, W, ^6 {9 |2 C$ X7 i% i - * 函数名:void HAL_GPIO_EXTI_Callback(void)$ [' Q, V& k4 G9 F) K: ^! u
- * 输入参数:无
! s& S, I' i2 P. \7 r - * 输出参数:无
: z6 i5 z H' O' n- n) c - * 返回值:无/ k& K/ j& m' `; v8 ^' P% h
- * 函数作用:外部中断处理函数的回调函数,用以处理不同引脚触发的中断服务最终函数
9 [9 [2 b. r: l) h* |) Z - */6 M1 C2 r, [* O
- static volatile bool key_flag = false; // 定义一个全局静态标志,用以判断按键按下的次数
F8 A- Q, c& K, D0 `* h" W, o6 O - void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)/ M( m: t9 s! _: d- [" J* H v' Q: u
- {6 x* v* L& R- |, N$ ]% ~/ }4 R
- switch(GPIO_Pin) // 判断是哪个按键
4 m0 S7 A2 s3 o - {
; r; ]* ] n% \ - case KEY_GPIO_PIN: // 如果是用户按键) o& F7 z' y# W) h
- {; W% o$ E; [( b4 d2 W
- key_flag = !key_flag; // 按下一次标志翻转一次
8 C7 C) ]. c/ t( k; T - BLED(key_flag?OFF:ON); // 根据标志控制蓝灯的亮灭7 ?$ x; t9 v3 y2 e/ _" B$ _$ O% P
- break;
6 t1 t4 Y3 Z# S# ^" @ - }
$ n8 W t" D8 B0 S - default:break;8 r, s e) |5 [" V8 [
- }2 T z4 i% l+ T g) ]* N0 z. D% v" l: C
- }
复制代码
* @& {$ 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- // 初始化按键
% u* }( p9 \) O4 D( \" O& [- |, ` - KeyInit();3 c/ b: P( R3 j& O! w
1 W, i# E7 u9 ~- @2 C- //初始化LED B# D' O) R# z( B
- LedGpioInit();
" o, | `/ {8 J6 N5 z3 P7 Q5 y
1 ~, G8 O3 o0 [% q6 {: ~0 {- while(1)
" O& t8 Z$ {. T2 R& ]& l V8 b - {
2 U) Z V& U N+ z) W7 l - }
复制代码 ' 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 |, } |