一、什么是中断
! k" W9 \/ Y- f: l首先介绍一下什么是中断。在实际开发过程中,中断是很有必要的。比如需要针对某种特殊情况进行快速响应,单纯的使用一个while轮询似乎并不能满足。中断的概念非常好理解,举个经典例子。比如你在家里看电视,忽然有人敲门,你临时把电视暂停了,转去开门。开完门之后再次回来继续看电视。中断也就是这种流程。看电视的行为就类似于程序中main函数的while,轮询执行业务。忽然有人敲门,对应程序运行过程中忽然产生了一个中断请求。此时暂停电视,对应于此时程序中断当前的业务,转而去处理中断业务(开门)。最后,中断业务处理完成后,再继续执行main函数while轮询中的业务。简单用一个图来表示一下
" E6 |. t& \% \ j. D' L o' ]. {& B$ Z* {8 \; ~
$ K9 R1 e% v4 c9 f. G4 ?* L- e5 f9 f$ U) \- g
中断概念示意图 8 ?* L$ @' }; W: u
! M, C$ Y4 R1 n7 E0 u; y
根据中文参考手册的介绍,STM32F103ZET6除了一些特殊的中断外,常用的中断有60个,这些中断是通过中断控制器来有条不紊地分配执行的。8 t5 x) k8 n* n
2 j# |0 D: o C
二、中断的相关概念! Z# v) ~6 }- P& y# l! u+ V
2.1 中断优先级- U+ I8 ~( ~5 y. O) w) {. i1 Q4 c& W9 M
从字面意思来讲,优先级用来区分中断的响应顺序。当同时接收到多个中断请求时,中断控制器会根据中断优先级来决定中断处理的顺序,优先级高的会先被处理。如果在处理某个中断请求时又来了一个中断,这时会根据两个中断的中断优先级来确定处理方式。如果新来的中断优先级比当前中断的优先级高,则会停止对当前中断的处理,转而处理新的中断。反之,如果新来的中断优先级比当前中断的优先级低,则需要等到当前中断处理完成后,再去处理新来的中断。; w9 u' F. f& K% V! o! G, l
* c- t. l# x( V, q. B1 Z, g3 d中断优先级有两种,一种是抢占优先级,一种是响应优先级。响应优先级通常又被称为“亚优先级”或者“副优先级”。当两个中断的抢占优先级相同时,用相应优先级来决定中断的处理顺序。如果两个中断的抢占优先级和相应优先级相同,则根据芯片手册中的中断向量号来决定中断的处理顺序。比如同时来了两个中断请求,在抢占优先级和响应优先级均相同时,中断向量号为41的中断会比中断向量号为42的中断先被处理。, D `3 o4 u5 a* k# ?/ s
9 h& ?/ V9 j k* e& z. k
8 ^& b9 p8 U% z i9 h" q7 tSTM32提供了16个可编程的优先等级(使用了4位中断优先级),优先级分组可以使用库函数提供的NVIC_PriorityGroupConfig()设置。& N- H. C4 e% X" E5 \/ y( e
0 z6 x7 x% I- }" V2.2 中断嵌套% d# m; W9 G1 C y* }' d
一些低优先级的中断可以被高优先级中断打断,这种情况叫做中断嵌套。) F. p/ o% r3 A' E/ I! _9 v
9 {6 U: Q' a7 c3 x; q9 Q2.3 中断服务函数
1 v) u7 `+ O$ A' T: A中断服务函数就是在进入中断后需要执行的内容。
8 f. E: j6 ?& o$ u4 G' J
8 S. N" {/ Z/ I3 s中断服务函数有特定的函数名,可以在下图文件中搜索“IRQ”找到。) ~' J7 a( l/ U1 X: s
7 o- D0 O5 Z. w+ g, }
8 A, T( c6 `% ^/ u- i
% n/ \% `/ P4 }% B$ D X- C中断服务函数名
o* q6 N, t1 ~9 ~ q; `; z: l/ x9 q9 q+ G5 m3 v- D) v
$ W8 F' Z0 Z( f
2.4 中断标志位- H8 W _+ [7 A. z% [
不同的中断会有对应的中断标志位,通常标志位默认值为0。当产生中断请求时,标志位被置1。比如设置一个串口接收完成中断,串口接收完成标志位初始值为0。当串口接收完成后对应的串口接收完成标志位会被置1。在中断服务函数中检测该标志位的值,来确定是否是串口接收完成中断产生了。每次中断服务函数执行结束后,需要清除一下对应的中断标志位。; W! Q8 n) X8 Z1 L& { C
! R! ~7 q5 {/ C& {' f
0 K; ^7 x! f0 v+ i三、外部中断EXIT
* t9 k, O8 i8 W* V3 vSTM32F103ZET6有一个外部中断控制器(EXIT),可以支持20个软件的中断/事件请求,其中外部中断的EXIT0~EXIT15同坐IO中断。
( l; L" x; ~9 C- S8 h# t/ T8 K; s
6 x. P4 |- m* c1 v* K& m
9 k" [5 ?! `( c7 d外部中断IO对应 : Q0 A. W \5 G# ^
" V+ u# O9 J0 t+ Z @* S$ N! B
( M3 g# }. F& c) f! A6 \9 H其他详细的介绍这里就不再说明。
/ c( c* R- D0 i4 {4 H: ?, }. \
( b8 c! T# t- D+ ~+ k2 K7 T6 I9 y' j6 |- C7 M/ t3 d; W0 n: ^5 E" Q' P
四、中断程序配置* {: \8 F3 ?! F$ p+ A
这里以配置PA0(按键WK UP)的外部中断为例,展示一下库函数开发时,外部中断的配置流程。关于其他中断的配置,后续使用其他外设时会单独介绍。7 | c/ \1 \8 q- H: M- s0 s8 h6 g7 p
+ x$ m' m7 W R: q$ [1 z5 x) X想要实现的效果是,利用外部中断实现按下WK UP,LED1点亮。' e% I5 A$ Q+ l& `* i
& |+ v6 t' j6 F; Z3 ^4.1 设置中断分组并使能中断
0 q) A- K R+ H! j这里使用外部中断,需要开启AFIO时钟,设置IO与外部中断线的映射关系。
: E* I% R& O% F" O. j- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); // 开启AFIO时钟
% c" _, P+ `* I9 c8 w1 X. U7 z
v+ K2 u9 b. z0 S- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); //选择GPIO管脚用作外部中断线路
复制代码 + G8 W! J1 g" @0 H& n+ @! K
设置中断分组并使能中断时,库函数提供了一个结构体,我们直接配置这个结构体就可以了。5 J7 Y( t3 V3 i+ ~
- //EXTI0 NVIC 配置
' S" Y& W* A; \ - NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //EXTI0中断通道
+ Y/ n0 w! Q( t, h9 J' f$ _ N - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级
8 |( F. D: [6 W- G" b. C - NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级) N7 d3 b# S) E" }
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能/ @" d+ n* n" O
- NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器0 X6 p( r2 S4 v
复制代码 # `4 Y3 i; A4 e b: I/ B
需要注意的是,配置优先级时,数值越大,优先级越低。. \9 R @+ A/ G) r+ L% d& l
( j/ h" u' G! ~9 m" l k4 m( ^- I4.2 初始化EXIT
: f* D( I* B5 u7 I" {* _7 _初始化EXIT时,库函数也提供了一个结构体,其中包括中断线,EXIT模式,触发方式以及EXIT使能或者失能。由按键检测一节了解到,WK UP按下时,会产生一个上升沿。因此触发方式我们选择上升沿触发。
! {; x- w* H0 e- EXTI_InitStructure.EXTI_Line=EXTI_Line0; // EXIT0
$ s! j7 L8 e) M( D4 ~ - EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt; // 中断9 F2 L T4 T; n; q
- EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising; // 上升沿触发
% g6 a% D5 J5 a- l - EXTI_InitStructure.EXTI_LineCmd=ENABLE; // 使能7 }# t! r, b$ W' S I- s- N" h$ r
- EXTI_Init(&EXTI_InitStructure);
复制代码
6 l5 H- B2 ^' V0 [- Y, j" S5 P整体配置函数如下0 o( c% {; k8 s, c | r" T- M( D3 f
- /*
( N c1 w; f% z0 u: m; L - *==============================================================================. e9 z2 D; _: C! h5 ^7 S6 N
- *函数名称:Exit_Init
" `- B2 b) U Y, q$ a - *函数功能:初始化外部中断* s) I# ?# B$ {7 d; Z8 T
- *输入参数:无
. r# i4 z* ^5 _9 q* c - *返回值:无
3 ^, ^; T8 @4 R4 m - *备 注:无
! v \+ V5 J8 }2 j4 X% F - *==============================================================================6 `. f1 b, j. h! J. `2 j4 |4 v
- */
4 K) v8 V8 {, ]: [* t - void Exit_Init (void)
! k0 T' u w$ s) D- I A% k - {
~7 a# u# c2 M$ v - NVIC_InitTypeDef NVIC_InitStructure;
" R& ]* p: E- _9 f/ v( T6 r - EXTI_InitTypeDef EXTI_InitStructure;( J1 t% Q- b9 I) I
- # m5 x$ V2 D0 r. x) T4 T
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); // 开启AFIO时钟
7 A4 i* @) k, I; q- U - 9 O2 T+ V3 m$ B7 E& k u" b* y
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); //选择GPIO管脚用作外部中断线路
7 L# n* `6 s. w _0 _; [ - ' }) s6 {- b: V9 \% @# c
- //EXTI0 NVIC 配置5 u: p _" m9 R1 C0 P) y
- NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //EXTI0中断通道
R# e V6 a0 Y5 g4 b. f1 n - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级
; a$ i4 {2 W2 M4 [ - NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级; ?% e/ E% H! w& |% T* g) G
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
0 p6 h# k3 `) X' k# Z [7 F - NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器1 {' Z; p8 s7 e. X v* [5 u7 @
-
& B |" v$ ]: L. N! ]* |* \ - EXTI_InitStructure.EXTI_Line=EXTI_Line0; // EXIT0
' S/ }0 _# X2 W9 i) ~+ E - EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt; // 中断; Y" `& m) |& i z- I/ v
- EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising; // 上升沿触发
& U1 C# E" b8 g - EXTI_InitStructure.EXTI_LineCmd=ENABLE; // 使能
& C( u- G0 H( J - EXTI_Init(&EXTI_InitStructure);2 N; f8 I: J- T, {- W' i
- }
复制代码
: |' ]( p( m/ ?3 j2 @! b8 p4.3 编写中断服务函数 a5 I5 P* Q/ h. O) Z O' d
上面介绍了如何找中断服务函数的函数名,这里直接开始写中断服务函数。这里的中断服务函数比较简单,直接点亮LED1即可。- n8 G* b$ L' O" `/ \" x' G. ]# C
- /*, ] ~6 S* B7 r6 A" O8 U
- *==============================================================================) S: @+ q, ?% ]5 h8 m
- *函数名称:EXTI0_IRQHandler
9 w& x& I. R* K" S) s. U' {( [3 R - *函数功能:外部中断0中断服务函数
4 E3 q* o* @9 I& q& U3 @$ c% y% a - *输入参数:无
0 b7 V4 G6 o; C - *返回值:无
, Z: J: d; f8 N$ B, q6 V - *备 注:无
( H7 T* m% _9 j! i1 m7 j# s - *============================================================================== f2 C3 X! a; K- ]
- */7 B- |- K! \" `( J: ~3 D3 [
- void EXTI0_IRQHandler(void)
! j) e% v( h& h - {8 Y4 \4 W9 p! ^. r. ?
- // 如果EXIT0中断标志位被置19 t% N [! r$ j4 r! u
- if(EXTI_GetITStatus (EXTI_Line0)==1)
8 i6 S( I" J; y$ W) u& x" i - {
6 V Y3 ?1 M; i8 j - Med_Led_StateCtrl (LED1,LED_ON); // 点亮LED1$ w' ~1 I$ I9 K/ ]. s
- }
s o! H) v( D( M - EXTI_ClearITPendingBit (EXTI_Line0); // 清除中断标志位
3 c6 y; g) z1 Q' H1 j0 b - }
复制代码
$ A9 ?$ S% }3 x) q/ O至此,按下WK UP后,LED1会点亮。这种方法与之前的按键点亮LED有什么区别?之前的按键点亮LED是在main函数的while中实现的,而利用外部中断的方法,是在外部中断的中断服务函数中实现的。即使main函数的while轮询业务中没有按键业务,按键依旧可以起作用。5 `8 g5 Q- U# {9 z
. M7 G7 s3 Y H2 t
; x# ^4 ?4 ~. d5 |1 _五、注意事项
4 i% g% c( W3 `& \# v) u' h5 Z$ [• 中断服务函数无需在.h文件中声明
) o: f, p8 z1 T8 Z+ I• 中断服务函数中不要有过长的业务* H( A1 S0 P0 f$ x5 u1 y& ~- B5 M
• 中断服务函数最后需要清除中断标志位
, j0 G8 k L, \6 t2 ?$ r7 Z6 C
: w6 r4 r* d% q9 R# O' D5 u# D- a转载自: 二土电子9 [5 c# |7 |6 k" N( `8 L' U S
如有侵权请联系删除
5 f1 d0 {2 h/ a/ T) ^. ]8 {1 k5 g! D' d" S5 [- T/ D
! n: r; N# z! c% z) G7 H' P |