对于按键,常见的是通过MCU不断检测按键连接GPIO的状态变化(高-->低或低-->高),来确认是否有按键输入,比如上一篇按键实验(上)。但是,轮询方式的缺点很明显,MCU一直在循环检测,对资源是一种浪费,不够高效。对于STM32,其 GPIO 是可以配置成中断的,有这个优势,我们就可以通过中断的方式来检测 GPIO 上电平的变化,进一步的得到按键的状态。" F/ e8 g# u% |% ]
按键电路和按键实验(上)的电路图一样,如下:, y3 m$ ^( t' `+ }' u# E6 a, S" U6 i
8 S9 H) u8 _$ M/ T' W; ^
( d6 `, {; L Y8 V2 z
( J5 Y+ \/ e8 h1 L一、外部中断/事件控制器EXTI控制器的主要特性如下:* @; N) V9 \2 o( R& L5 I
●每个中断/事件都有独立的触发和屏蔽;
) B5 ?6 z& ]$ F) ]' X# j●每个中断线都有专用的状态位;) L5 Y" X6 V7 W# j
●支持多达20个软件的中断/事件请求;
" q8 c, ?/ [& N6 a" |6 P! E* q: N●检测脉冲宽度低于APB2时钟宽度的外部信号。 e, s$ p2 S! Q! w2 Q% o8 E/ c
: G/ p4 f, U. _" N二、外部中断概述; e( U: h, t$ s' y
" T' ` C, _' w6 N- {
STM32中,每一个 GPIO 都可以触发一个外部中断,但是,GPIO 的中断是以组为一个单位的,同组间的外部中断同一时间只能使用一个。比如说,PA0,PB0,PC0,PD0,PE0,PF0,PG0 这些为1组,如果我们使用PA0作为外部中断源,那么其他的就不能再使用了。在此情况下,我们只能使用类似于 PB1,PC2 这种末端序号不同的外部中断源。
' w8 f, U% v x" i2 m& j
' g- d/ p7 c7 K# L
% ]7 g. h4 V! ?2 X# ^ b" \; u
9 r) q6 v! B7 {9 [STM32的中断控制器支持19个外部中断/事件请求:
1 M# ~6 d) R5 E: h. d
; U' d- y# b9 T# q! ?8 b) w线0~15:对应外部IO口的输入中断。5 b: L: Q. r6 R: j8 u
线16:连接到PVD输出。$ F7 c+ d7 Z4 d: r4 T) d9 \
线17:连接到RTC闹钟事件。
4 p6 i- F; j' {! F# e; q线18:连接到USB唤醒事件。
' Q. K5 ]; M* `
) f* b9 v( [/ a9 s每个外部中断线可以独立的配置触发方式(上升沿,下降沿或者双边沿触发),触发/屏蔽,专用的状态位。
) H6 R1 N/ G' G2 a6 U每一组使用一个中断标志EXTIx。EXTI0-EXTI4这5个外部中断有着自己单独的中断响应函数,EXIT5-EXIT9共用一个中断响应函数,EXIT10-EXIT15共用一个中断响应函数。
5 w/ I; O1 f9 o* E% P
, t3 z% ~/ ^- b& y小R接着上一次按键实验进行补充,因此跳过新建 key.c 和 key.h 两个文件,以及把 key.c 添加到工程中。% k0 o/ Y9 ?0 H& T
) R2 k: v0 Q% _- u0 C) _4 p
在 key.h 中补充以下代码:
! P0 `) c9 D9 Q& X0 Y' z/ i
6 g' C, M8 M) f+ s6 @
- r6 _ ]: \. ~) p7 U* l& T. `3 |+ h
在 key.c 中补充以下代码:" g4 ?% ?6 ~: Q3 P8 D
" t$ T9 e- ?: r! F6 g- H* f- void KEY_Init(void)) K3 Y, r; V* G, ?$ v: T; z0 ^
- {/ E/ C, s2 I2 X$ M9 X6 i( X; H
- GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO结构体: C0 C) O3 m# C( e
- EXTI_InitTypeDef EXTI_InitStructure; //定义外部中断结构体6 p9 t4 I/ j4 f* n, T
- NVIC_InitTypeDef NVIC_InitStructure; //定义中断优先级结构体
5 h- L3 l# N* } - ! }% L$ V8 A6 _* ?7 c! L C: s
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置中断优先级分组7 R6 I0 h; E2 _
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能GPIOA时钟
8 B) [: g5 M- i/ O f - RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //开启复用时钟
! h7 o5 I; d$ _$ D$ u - ) Q( b+ n- \+ f- W: Z
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //定义PA.12
5 r: y+ ]# \/ I2 ^ - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //输入上拉: Z! H$ v- g- F% k z, \$ x8 Q
- GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化GPIO8 B! G9 d8 m, `
- & J4 q; s0 u2 r3 ?/ l
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource12); //PA.12中断线映射
- l6 Q, p5 B! W1 d& [+ `! p, s - EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //外部中断模式% J E' r1 d: k
- EXTI_InitStructure.EXTI_Line = EXTI_Line12; //中断线12
0 w: m) V" `6 [, f6 h t! E! D, t - EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
$ @3 d5 f7 a4 o& g - EXTI_InitStructure.EXTI_LineCmd = ENABLE; //中断线使能3 B7 c) r9 Q7 A9 Z% m
- EXTI_Init(&EXTI_InitStructure); //初始化外部中断9 n9 g* a' Z. @, i0 Y! X9 B7 f3 |
- . K4 L5 c1 G* K5 a% I$ J% B
- NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //配置外部中断通道
% G- {1 m' t* N% N; ~2 Y% s - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //设置抢先优先级为0
6 o# w% n0 _4 h$ N: A - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //设置子优先级为1
( Z8 o! c9 Y- e3 {# B& N ^ - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道% z" V+ m" j( G2 ^" [+ |
- NVIC_Init(&NVIC_InitStructure); //初始化中断优先级
. V" W* f9 F d7 |" a3 o( X - }
复制代码
\( O% t2 Q( N1 V/ r. o2 v1 I6 b
5 s" {/ Q+ t- @6 u( n- v0 W; K, m
; C; M/ Q0 I# ]实现按键中断控制LED灯亮灭1 s$ b0 ], x% {
% k; f6 c. t# v+ b5 `9 u
- #include "stm32f10x.h"
5 t' Q. z+ G. s9 }0 P, e B - #include "delay.h": |. s0 g" N/ C& t5 i) S
- #include "led.h"
; g7 ?6 ?& Y' D - #include "tim.h"( h i1 d9 ^( C% ^
- #include "key.h"& X: k4 I; X, H- T
- ( P$ a! p+ e% f" A/ ~
- int mian(void)
$ k! F0 U: V& E6 X1 [, t - {
i' }) |' j$ U+ N* V+ ~3 `# p - delay_init();7 u2 B3 P; X. T
- led_init();
8 x; ]) z6 {4 K* O - KEY_Init();' x4 o ?7 w9 q1 F
- ]( V- |% d) H* I
- while(1)
% C2 {: D+ m1 d - {
- y# A. X1 d9 w3 a8 O, ]) k' {$ S - }0 t% ~7 C- f$ h1 u; D0 W. \
- }
) P; P: P! Z% H1 g* R& D - 1 v1 m4 }) a5 ?7 C1 y" H
- void EXTI15_10_IRQHandler(void) //按键中断服务函数# h# v2 h' i! t! |7 K
- {! b% J+ H0 [' R) I- @( U# t
- if(EXTI_GetITStatus(EXTI_Line12) != RESET)6 Z! b. P; [: {! P( n) _
- {2 S( [" V1 n. N% P( I9 m: T; _
- if((GPIOA->IDR & GPIO_Pin_12) == RESET) { //检测按键是否被按下
/ d+ R! d& w) E3 F - delay_ms(10); //按键消抖10ms
7 y2 N1 ]* H2 Y - if((GPIOA->IDR & GPIO_Pin_12) == RESET) { //再次检测按键是否被按下
+ f$ x" u) [8 A/ T - GPIOC->ODR ^= 0x0001 << 13; //PC13电平翻转
" H( g- }$ S/ g& E* ~1 b! A& ~ - }
/ k; ` U: K* r; A' u - }5 U- p$ T* Q: o* F% I2 c a* M
- EXTI_ClearITPendingBit(EXTI_Line12); //清除中断标志位
. w2 a$ K: y+ O* _& z, ~ - }
E& ^5 j! K7 V, F - }
复制代码
+ m( w/ T2 ^" U在这里需要说明一下,固件库还提供了两个函数用来判断外部中断状态以及清除外部状态标志位的函数 EXTI_GetFlagStatus 和 EXTI_ClearFlag,他们的作用和前面两个函数的作用类似。只是在 EXTI_GetITStatus 函数中会先判断这种中断是否使能,使能了才去判断中断标志位,而EXTI_GetFlagStatus 直接用来判断状态标志位。, k' }- g7 n( m/ R
) `( z* L* C+ t) U& E
# w/ m3 s- @- r1 k8 g小提示:如果身边没有按键面包板搭电路,可以使用一条杜邦线完成实验,杜邦线的一端接PA12,当另一端接GND时代表“按键”被按下。$ f; ]7 ~! [! X- R$ P" S$ S6 u
5 B( K% [$ \! C5 C4 v
8 M! ?1 I+ v8 N |