13.1 关于STM32的EXTI: W1 ?/ y# t2 t0 b1 U0 ^! F% O
前面“第10章 基础重点—中断系统”介绍了STM32的中断和中断优先级,知道了所有外设中断都由NVIC管理,比如USART、ADC、I2C、SPI等。GPIO产生的中断也不例外,但在给NVIC管理之前,还有一个EXTI(External interrupt/event controller,外部中断/事件控制器)先处理一下,如图 13.1.1 所示。
' @ H W0 j/ ~/ q# M% t7 o1 h" c) N* @
! A3 V# I# v: N' H% ?6 Y2 [图 13.1.1 STM32中断处理机制
. Y, l0 q9 @; B3 q
* `4 E9 m/ }! K5 j" ?5 _& }6 C
9 m( x, ?" Z+ uSTM32F103系列的EXTI支持19个外部中断/事件请求(互联型系列的STM32支持20个),每个中断/事件都有独立的触发和屏蔽设置,支持中断模式和事件模式。
' A" d. T$ q- F
k7 h {" \0 c: h- v! ]4 ]) `中断模式是指外部信号产生电平变化时,EXTI将该信号给NVIC处理,从而触发中断,执行中断服务函数,完成对应操作。
# f, U3 l% w! d% T7 i
3 R1 \+ q, H! G, k% P5 ?9 W3 E事件模式是指外部信号产生电平变化时,EXTI根据配置,联动ADC或TIM执行相关操作。0 y( }0 @3 T0 X& [# T% c* A/ P
* [8 u& T- p6 }5 r O' U
中断和事件的产生源是一样的,中断需要软件实现相应功能,而事件是由硬件触发后执行相应操作。前者需要CPU参与功能实现,可以实现的功能更多,后者无需CPU参与,具有更高的响应速度。
3 i# T: _1 A& L7 H0 Q% M
3 _) [1 |: s* x: O& x: N4 oEXTI的结构如图 13.1.2 所示,图中画斜线“/”的信号线表示这样的线共有19根。外部信号输入后,首先经过边缘检测电路,可以实现对上升沿或下降沿信号进行检测,从而得到硬件触发,也可由软件中断事件寄存器产生软件触发信号。无论是硬件触发还是软件触发,如果中断屏蔽寄存器允许,则产生中断给NVIC处理(绿色路线);如果事件屏蔽寄存器允许,则产生事件,脉冲发生器产生脉冲供其它模块使用(黄色路线)。$ J9 J- J2 B$ M6 ^! Q3 b* z4 V
1 }2 o7 s2 b" G
STM32F103的GPIO挂载APB总线上,如果要使用GPIO引脚作为外部中断/事件功能,则必须使能APB总线上该引脚对应端口的时钟和AFIO复用功能。4 \( g9 ] A1 e4 G; e, H' o! e. c `8 w
% s( }, M h) @- v. E5 W
& u9 X" J* M) T) q* c$ _; E
# E! s( U t' A- }# B3 u图 13.1.2 STM32F103系列EXTI结构框图
" ` L$ I9 e2 z7 q, M3 O9 BSTM32F103C8T6有2组GPIO,每组16个引脚,即32个GPIO引脚,但EXTI只支持19个外部中断/事件请求,因此需要将多个GPIO合成一组,共用一个中断线,STM32F103系列中断线分组如表 13.1.1 所示。, P3 B0 Q3 E- \) }* s, G+ q
/ Y" G, M( Y; m; L& n- k* a, n
* O6 s8 }" n) z
( L6 d- M- H$ c- V表 13.1.1 EXIT中断线分组 1 c- o5 C" b, A+ `6 \' b W, _
0 {4 C) w; t; B4 X) g* ]+ j" Y3 h) ^8 s# x! k
结合图 13.1.1 所示,EXTI0~EXTI15作为GPIO中断线使用,同组的GPIO共享一条中断线,比如EXTI0组,PA0作为了中断源,则此时PB0~PG0不能作为中断源。1 a, T5 r' K% c# B
1 @: B: S; Q+ e1 }8 }) C9 b
, }4 S$ u9 s% v' M3 h# x( D
! @ W+ V# [+ q1 E+ `/ t: }' G【总结】' L' l* a1 N) ~6 ~2 i5 w$ R% w! R
) e, k* ]4 n, l+ U5 |" C
STM32有众多异常和中断,其中内部中断源(USART、ADC等)直接由NVIC处理。GPIO引脚可以产生外部中断或事件,如是中断则交由NVIC处理,如果是事件则产生脉冲信号联动其它模块工作。0 b' x2 T/ _4 R8 [% S5 j
# \) w+ Q2 T! g; C/ K: L; x
无论是内部中断源,还是GPIO产生的中断,都由NVIC管理分组,然后根据中断优先级分组确定抢占优先级级数和子优先级级数。
( s# |7 o" f' P& ?8 O9 g4 F* }0 j$ G p W
GPIO引脚众多,将引脚数字相同的作为一组,共享一个中断线。4 a- H$ N4 r" Z
g8 Q: t# `, e
8 h. a @- L B- c- g7 k
v7 G: g8 w& n8 u/ c0 z# E _. G13.2 硬件设计5 g$ g' D$ h- D8 _' B
同“12.2 硬件设计”小结内容。; Y$ x8 ?4 _) Q6 u, `8 g) Q# P
# s/ B& p& i6 L8 {6 [9 S
7 q C' t; ~( q( w# u3 _$ l13.3 软件设计- g6 d8 Y: K2 K
13.3.1 软件设计思路$ H$ V' w; s j. x& o, h0 r: H
实验目的:本实验通过使用外部中断功能去判断按键的状态,通过中断的形式能够更加灵敏的读取到GPIO的电平,让用户更加直观的感受到STM32F103的中断,并学会如何使用和开发其中断功能。
' x/ l2 \0 P" [& @) B' S& P$ d) f, ]
, L8 C2 K' f+ H4 U1 }& \! F, z1) 按键初始化:GPIO端口时钟使能、AFIO复用功能时钟使能、GPIO引脚设置为下降沿触发中断(PA0);" r! W0 X% }# R+ r7 H5 G0 ?
' N7 m4 [4 t8 V% S" e; o8 N2) 填充按键中断处理函数:读取按键GPIO状态,操作对应LED灯亮灭;; b/ q6 D" M/ Q# b
6 _/ |/ B& |4 [9 V+ b+ {3) 主函数调用LED和按键初始化后,无需任何操作;1 w' O, H/ l8 E$ x
* G0 ~! C8 l" R F0 _& f
本实验配套代码位于“5_程序源码\6_GPIO—按键中断\”。, A0 {0 k+ ?* `% u" H
3 L, ?0 G5 V6 M g; J6 Y2 C1 g ?6 _
/ B" {5 _# o1 x
0 y% o9 D" {) }* {7 n( |, _13.3.2 软件设计讲解9 |; A+ W8 H4 {2 s2 Y/ }
1) GPIO宏定义与接口宏定义3 R) j; T2 K: \( |% J( n( n
% k# k; K0 `% f* |* l5 H- W" o1 s5 n代码段 13.3.1 引脚宏定义(driver_key.h); t7 u6 H9 W: f7 N6 f% x
9 Q$ [$ X1 w6 B. e3 k
- /*********************; d! `* w& n. R5 l( K0 L3 p& T
- * 按键引脚状态定义- @. v: ~6 F" e! f- g' h- u
- **********************/
2 t2 |, s: I% o - #define PUSH_DOWN GPIO_PIN_RESET# ]( G4 S/ k( A8 N7 H; e. h
- #define SPRING_UP GPIO_PIN_SET6 }+ c/ b7 G1 e+ R* E! R/ S
2 J: N5 _8 h3 u. Y' n: v- /*********************
# E4 W. M- f1 W0 k3 k - * 引脚宏定义
$ }& G; U! s8 @- ?8 p - **********************/- o7 N; `$ R4 X) D7 h
- #define KEY_GPIO_PIN GPIO_PIN_0
3 F4 b6 }6 a/ }7 j2 x! \7 c7 j - #define KEY_GPIO_PORT GPIOA
E" e( l/ X4 S5 N# p' H* e Y - #define KEY_GPIO_CLK_EN() __HAL_RCC_GPIOA_CLK_ENABLE()
1 _' ~8 V$ _# B |9 y7 _' g H - # H9 m5 [2 [) x2 w9 z+ V* y. a8 ]; h
- /*********************
% B0 l0 s" R; W* u - * 函数宏定义
0 w! Y0 H& `" |7 D& `; `9 ? - **********************/
' Z& `1 Z( M/ e T - /*+ R8 t O6 B/ `3 r) Y
- * 按键状态读取函数宏定义$ D0 e) u' k1 Q/ R' C8 U
- */
! b. V; r! n1 Z# A/ J c+ r - #define KEY HAL_GPIO_ReadPin(KEY_GPIO_PORT, KEY_UP_GPIO_PIN)
复制代码 $ Y* M1 {) X$ F; l0 A
此处与上一实验一致,不再赘述。 L5 D4 n0 j" z+ I# ?8 {
( Q m1 C- F; b% K# e A; j( j2 X
" `4 a! ~8 |5 U1 l7 l! {% b6 E
2) GPIO初始化9 l* u! t+ N. Y z a( P; j* y
3 f1 l7 S# H- \: r& C2 \
代码段 13.3.2 按键初始化(driver_key.c)3 T+ t# F8 c4 }$ y8 o
J( J$ b \" Q8 S, B- /*
( y" s5 @+ o5 V$ c% ] - * 函数名:void KeyInit(void)
: k8 J# k5 r; R' E" P- q+ L - * 输入参数:无( T U G7 A6 x7 b
- * 输出参数:无& p6 W8 ]1 Q" j
- * 返回值:无
i+ j7 J5 m5 M, g3 x& K - * 函数作用:初始化按键的引脚,配置为下降沿触发外部中断
j/ B6 u* g4 O/ C$ D
: @: r+ c: n5 l0 a, F9 W0 |- T- */ T; c9 W* I Z7 y0 D
- # z( D0 b& A" O, p4 G: c& R
- void KeyInit(void)
; F3 W3 H- W; D! w4 h - {, |7 h7 G8 n# }! o
- // 定义GPIO的结构体变量
4 t" A$ e2 b" u* z& f% b6 p, o - GPIO_InitTypeDef GPIO_InitStruct = {0};
{2 |4 z' `& f+ }0 n3 _( t+ ]# T - 4 f: A L/ E) e k1 d5 b+ I$ h1 r
- // 使能按键的GPIO对应的时钟6 T4 i/ x: b1 E% l- {* g; v6 Y2 g, F
- KEY_GPIO_CLK_EN();6 \' \+ m: z% r( {3 P) ^0 n
2 e) Z8 }1 B6 X# C1 y- GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 设置为下降沿触发外部中断
9 h9 }" u1 @7 p* V4 L4 E - GPIO_InitStruct.Pull = GPIO_PULLUP; // 默认上拉1 n5 {3 o% u- K- J$ Z
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 引脚反转速度设置为快& {) m" c& R; @0 @
- w- K: P. a+ l6 ~ R
- // 初始化按键引脚配置3 W$ p8 e1 q* j1 u
- GPIO_InitStruct.Pin = KEY_GPIO_PIN; // 选择按键的引脚
! j/ j9 F# P2 D5 m2 o- ~" q2 f3 L - HAL_GPIO_Init(KEY_GPIO_PORT, &GPIO_InitStruct);
1 E* C. z; B( l k: i/ E - 6 O5 m9 |% U @9 X$ g- V
- /* EXTI interrupt init*/
5 _3 d; n, _- c7 P" L - HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);7 i) U5 l+ k0 Q$ M3 I( s$ z
- HAL_NVIC_EnableIRQ(EXTI0_IRQn);
3 ^0 V# @6 ^ A" T9 a
' M& U- ^$ d: O- }
复制代码 2 ], x- s$ y2 M2 Q$ ]3 b# }5 z
13行:使能按键对应GPIO端口时钟;& [2 ~* o+ r# j" W
5 Y# M4 v& J- I3 j) {* r, Q/ U15行:设置为下降沿触发外部中断,即按键按下瞬间触发中断。可根据需求设置为上升沿触发,即松开按键触发中断,双边缘触发,即按下松开都触发中断;
( d/ x6 g! v6 z% B T4 [: h- \. e5 t
* F, S5 a" S- [+ D( \$ D+ _4 p+ a16~21行:初始化按键对应的GPIO,“HAL_GPIO_Init()”里会判断该引脚是否为EXTI模式,如果是则调用“__HAL_RCC_AFIO_CLK_ENABLE()”使能AFIO时钟;2 `0 s" L5 I7 }5 [+ `
# P) e9 H8 ^2 F25~25行:设置EXTI中断线0的优先级和使能,即PA0所在中断线;
$ |& v; t- Y* N7 k
- t) Q& u) Y4 j4 t 3) 中断处理函数& B. p6 O5 Y) g& M( {0 [
* g* T" T6 C1 }: I5 ?
当中断发生后,则自动跳到代码段 10.2.1 中所对应的中断函数所在位置,执行中断函数的内容,因此我们需要编写中断函数的内容,如代码段 13.3.3 所示。
0 h0 P: h, S7 j \$ ^" U2 D% Y- ]# g! \) u+ x2 s
代码段 13.3.3 按键中断处理函数(driver_key.c)* X2 p( F% M3 Z: ?& Z* r
7 P8 f- P8 j+ |% g& Z- /*
0 O3 U N. h7 V$ q1 ]% S - * 函数名:void EXTI0_IRQHandler(void)1 r$ f. A$ c" U
- * 输入参数:无
% w( u0 ~& M5 ^% s/ @ - * 输出参数:无
6 N) L5 W X# G% | - * 返回值:无
3 z( |7 v; j' s5 J - * 函数作用:外部中断0的中断处理函数
! O: H9 E- R. [. Z - */
) w8 }( g9 U: `* G' q/ c% s: B
7 _ l2 g! x* P7 G- void EXTI0_IRQHandler(void)7 R9 r" V% Q5 L5 L- D1 G
- {
1 x% `6 t7 u' m1 q+ N+ v- c; L1 a - HAL_GPIO_EXTI_IRQHandler(KEY_GPIO_PIN);
/ H) h- N1 [0 j# ^ _/ n - }
复制代码 |6 d4 G) e, j# e$ L& a2 F
注意这里的中断处理函数的实现,可以放在“stm32f1xx_it.c”中,同时注意在“stm32f1xx_it.h”中声明。本示例放在“driver_key.c”中,方便读者理解。 W& T$ q9 r$ S( D: s4 K$ K
n6 g. Z% w2 I8 p$ N每个中断处理函数里,都调用的“HAL_GPIO_EXTI_IRQHandler()”准备后续处理,传入参数为外部中断的引脚号,该函数原型如代码段 13.3.4 所示。' {9 I3 R# E! l! E* ^
3 B1 Q$ E% e3 l4 P/ y0 p3 A2 E代码段 13.3.4 外部中断处理函数(stm32f1xx_hal_gpio.c)6 q+ ?, L/ v' N0 K, U4 K
8 g6 P+ j, \" @- /**
+ d8 @: |. Z3 [ l( C# C! K - * @brief This function handles EXTI interrupt request.
4 `+ ~2 N3 U9 |% h7 \; \- w. R - * @param GPIO_Pin: Specifies the pins connected EXTI line
4 _8 r- T- Q4 u% l - * @retval None& S! J$ d6 L) m! s
- */$ }# a- v4 V* ?4 `% i
- 9 F* k3 D9 K( D2 V
- void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
/ \2 j5 E! ?) g, s8 f - {$ \% i1 c2 ?# [+ o0 T& J, v
- /* EXTI line interrupt detected */
# @" M* ?) X7 ` r+ K - if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
8 b1 _3 S, a3 d* U - {
! {( f0 f% J: l$ y, ~ - __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
1 T- ?6 H( a2 G; g' a- \* ] - HAL_GPIO_EXTI_Callback(GPIO_Pin);
$ R2 ?& k- B) K, I$ r - }6 r8 O% k9 B9 {; U W! U, t
- }
复制代码
9 V# ], b. y; G- ?" `, _该函数先判断传入引脚是否产生了外部中断,确认该引脚产生了中断,则清理中断标志,再调用“HAL_GPIO_EXTI_Callback()”回调函数。在该回调函数,通过判断输入的引脚,完成对应的用户操作,如代码段 13.3.5 所示。
9 H/ ?7 Z! [3 Z' O( a& H. S9 P0 j! H+ k: i& Q/ u/ E
代码段 13.3.5 外部中断处理函数回调函数(driver_key.c)* U9 K3 [' L5 x6 h# i
8 i: c$ H$ v" d$ ^- v7 R2 g* j- /*
: `3 Y' S% N6 g, V% X% @$ _! ` - * 函数名:void HAL_GPIO_EXTI_Callback(void)
" a' @! a7 p" g$ v4 K9 {* @( h - * 输入参数:无
; {3 s; D, d& d {8 a) \7 }8 y - * 输出参数:无
, H! p' j2 s+ a) E" K - * 返回值:无' @4 {; W- K; ]+ J1 P+ A
- * 函数作用:外部中断处理函数的回调函数,用以处理不同引脚触发的中断服务最终函数$ N1 p* i* j4 F9 n+ K4 J
- */
6 |, S+ w9 L3 s6 J1 b$ W# v" `9 W - static volatile bool key_flag = false; // 定义一个全局静态标志,用以判断按键按下的次数
- e6 `% b) y7 l/ y3 J ^2 B6 d: ~8 o: E - void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
: w% l2 c1 j+ \* { - {
/ Y, S5 b* b( u2 ]# E: y# n$ @* o - switch(GPIO_Pin) // 判断是哪个按键
, l& ~' e8 k6 W/ h( ~+ z - {* m' x* J! Q" z0 V
- case KEY_GPIO_PIN: // 如果是用户按键
* o' F' g% N# `; k - {
- Q+ K1 @6 ~6 ^6 D7 T - key_flag = !key_flag; // 按下一次标志翻转一次
' |$ p. B: v- p4 l9 N& A9 r- A - BLED(key_flag?OFF:ON); // 根据标志控制蓝灯的亮灭
8 S( \) o, H* s* w: m - break;
1 j% [- o2 K9 _) {9 f8 G% T* w* m - }+ o" L/ G5 e% Y7 g
- default:break;
& r" P/ Q0 g; S5 G$ _! i. H( q' t* } - }+ I3 g; B4 y% C9 v1 S2 g: v
- }
复制代码
! c# z$ W) l( z5 R0 k4 |8行:定义了全局变量标志位,用于记录按键按下状态;+ }$ p$ n/ x* w% ] f: y% V
k" `# ~% `- i' p) `9~21行:根据传入的引脚号,得知是哪一个按键按下,从而控制对应LED灯亮灭;
& t, u( Y# b5 Y& }$ b( Q: p" H u' H( y% @+ y$ x- C& t
4) 主函数测试4 G6 a- x* G! h7 s" m! j
& J7 ^0 ~8 V7 o# k6 p2 N) e
代码段 13.3.6 按键中断主函数(main.h)
; d% X+ s2 A p O) k- X6 N9 a. W, A. c
% F. ~4 q. j) Y2 w w, g! m; }- // 初始化按键
+ z+ d) P, D/ w, z* Z5 g - KeyInit();
8 \4 T" A$ p6 i8 C8 z' S
* o& I; p' I* a$ _5 U: d- //初始化LED
( _; d' x5 v: ^, W, a$ \) @- W$ ^$ q - LedGpioInit();
8 ^: `* X+ S7 J' s7 K - $ J P( c% U5 w, X5 l/ I
- while(1)
7 j% d Q5 k) A' \* Q1 m - {$ X0 U) F4 P9 t7 ^% `
- }
复制代码
$ b1 f0 O+ B7 r) K* J 主函数只需初始化LED和按键,无需任何操作。一旦按键按下产生中断,将自动跳转到对应中断向量位置,调用该位置的中断处理函数。读者需要自行填充中断处理函数内容,这里设置中断处理函数最终都调用外部中断回调函数“HAL_GPIO_EXTI_Callback()”,在该函数里判断是哪个引脚按下,执行相应操作。
6 v/ f4 c/ Y* S, Z" |
0 v0 k! F% o: E' N7 C! ~13.4 实验效果
9 ]! v% z \: P- d5 l本实验对应配套资料的“5_程序源码\6_GPIO—按键中断\”。打开工程后,编译,下载。按下按键,用户LEDd灯亮/灭。 ( S5 d3 t' n+ `9 e0 F* v! d
/ m3 }' ]' s P6 z! s$ L/ V
作者:攻城狮子黄
" M0 X& j- z, M9 Z0 S1 M1 E; @
Z3 s O, l( i% X( @, i: ~. B) h# ?' G# X
|