前言, h4 R6 O+ `& x
本章讲解STM32的另一个重要的外设资源——外部中断;在前面已经讲过了NVIC中断优先级管理,相信大家对STM32的中断系统有了初步的了解,知道了如何给每一个内部的中断设置顺序,然而只有内部外设的中断信号还不够,有时候我们要监视外部的数据信号变化,这就需要外部中断了。
+ L2 Y( _* ~. t7 s8 ~
. m I9 f( D+ E" P0 F* D一、外部中断是什么?( t" Y+ @" F# k$ P' l5 X
首先我们要理解什么是中断。
7 v$ S/ r& X+ q# @+ M. \4 d$ `; P V" j: h( N% T
想象一个场景:你在家里看书学习,这时候突然来电话了。你的做法是停止学习去接电话,电话打完再放下电话继续学习。这一个过程就体现了中断的思想。我们抽象一下,把自己想象成一个单片机系统,看书就是正在执行的主任务,如果没有外界干扰,这个系统会一直执行当前的主任务。而当外部有改变(电话响了)的时候,系统会第一时间响应,从主任务跳转到这个紧急任务中,这个突发的事件就是(外部)中断,而系统处理的紧急任务则是中断处理函数。当紧急任务(接电话)完毕之后,系统又会回到最初的主任务中。4 j( l6 B9 ?1 } m! ]
4 y7 N- [% N3 U# [这就是MCU的外部中断过程。
9 p7 c( B3 v( R4 T5 i& h& \: \7 ?+ i5 r7 O
二、STM32外部中断概述/ f/ G$ ^5 B4 v( c3 ~
1. STM32的外部中断线
2 {- c: y) t0 q& @ |2 _$ u7 ?STM32的每个IO都可以作为外部中断输入。
2 R' D' g1 _7 ^* [4 _STM32的中断控制器支持19个外部中断/事件请求:8 C2 j' H; W0 l- w' F) j
线0~15:对应外部IO口的输入中断。
+ k4 k! n! v" T5 a& I' K' B8 J" \6 m- n2 B
线16:连接到PVD输出。
9 A* C% Y* i$ n1 Z7 q8 ]1 }
2 S/ M& z8 s6 i/ B$ Z/ ]/ }线17:连接到RTC闹钟事件。
" d+ {. _! \& }+ \( p2 n+ M p6 Y% W* \0 s: |2 g
线18:连接到USB唤醒事件。. ]0 c) o, @, y& f3 E+ L
% ^- t6 k; l2 R/ a" y( K* o
每个外部中断线可以独立的配置触发方式(上升沿,下降沿或者双边沿触发),触发/屏蔽,专用的状态位。/ @$ c2 e' n) }7 D# v# a
# v1 ]1 M9 N! D% ^% |
2. 外部中断线与IO引脚对应关系 , ?& Q( }- a2 l4 z- I/ N0 G
这时候想必读者可能要问了,STM32供IO使用的中断线只有16个,但是STM32F10x系列的IO口多达上百个,STM32F103ZET6(112),STM32F103RCT6(51),那么中断线怎么跟IO口对应上呢?下图就是STM32的外部中断线和IO口的对应关系:; q. I$ R* x5 j1 q
$ M/ S. {6 i. a1 F: c" z# Q8 j$ O3 H
7 X& M6 K" m1 c+ o- n- [. u- Q
# a; i. L6 k- j1 [/ Z
对于每个中断线,我们可以设置相应的触发方式(上升沿触发,下降沿触发,边沿触发)以及使能。: v+ [4 d7 {+ @6 o* C" N
7 E; B0 q5 U0 S! \9 L( m
3. 中断向量与服务函数5 M: v% y$ q8 Z& ?4 S( H
是不是16个中断线就可以分配16个中断服务函数呢?
/ u7 X3 C' W- H% l: f/ f) F/ x3 t& w! c2 b m
IO口外部中断在中断向量表中只分配了7个中断向量,也就是只能使用7个中断服务函数
0 \! Z$ S& \3 Q5 b$ d# E8 c1 U: ^0 J
6 d5 l3 @7 t j5 \
" Z! u" x! M6 V3 A0 B从表中可以看出,外部中断线5~9分配一个中断向量,共用一个服务函数 外部中断线10~15分配一个中断向量,共用一个中断服务函数。 Z% b1 |8 ^- v
下面是中断服务函数列表:+ |) O# u" U. ?
$ p5 M; \7 d [: ~; l7 r
EXTI0_IRQHandler
5 ?% E$ }- Q3 g& [$ O
- M. G, `$ ~- s- ]9 o& u- PEXTI1_IRQHandler' e! A3 a' W# \
. l1 T D# U W
EXTI2_IRQHandler
& _# {9 \ c; V: J% M& k- A# @% M" q$ V: o* K2 I7 P* E
EXTI3_IRQHandler # I. ~4 S$ F- H- i
8 w0 }" N, ^ c0 E+ E R- A+ nEXTI4_IRQHandler
2 l: o3 K7 ^3 g. E, N5 m+ |5 K% D- B
EXTI9_5_IRQHandler 4 e3 q6 R$ m& C: h# E
6 l. Q# Y7 d8 l0 p* v
EXTI15_10_IRQHandler ' V9 m+ r* v# P7 x7 t( }( J
- ~: @- ?' C7 H+ M- V1 q三、STM32外部中断库函数配置& q3 f% R8 L5 }* e/ D7 d% j
1. 常用库函数
b+ b. _! ]. l8 W% B$ z①void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);6 T4 ` g& V4 w0 c
9 k+ K' P- [5 M% ~ //设置IO口与中断线的映射关系3 {; L( r: ~" N: y
" x2 `9 D4 T4 P* u! _* _5 S6 C& `
exp: GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);- S# S9 h# q; }
( h; k& f) Y0 Q$ w0 i' l& }②void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
2 ` W) h/ x$ x6 t, Q
* \% V5 i4 o, c$ u1 B //初始化中断线:触发方式等
) [. {: K! |3 b( u2 J4 s" W1 J' d# f' d
③ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
7 A" t& ?/ n/ m. w% u' A2 ^
: a3 V' |; f( n2 k6 J$ @//判断中断线中断状态,是否发生
; P+ S: F! M0 |6 }
, ?' W9 o# S- b- x④void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
# m% _( |4 C4 v9 H; [" w; Z! w/ {; _: ~' V. k
//清除中断线上的中断标志位
* g$ l0 e" Q% E1 x! ]) d+ B! j+ V. d+ ]1 u! ]/ L5 j
以上这四个函数是配置外部中断常用的库函数,第一个函数很好理解,就是配置IO口与中断线的映射关系的。我们重点讲解第二个函数:
' I# R9 d4 N8 L. T, w# A& c
5 s& W. j+ l# C, s: HEXTI_Init函数:
; X7 m; A/ l" d5 dvoid EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
/ i: Q7 |& S0 H2 ?' G, D" r6 Q6 |0 E6 G6 E0 y; a2 @" g
其结构体成员变量如下:% M+ u4 k u& P5 p0 e) [* Z0 k) Z
7 f0 u' x9 }# H, {
- typedef struct/ c9 L* `. p" E( a2 N, u5 A4 S# m
- {4 i% e2 Z, ~' e2 f) _2 C
- uint32_t EXTI_Line; //指定要配置的中断线 ' W( G# g- a6 u9 ?
- EXTIMode_TypeDef EXTI_Mode; //模式:事件 OR中断
: Y, T( Q# O5 p( F - EXTITrigger_TypeDef EXTI_Trigger;//触发方式:上升沿/下降沿/双沿触发
7 z5 K5 x! G% X2 ^7 z - FunctionalState EXTI_LineCmd; //使能 OR失能3 t) k& z8 S2 f/ M
- }EXTI_InitTypeDef;, [/ f) ]1 {8 ~$ h' e# Z2 \
# y7 ]( D1 u" u- //实例:
8 @' k+ x0 V6 Y - // EXTI_InitStructure.EXTI_Line=EXTI_Line2;
* O3 h( F0 @% Z1 U - // EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
; q2 s4 T* J6 U8 b0 D5 q1 e - // EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;% s' m, Y" S, ^& M* g M
- // EXTI_InitStructure.EXTI_LineCmd = ENABLE;4 A" K }- `) U' O
- // EXTI_Init(&EXTI_InitStructure);
复制代码 7 j1 B, `6 i' S; m4 @* O
可见由四部分组成,分别是中短线、中断模式(事件或者中断)、触发方式(上升沿、下降沿或者双边沿触发)以及使能位。+ y6 v9 _6 i6 j v% {7 n$ r& Q
) L4 W/ i6 {& \/ Q- I; f; \! X2. 库函数配置一般步骤
0 q7 T% i6 p5 x1 L: V①初始化IO口为输入。1 S+ i! R3 t: R5 q
GPIO_Init();8 j. w3 H5 ~- v, R5 d+ s! c
( v6 ~% Q. n9 r6 }2 ~$ u$ Z: L/ }
②开启IO口复用时钟。
) R( W; z, n( @* s; R- l5 U9 s0 z% B g; Q/ K
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
* m: p$ } I0 g" b" p1 v
# v6 K J( H, R! M6 p③设置IO口与中断线的映射关系。- z1 o) a) L; W! O* X( X7 J9 N
void GPIO_EXTILineConfig();" X/ m* }& s J
5 i) P1 k5 |& T+ j, B; k3 N
④初始化线上中断,设置触发条件等。
! a; c' N9 b0 y5 ^. M1 N EXTI_Init();: o$ J6 n. W# e4 W4 A+ V
+ E' v6 U: |, t; c0 U9 k. j" c
⑤配置中断分组(NVIC),并使能中断。" j9 Q! v0 K6 C q( ~' s% @, h
NVIC_Init();: e U' W3 A _% E' ?3 u+ y, W. d" i
/ a- T! m/ X7 \0 ~, y
⑥编写中断服务函数。
4 U/ @* K4 o. \2 n J) {
; O9 h' D2 e& x/ j9 q; g& B+ N EXTIx_IRQHandler();
( U( m9 Q- _: _! q7 z$ ?
/ |: ?2 W2 [4 ^6 L, v/ n1 Y+ a1 t⑦清除中断标志位
3 [' {' f, D) h0 m9 @7 P EXTI_ClearITPendingBit();
9 h. V1 z/ x& u/ M
5 X* n+ `( F5 I% A& ]; ]下面是配置外部中断的代码,其实现的功能是通过STM32单片机的外部中断0来捕获按键的电平变化,使按键按下时LED灯反转,按键和LED的源文件在前面的GPIO那一章有讲解。/ e( C. T L* p* r3 S) h
: `4 q" n6 z7 k- //exti.c源文件
D) ^: I3 o, f J, L9 A" k - #include "exti.h"5 l! z' d" B# w+ c+ ]( v
- #include "led.h"* q; r$ b. v4 `: J; m7 u, ~5 i
- #include "key.h"+ t8 [1 ~! M" D* F
- #include "delay.h"
: F: S: b& d8 ]! ?
# \; }2 o- s6 Z8 O! i. L! d- g- //外部中断0服务程序/ Q6 r$ ?- R( y
- void EXTIX_Init(void)+ {- F. u9 z8 M5 t+ f
- {7 p- B0 H- W6 o* ]/ D; e$ c& A6 D" n
- EXTI_InitTypeDef EXTI_InitStruct;' x+ B/ m' {& r
- NVIC_InitTypeDef NVIC_InitStruct;
' U- a7 q# |6 U8 ?: w - ' z$ y( y; e: F0 Q2 z+ T
- KEY_Init();, h; s7 P! _6 I4 ]. [1 U K( F$ o
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);7 ~* Q/ k* K9 w. f$ D2 Z
-
$ z) P+ ^: f6 [+ j - GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4);, o+ B. K) G7 h
- ! ?7 I: l2 I1 }( H. I) N
- EXTI_InitStruct.EXTI_Line=EXTI_Line4;
0 C- Q! O. D( R$ [2 [/ \ - EXTI_InitStruct.EXTI_LineCmd=ENABLE;
7 ~. c4 o: ]7 J9 L) C' U6 P Y; o - EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
5 ?0 l8 M, w' a0 B& f' Q - EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling;% H0 R% a9 y! X) x( k2 r
-
$ {& |$ j, \1 R% s" F5 d4 P. L9 L - EXTI_Init(&EXTI_InitStruct);
! X/ O. _3 C: `, a" d* T- c: u8 _" y - & z4 ], D/ d2 t- c" h* c& r1 N
- NVIC_InitStruct.NVIC_IRQChannel=EXTI4_IRQn;
. I+ H; F* S- R# |; ? - NVIC_InitStruct.NVIC_IRQChannelCmd =ENABLE;
1 i! x j0 e9 I8 J; M6 @ - NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority =2;' {* Q W' d1 C# I+ u
- NVIC_InitStruct.NVIC_IRQChannelSubPriority =2;/ K& A: o9 @7 |4 ?7 l3 h
-
5 ?2 m% Y* U" x" [% N% e - NVIC_Init(&NVIC_InitStruct);; v% B$ ^1 A! X1 ?2 P' w8 c Q$ J
- }
X a" d/ B6 M. O# z. H0 \ - void EXTI4_IRQHandler(void)
1 F- t8 A& w5 x$ W8 }3 u - {
/ [/ [0 q$ J/ I6 J; B& s. I0 h - delay_ms(10);
4 I c% j4 c, l7 U+ U# f - if(KEY0){
' H4 B6 T9 n A( l# O+ x, a% Z - LED0=!LED0;; V' l. o: A" Q* w$ m6 Y
- LED1=!LED1;
6 y7 d" y. E3 e: | - }
3 m$ o& u6 E0 Z5 b& m$ d - EXTI_ClearFlag(EXTI_Line4);
1 r+ D/ ~" z1 ]+ G - }
复制代码- //exti.h头文件 \3 z3 W6 z2 z
6 o% Z2 d0 O: V- m; d4 q2 G- #ifndef __EXTI_H
+ r& \3 {. n! h# t0 L8 X* U7 }9 @! J( X - #define __EXIT_H
: y. _# _0 X! ^. V- t - #include "sys.h"
' D4 H: m5 u4 c1 E, Z - 2 j7 `0 H- W2 y C* \, j
- void EXTIX_Init(void);//外部中断初始化
# d& h; X B6 ?" x8 t - #endif
复制代码- //main.c源文件2 P" w" S9 W: {! A
- 9 M$ v) z+ x, ^3 [9 A
- #include "led.h"
5 V q4 o, f/ _/ f0 ^) _1 w - #include "delay.h"
, f! x5 U6 v% t8 a) V6 U - #include "key.h"
8 ^) Y0 \. m4 p - #include "sys.h"
8 M; u7 M9 j8 \5 A2 U F - #include "usart.h"& z" J7 ^6 r/ g+ y
- #include "exti.h"
1 S6 }! h1 T* s - 7 ~& m. ]: k" _0 J+ U9 n& Q
- int main(void)
3 m3 f$ `, g: Q( G' `! H* k - { 6 k! {, ]' m1 v8 {
- int i=0;
. E" T; I" C6 Y1 S4 V - delay_init(); //延时函数初始化
9 C. T$ }9 n) v. }$ q A6 S - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
. C0 o* }6 G3 b6 W( I2 F - uart_init(115200); //串口初始化为115200
6 X7 E8 \" [0 M- U - LED_Init(); //初始化与LED连接的硬件接口' K- u; z. t7 \0 \0 T' ]
- KEY_Init(); //初始化与按键连接的硬件接口+ n+ M8 Y% Y' [" a
- EXTIX_Init(); //外部中断初始化
; X* R$ W% l0 Y8 F1 }# {/ k - LED0=0; //点亮LED0
; j+ _7 p9 Y4 u# D! w# r& K - while(1) n' t7 h$ C! e4 T( s7 ^# w8 N
- {
0 P1 e( y1 q) t2 O8 e8 \ - printf("%d\r\n",i);
" N: V6 ~, Z6 D4 d8 \ I - delay_ms(5000);
. A0 s% @/ L7 C. Z+ F3 G - i++; : Q# d$ R4 B k1 q* y. i* _/ a7 ]
- }5 P3 o" m7 m8 V+ r& ]+ o* \
- }
2 V9 a: j# B/ N; O - / M5 s/ R% L/ h% c5 X/ u* z0 B9 j' |
- % d3 G; K4 D7 V- @& l4 L% Z w
- 9 `3 z! |! O* k+ K/ o
1 P* X/ A$ A4 J
复制代码
4 b/ D8 O3 e$ }/ i. a: }- U总结
' o7 B# v' i; G3 K3 f3 [本章讲解的是STM32的外部中断,通过外部中断的原理、STM32外部中断概述以及外部中断库函数配置流程来详细讲述了STM32的外部中断这一外设,希望读者能够认真学习。2 u& {+ K: p {3 r' ^
|