一、什么是中断4 M$ D( {1 Z5 r' m: T% g- s
首先介绍一下什么是中断。在实际开发过程中,中断是很有必要的。比如需要针对某种特殊情况进行快速响应,单纯的使用一个while轮询似乎并不能满足。中断的概念非常好理解,举个经典例子。比如你在家里看电视,忽然有人敲门,你临时把电视暂停了,转去开门。开完门之后再次回来继续看电视。中断也就是这种流程。看电视的行为就类似于程序中main函数的while,轮询执行业务。忽然有人敲门,对应程序运行过程中忽然产生了一个中断请求。此时暂停电视,对应于此时程序中断当前的业务,转而去处理中断业务(开门)。最后,中断业务处理完成后,再继续执行main函数while轮询中的业务。简单用一个图来表示一下1 X# D* T* s; x' \& j9 I- x1 u
6 K# H, g6 \* `3 H; K1 A: }& \0 {- T5 x& |' K' k+ N
, [- o' |. \/ x2 B* Q中断概念示意图 " j- ^6 \8 o/ u# [3 N
; B4 j1 L/ h7 H }& ]0 |) C: R
根据中文参考手册的介绍,STM32F103ZET6除了一些特殊的中断外,常用的中断有60个,这些中断是通过中断控制器来有条不紊地分配执行的。
( ~ A9 J0 I( G6 z- }' r
/ V n O: X# a5 h3 ]" x" B) e: R二、中断的相关概念
# N) \5 H& R9 h$ C/ G0 B2.1 中断优先级
1 b' p* p( d7 ?, L3 i5 B从字面意思来讲,优先级用来区分中断的响应顺序。当同时接收到多个中断请求时,中断控制器会根据中断优先级来决定中断处理的顺序,优先级高的会先被处理。如果在处理某个中断请求时又来了一个中断,这时会根据两个中断的中断优先级来确定处理方式。如果新来的中断优先级比当前中断的优先级高,则会停止对当前中断的处理,转而处理新的中断。反之,如果新来的中断优先级比当前中断的优先级低,则需要等到当前中断处理完成后,再去处理新来的中断。
0 J# I1 f3 k: X1 U3 p$ g6 _( i( @' f" z- N) z+ R) B( t
中断优先级有两种,一种是抢占优先级,一种是响应优先级。响应优先级通常又被称为“亚优先级”或者“副优先级”。当两个中断的抢占优先级相同时,用相应优先级来决定中断的处理顺序。如果两个中断的抢占优先级和相应优先级相同,则根据芯片手册中的中断向量号来决定中断的处理顺序。比如同时来了两个中断请求,在抢占优先级和响应优先级均相同时,中断向量号为41的中断会比中断向量号为42的中断先被处理。
1 B/ M$ `' L: k% O5 c3 n6 H. @& j5 n0 T8 t/ w$ b. \
# ^& h2 W3 W! m0 V NSTM32提供了16个可编程的优先等级(使用了4位中断优先级),优先级分组可以使用库函数提供的NVIC_PriorityGroupConfig()设置。5 j4 ?% d4 L L6 M+ o: @! C
0 f* c! [5 ]" A: _; R
2.2 中断嵌套
3 h5 E0 v' L9 n, j3 c& q一些低优先级的中断可以被高优先级中断打断,这种情况叫做中断嵌套。" h l# E f% h6 j3 e# f: ~/ w
1 f- t }+ n5 i M; @
2.3 中断服务函数! E1 R$ U8 y0 Q
中断服务函数就是在进入中断后需要执行的内容。% q! L! x7 s; O) H2 z9 E3 ^0 y2 E
3 c+ I) L; q+ x3 y# C' R中断服务函数有特定的函数名,可以在下图文件中搜索“IRQ”找到。
7 Q9 L$ Q& K7 q" t: u6 H" y& E* w9 H4 q$ z3 V' `
" q: ]& p7 _ T2 {/ {+ i
+ s* O! K5 e* |中断服务函数名
8 H, g. R- L( n7 R4 c" }& G3 M8 D0 G- e$ ^* v M
: L$ W) G5 a$ \, e
2.4 中断标志位
$ R9 f& ?: r* u( z# n8 a不同的中断会有对应的中断标志位,通常标志位默认值为0。当产生中断请求时,标志位被置1。比如设置一个串口接收完成中断,串口接收完成标志位初始值为0。当串口接收完成后对应的串口接收完成标志位会被置1。在中断服务函数中检测该标志位的值,来确定是否是串口接收完成中断产生了。每次中断服务函数执行结束后,需要清除一下对应的中断标志位。+ ^1 w- o) ?; T" Q' M Q0 o
4 ?# J4 o* S# Y' M9 p* Q9 r2 E; Q
, W; v$ s" T. f/ D0 R0 D+ [三、外部中断EXIT
' [8 w% Q5 m2 x ?( `: r* TSTM32F103ZET6有一个外部中断控制器(EXIT),可以支持20个软件的中断/事件请求,其中外部中断的EXIT0~EXIT15同坐IO中断。6 U$ b* y' @9 `9 p
$ s$ W9 a3 Z. J5 e, P
/ ?3 `+ X* Y* C6 ?0 |1 J' O4 e外部中断IO对应 # E) m& k* R7 \( |$ O' _0 J) Q+ G; E
9 l3 Q D2 t1 H: i9 E/ F) l6 V
' W, f" _# ~: s其他详细的介绍这里就不再说明。
9 x) L2 L5 w# U8 t6 x, n% S: q' L) b0 `
! m; |1 ?* S' S! y4 K6 [1 v* i
四、中断程序配置8 m& v% T$ S& d2 J& B
这里以配置PA0(按键WK UP)的外部中断为例,展示一下库函数开发时,外部中断的配置流程。关于其他中断的配置,后续使用其他外设时会单独介绍。
( p! l3 l! [( t# k/ b$ j' Y/ v6 `& W, g; U& h4 y) M6 d4 [
想要实现的效果是,利用外部中断实现按下WK UP,LED1点亮。
4 b- K! D) W: N7 r0 Z/ L, q7 j# Q& j) b3 M. O7 V: b1 ?- A" `
4.1 设置中断分组并使能中断
T$ v$ s) c+ H( o/ a0 N9 e: l3 j这里使用外部中断,需要开启AFIO时钟,设置IO与外部中断线的映射关系。2 E9 c8 ? f( R2 v0 |% o5 b
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); // 开启AFIO时钟8 ]/ I& D1 y: i S- Q& ]
- ! l2 k# m8 g4 _8 q( R" `- }0 ~1 Z' z
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); //选择GPIO管脚用作外部中断线路
复制代码
- b' d2 n4 @% B) G& i设置中断分组并使能中断时,库函数提供了一个结构体,我们直接配置这个结构体就可以了。# ], z9 f6 g! P- a8 P( j
- //EXTI0 NVIC 配置
4 ]4 m6 ~% n* d3 V' j! a: @$ i5 w - NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //EXTI0中断通道) d- s$ Z) ?1 q
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级# b, p1 \1 A* O: `* \/ ~2 U
- NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级
9 H! c% Z" F8 Z4 O2 } - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能- ]) e/ V; \2 L$ C+ F0 P5 V
- NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器: M0 J* g8 S5 y- o$ U9 k
复制代码 9 X; q) T5 ]3 @
需要注意的是,配置优先级时,数值越大,优先级越低。/ K$ D: I2 l6 j8 ~$ l
* j' S- n) @2 g
4.2 初始化EXIT
# ]1 k w3 `6 O$ z初始化EXIT时,库函数也提供了一个结构体,其中包括中断线,EXIT模式,触发方式以及EXIT使能或者失能。由按键检测一节了解到,WK UP按下时,会产生一个上升沿。因此触发方式我们选择上升沿触发。
( P0 ]4 d$ N$ b4 l- EXTI_InitStructure.EXTI_Line=EXTI_Line0; // EXIT0; X/ @" ?; d& F7 r
- EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt; // 中断
$ i6 }: h+ q9 r4 o' \5 N8 m - EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising; // 上升沿触发
; ^ k- m/ j- R0 I% I - EXTI_InitStructure.EXTI_LineCmd=ENABLE; // 使能
, E% q+ `" ?( x$ m - EXTI_Init(&EXTI_InitStructure);
复制代码
* L O/ y% v9 f- i整体配置函数如下- e& C( s9 W! G# n1 d, z/ k' S
- /*
' F) Q7 z9 y$ K7 B+ q( Y2 Y: L - *==============================================================================
* U$ q$ n: s, ^' j; H - *函数名称:Exit_Init
' F/ a1 Q- E4 C$ J3 ^$ x - *函数功能:初始化外部中断% ~0 ~* }, i0 x% v4 n
- *输入参数:无
5 V3 M' m; `% b- c4 i+ n - *返回值:无0 k8 J; J! [1 t1 V; e% O6 b: ]
- *备 注:无6 c x: }. a: B7 @( n
- *==============================================================================
3 P6 S& u& J0 `& N - */9 j% h. q( L P0 l
- void Exit_Init (void)/ }: y- K; R' K2 @
- {8 m: _5 W6 A/ k5 c* N# S
- NVIC_InitTypeDef NVIC_InitStructure;
) v4 B$ c* O' V6 M% X0 H: n+ K - EXTI_InitTypeDef EXTI_InitStructure;5 f: Y; ^& i: D0 ], G, m
- ) F# X) A9 R7 b
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); // 开启AFIO时钟
8 r7 I$ r* y$ F- R7 f& q9 ~
! h0 p1 x( E: h& V- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); //选择GPIO管脚用作外部中断线路1 a+ k/ W0 w+ s0 O' f
- ! a/ n9 M/ Y) }2 R$ h
- //EXTI0 NVIC 配置 }0 P& W/ O) q5 O% j2 Z
- NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //EXTI0中断通道
% V0 N R/ V9 [3 Y" S - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级
) ]* \4 Y* V; b, k# {% N/ N% @4 { - NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级
2 W: k% Q6 V) H* X - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能0 V7 E( D: O: k, ~: B6 X8 G: u3 k
- NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器% d: p$ I) I1 z3 f: R/ G- |: w
- + D0 B5 T& a% D
- EXTI_InitStructure.EXTI_Line=EXTI_Line0; // EXIT05 Q% S$ v7 ^9 _) f H2 M
- EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt; // 中断
! K: V- E, @ g( Y5 ~7 H - EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising; // 上升沿触发
) Z' o* p4 ]% V- Z0 {0 A9 N7 x6 h - EXTI_InitStructure.EXTI_LineCmd=ENABLE; // 使能7 P5 C: z# \& h: @4 P$ J
- EXTI_Init(&EXTI_InitStructure);
0 l8 w/ {& @& H" i+ I2 z - }
复制代码 * X3 e2 Z/ |7 Z( F" _! b( Z- j" k
4.3 编写中断服务函数
4 ?6 N5 f1 D; i: A1 _+ z上面介绍了如何找中断服务函数的函数名,这里直接开始写中断服务函数。这里的中断服务函数比较简单,直接点亮LED1即可。
5 X' y# O- V3 e W- /*
0 G0 }+ g/ g( C6 o) q: d0 J& Q+ | - *==============================================================================
k6 q0 d8 k2 l+ z - *函数名称:EXTI0_IRQHandler! V+ p" h2 L. R: }( N8 U; }# O) l
- *函数功能:外部中断0中断服务函数
' E, i9 u$ I& a6 O9 f6 J - *输入参数:无
4 [# ^* w. D+ G5 t5 e9 K - *返回值:无0 y% V) ^2 }/ u3 q9 C; R
- *备 注:无3 T, I; ^3 V7 g
- *==============================================================================
$ |1 S7 O- ]1 u' `" \ - */0 R' N' c* Y7 g7 J, \) N7 C5 b+ _% _
- void EXTI0_IRQHandler(void)
2 J( u m- |( {. T/ k: t - {5 e6 ]7 `0 V! w8 }2 w! T' g# n
- // 如果EXIT0中断标志位被置1
; d: r% ^$ z, e$ L/ c - if(EXTI_GetITStatus (EXTI_Line0)==1)( ~- U1 ~$ E5 a& F
- {1 S& \1 {. U! x; O
- Med_Led_StateCtrl (LED1,LED_ON); // 点亮LED1
, B# R( L# x% k Z! B4 R! V - }% R5 ^2 }$ u& Z5 c
- EXTI_ClearITPendingBit (EXTI_Line0); // 清除中断标志位6 J! K L2 h, M: _. g" y- n
- }
复制代码
% A% ]. T5 R& O8 Y0 O至此,按下WK UP后,LED1会点亮。这种方法与之前的按键点亮LED有什么区别?之前的按键点亮LED是在main函数的while中实现的,而利用外部中断的方法,是在外部中断的中断服务函数中实现的。即使main函数的while轮询业务中没有按键业务,按键依旧可以起作用。
2 J8 `; \0 c1 x' U' F! y" z4 \" u7 `5 R) B! D- m+ K( b
8 K, \; @+ W3 } b a" K4 b Q, V$ k
五、注意事项1 s9 m8 L% A4 ^1 p' Q" o. V
• 中断服务函数无需在.h文件中声明
1 q: f7 ^8 O7 ~1 H: K• 中断服务函数中不要有过长的业务
4 N, X2 l& i8 H! H• 中断服务函数最后需要清除中断标志位
4 R' N" y1 V- k5 E0 O# G& {+ J) t
, F( @- f5 I; R8 `, P0 `+ T( y9 r1 W& Q9 j- G
转载自: 二土电子; B8 V9 i& ~4 b. l' x1 i7 B4 ?
如有侵权请联系删除
$ \. C- c5 Z2 K- q! H$ a5 n/ h: D0 E3 Z- p: v7 T9 x
" C/ r' U3 ^" ?9 R! Y4 f; Z |