一、什么是中断
4 p0 ^) |/ @; H# D, Y首先介绍一下什么是中断。在实际开发过程中,中断是很有必要的。比如需要针对某种特殊情况进行快速响应,单纯的使用一个while轮询似乎并不能满足。中断的概念非常好理解,举个经典例子。比如你在家里看电视,忽然有人敲门,你临时把电视暂停了,转去开门。开完门之后再次回来继续看电视。中断也就是这种流程。看电视的行为就类似于程序中main函数的while,轮询执行业务。忽然有人敲门,对应程序运行过程中忽然产生了一个中断请求。此时暂停电视,对应于此时程序中断当前的业务,转而去处理中断业务(开门)。最后,中断业务处理完成后,再继续执行main函数while轮询中的业务。简单用一个图来表示一下
; N/ r, a! }; e8 e+ H. Q0 Y" P8 _$ A/ R2 H0 X y! F1 u
% i- N- M% Z# ~# e) F' F9 D; J$ K( t! r# m2 f {! b% Q/ N: ~$ Y
中断概念示意图 8 D( Z% M: b8 r* d8 H& n8 V
3 I0 ]5 m0 r5 u" V+ C, _" @
根据中文参考手册的介绍,STM32F103ZET6除了一些特殊的中断外,常用的中断有60个,这些中断是通过中断控制器来有条不紊地分配执行的。
! f( s: f4 M4 k% d; U6 E6 W% V! C. Y4 p
二、中断的相关概念( L1 @! ]- L; Z- N5 u1 k) ~3 G2 W# g) d
2.1 中断优先级
% m) u- j0 v' I& V6 J从字面意思来讲,优先级用来区分中断的响应顺序。当同时接收到多个中断请求时,中断控制器会根据中断优先级来决定中断处理的顺序,优先级高的会先被处理。如果在处理某个中断请求时又来了一个中断,这时会根据两个中断的中断优先级来确定处理方式。如果新来的中断优先级比当前中断的优先级高,则会停止对当前中断的处理,转而处理新的中断。反之,如果新来的中断优先级比当前中断的优先级低,则需要等到当前中断处理完成后,再去处理新来的中断。/ B2 K! F9 s" n6 C% O v
' m: V# K4 e) b2 f6 F4 P2 e$ S% O
中断优先级有两种,一种是抢占优先级,一种是响应优先级。响应优先级通常又被称为“亚优先级”或者“副优先级”。当两个中断的抢占优先级相同时,用相应优先级来决定中断的处理顺序。如果两个中断的抢占优先级和相应优先级相同,则根据芯片手册中的中断向量号来决定中断的处理顺序。比如同时来了两个中断请求,在抢占优先级和响应优先级均相同时,中断向量号为41的中断会比中断向量号为42的中断先被处理。6 V5 v$ E6 [5 i) d
$ f! u6 S( r6 R) Y& ^: P d$ p
/ ?- x9 e0 X; ^/ e+ j* Q- f
STM32提供了16个可编程的优先等级(使用了4位中断优先级),优先级分组可以使用库函数提供的NVIC_PriorityGroupConfig()设置。
/ v$ _3 c- R0 r9 O- D7 S' w# p" g! q$ R( a- K
2.2 中断嵌套7 p( {% U" e' w/ g" Y& r3 y; c- j
一些低优先级的中断可以被高优先级中断打断,这种情况叫做中断嵌套。7 t( |0 [3 C, w! C& x& Q- f' @
$ x9 j! L3 H5 S7 U/ W6 X- p2.3 中断服务函数
+ k* ~# k% w' v中断服务函数就是在进入中断后需要执行的内容。$ v' Q2 R5 K+ r6 N0 k8 P0 z% n
& L3 V0 m) Z3 _7 j4 K% b# K中断服务函数有特定的函数名,可以在下图文件中搜索“IRQ”找到。
0 ?7 e9 @0 c' d0 I; y$ o8 P4 n/ u( y9 d0 {6 ~6 {' V, s
. i; _! ?! ]6 k N' R
6 G8 e# \: T+ @: d: _ r
中断服务函数名 2 C: o8 t$ Q' \' s- A* P X# h. [
& {! r- T8 n X( D; ?* u3 h# M2 m! u# P7 I
2.4 中断标志位' T# ]/ ?" v" Z9 N
不同的中断会有对应的中断标志位,通常标志位默认值为0。当产生中断请求时,标志位被置1。比如设置一个串口接收完成中断,串口接收完成标志位初始值为0。当串口接收完成后对应的串口接收完成标志位会被置1。在中断服务函数中检测该标志位的值,来确定是否是串口接收完成中断产生了。每次中断服务函数执行结束后,需要清除一下对应的中断标志位。& P9 J) D! e# u9 d+ y" j
7 z+ H7 F2 T) K% f3 u7 ]6 X2 D
% L+ E* ]4 r, Y( r5 A8 K9 o, [
三、外部中断EXIT
6 l( }9 h0 c. c/ v! H- P% q( ]. JSTM32F103ZET6有一个外部中断控制器(EXIT),可以支持20个软件的中断/事件请求,其中外部中断的EXIT0~EXIT15同坐IO中断。! m9 \( q8 v7 A0 T' c4 J
+ n. K6 z$ }! W5 k7 d/ r1 R; A8 E3 W: O) k. q$ z9 Y
外部中断IO对应 % [1 b& C4 V3 T9 I9 j
1 }& t# o! e# d: ]4 s
& ^* |9 n+ z' y3 X& `其他详细的介绍这里就不再说明。
. M& h9 C0 Q$ ^5 m8 r2 C' ? O/ B! [, ]& j( B& U; j) W
% a! M9 d$ K& }4 M# k0 x% K四、中断程序配置
3 g$ f6 E1 B O3 G p: i这里以配置PA0(按键WK UP)的外部中断为例,展示一下库函数开发时,外部中断的配置流程。关于其他中断的配置,后续使用其他外设时会单独介绍。
8 I) x7 e. T! m1 t. T0 |6 E6 R* @6 g3 K' e
想要实现的效果是,利用外部中断实现按下WK UP,LED1点亮。
4 _6 y2 S f ]+ f% B6 A; H8 S( |5 v
" D& E% U, l& h6 l6 U: e4.1 设置中断分组并使能中断, a- }# U& ^! \$ C
这里使用外部中断,需要开启AFIO时钟,设置IO与外部中断线的映射关系。: U, W+ C) V( \# c ^: q, e
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); // 开启AFIO时钟. B) t) D+ X9 k$ ]# z4 i9 W7 l+ Q
8 l2 W% @! n( }4 Z; G& s8 `- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); //选择GPIO管脚用作外部中断线路
复制代码
v8 _( v4 V/ A% z' v设置中断分组并使能中断时,库函数提供了一个结构体,我们直接配置这个结构体就可以了。' v$ \' t+ c7 |
- //EXTI0 NVIC 配置2 \' E3 t" v9 J/ m; T% A1 d+ U! G
- NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //EXTI0中断通道
+ d* W1 I/ K" d$ J. e' n - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级
) b7 o/ L2 W3 l2 h9 Z - NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级) X/ C W( ^( T, M k$ d" M J/ ?
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能3 I5 f5 z, T# W' u
- NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器6 r& m5 p1 K( f, I3 C* j4 ]) h: {
复制代码
$ D& v" s: S4 P* z* Z& U需要注意的是,配置优先级时,数值越大,优先级越低。
/ S9 ?. b9 l) V+ L: T. |( x9 t, P+ h+ S& p( N
4.2 初始化EXIT+ L0 s; e H" L' ]7 n7 m
初始化EXIT时,库函数也提供了一个结构体,其中包括中断线,EXIT模式,触发方式以及EXIT使能或者失能。由按键检测一节了解到,WK UP按下时,会产生一个上升沿。因此触发方式我们选择上升沿触发。
) M$ C% Z& ^' a- EXTI_InitStructure.EXTI_Line=EXTI_Line0; // EXIT0
! H! j j( l( m* G( F - EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt; // 中断
: Q% P# k5 w9 F - EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising; // 上升沿触发
- V( R' ]! R! _& b. Q) G. P6 U - EXTI_InitStructure.EXTI_LineCmd=ENABLE; // 使能
5 h) @) h" g( \- N; d {$ D6 k! W- \" c - EXTI_Init(&EXTI_InitStructure);
复制代码 # N' D0 E, l* P: E) u/ l+ p6 L- _
整体配置函数如下$ {3 K, X/ z. U7 x" n! o$ \9 h
- /*1 v: G/ t6 c2 H& C% D
- *==============================================================================
7 ?2 g D2 \) J - *函数名称:Exit_Init
0 A9 t( s3 u/ G6 {$ g - *函数功能:初始化外部中断 L6 O) m6 a8 S. G2 p( B% P' ]
- *输入参数:无3 n0 E/ i! d' P) l& y. Q+ B8 {
- *返回值:无; O, D8 K7 ~8 J4 w0 y/ E
- *备 注:无* @; z" M7 x( E
- *==============================================================================
$ R& z) Q' U" J" f: G - */
: v: F; `+ D# H& G7 y5 o - void Exit_Init (void)8 ]/ N6 n6 t0 K1 A% b4 R' ~
- {
V' s6 v( l2 Z: u - NVIC_InitTypeDef NVIC_InitStructure;
' U/ \0 D0 i; Z. z/ q O - EXTI_InitTypeDef EXTI_InitStructure;
4 U* _$ |" C. Q, T6 [2 F3 l -
( H- H4 w1 i V- f' B* _2 s - RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); // 开启AFIO时钟
% m9 }+ @( Q V1 [# u* e9 K - ) M$ Q. X( _+ M+ _/ t. g
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); //选择GPIO管脚用作外部中断线路% [6 f$ i9 e( F$ A' u
-
2 l$ j) C+ Q7 G" F) Y6 U) Y1 [ - //EXTI0 NVIC 配置/ i3 b& J. G0 ^! J0 l5 ]. v
- NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //EXTI0中断通道! \8 c- V, H* C y
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级
6 V: Z" P% A, @; O6 r - NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级" ^0 r" L: E: C0 \
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
4 D4 x/ e. ~! z7 H) S( Z6 u0 V - NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
* }2 K+ }3 X, C; Z' o& | - 0 |/ h/ E+ p; F/ T6 x: b$ s# P
- EXTI_InitStructure.EXTI_Line=EXTI_Line0; // EXIT0
, Z: K) W3 z/ w" G+ j' p - EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt; // 中断4 X3 L; J( t4 G$ B& c" C
- EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising; // 上升沿触发5 H) ^; C7 X- `# l5 s3 E
- EXTI_InitStructure.EXTI_LineCmd=ENABLE; // 使能
, y2 W' J/ k5 Z$ \" [* M6 p7 F: } - EXTI_Init(&EXTI_InitStructure);6 K# `$ n8 `2 a* W4 N$ g
- }
复制代码 $ \" L% C; U* B9 [" F; S. J: |
4.3 编写中断服务函数' e" y8 ?& o% S2 E$ g$ M7 _
上面介绍了如何找中断服务函数的函数名,这里直接开始写中断服务函数。这里的中断服务函数比较简单,直接点亮LED1即可。2 Y' x, u0 h( N" n1 {
- /*
& \! T* {2 e c& a( ~" R; G8 A - *==============================================================================
; D. M: t) i+ F8 X b' @& ` - *函数名称:EXTI0_IRQHandler
# m% n9 H+ }- ] h0 @5 L - *函数功能:外部中断0中断服务函数
: m! R4 {5 v" S) Y - *输入参数:无) d( G3 D0 w) ^! Z/ ]4 u
- *返回值:无% X. ]# a; W! U, @- N
- *备 注:无5 u0 @; k8 E$ _+ q
- *==============================================================================
5 d; V4 ?6 B( D% e$ c - */3 j$ F. T: O+ H4 p; y( |
- void EXTI0_IRQHandler(void)$ c4 W0 O7 I+ B: g. W& k4 v- |
- {
/ |- H4 s, y: o' K2 E. S' Q - // 如果EXIT0中断标志位被置1( e- l- k& E/ M
- if(EXTI_GetITStatus (EXTI_Line0)==1)7 J' [# a+ B, R. ~( @
- {
+ h6 p" K% K; {8 Q - Med_Led_StateCtrl (LED1,LED_ON); // 点亮LED1- B" |! Z. \! N* g) z; p. B
- }5 }( T' w* b0 _) r' I4 ?% r
- EXTI_ClearITPendingBit (EXTI_Line0); // 清除中断标志位
2 I) A: y9 M6 d; z - }
复制代码 7 H/ H' |0 @0 B! Y, Z6 ~8 m2 q
至此,按下WK UP后,LED1会点亮。这种方法与之前的按键点亮LED有什么区别?之前的按键点亮LED是在main函数的while中实现的,而利用外部中断的方法,是在外部中断的中断服务函数中实现的。即使main函数的while轮询业务中没有按键业务,按键依旧可以起作用。% x' B; m# i! _+ B
9 a5 j7 A; W, M1 A
- H- R- V# x0 l五、注意事项- v0 n9 x, E: r( m" ]' n9 ?2 F
• 中断服务函数无需在.h文件中声明
3 S9 y% Y- P y3 W• 中断服务函数中不要有过长的业务4 H9 p7 x5 I+ l- E4 U( T& [1 j
• 中断服务函数最后需要清除中断标志位
( J( K$ s) I' z- a& p3 W9 O1 |2 r; G. Z3 s
/ P# ?! S) b" ~7 D( g: u% b转载自: 二土电子% f( l: s1 {% c) U; R* Z: c
如有侵权请联系删除- j2 r1 _; P$ S# J3 Z# h( J7 T' C# Q
X% o6 Z/ k* N4 K; Q
1 i2 S. h6 T: B% d) D0 h, g
|