一、什么是中断" ~& _5 Z: `' n P' q4 v
首先介绍一下什么是中断。在实际开发过程中,中断是很有必要的。比如需要针对某种特殊情况进行快速响应,单纯的使用一个while轮询似乎并不能满足。中断的概念非常好理解,举个经典例子。比如你在家里看电视,忽然有人敲门,你临时把电视暂停了,转去开门。开完门之后再次回来继续看电视。中断也就是这种流程。看电视的行为就类似于程序中main函数的while,轮询执行业务。忽然有人敲门,对应程序运行过程中忽然产生了一个中断请求。此时暂停电视,对应于此时程序中断当前的业务,转而去处理中断业务(开门)。最后,中断业务处理完成后,再继续执行main函数while轮询中的业务。简单用一个图来表示一下
5 {( C7 w9 e! O
4 l4 v# H7 g5 w9 U6 e/ b9 ?9 c( @; h5 i
: j( J& t2 ~" C! R. x0 k4 S中断概念示意图
" A6 B4 _) O# m: z5 ?) m/ F
& `& ^$ T; O+ S5 E, `1 G根据中文参考手册的介绍,STM32F103ZET6除了一些特殊的中断外,常用的中断有60个,这些中断是通过中断控制器来有条不紊地分配执行的。
5 J: E" M- f% k6 Z9 h0 Y
+ K+ n8 p, {3 i二、中断的相关概念0 ^5 M# k( ? [2 ^& e
2.1 中断优先级4 u/ ]) v# `: p8 A; y
从字面意思来讲,优先级用来区分中断的响应顺序。当同时接收到多个中断请求时,中断控制器会根据中断优先级来决定中断处理的顺序,优先级高的会先被处理。如果在处理某个中断请求时又来了一个中断,这时会根据两个中断的中断优先级来确定处理方式。如果新来的中断优先级比当前中断的优先级高,则会停止对当前中断的处理,转而处理新的中断。反之,如果新来的中断优先级比当前中断的优先级低,则需要等到当前中断处理完成后,再去处理新来的中断。
2 A* i; W/ y* D/ F5 H. G+ V3 ^/ a8 t9 d5 c
中断优先级有两种,一种是抢占优先级,一种是响应优先级。响应优先级通常又被称为“亚优先级”或者“副优先级”。当两个中断的抢占优先级相同时,用相应优先级来决定中断的处理顺序。如果两个中断的抢占优先级和相应优先级相同,则根据芯片手册中的中断向量号来决定中断的处理顺序。比如同时来了两个中断请求,在抢占优先级和响应优先级均相同时,中断向量号为41的中断会比中断向量号为42的中断先被处理。& Z7 I# m& e% Z S/ Q- c- q
# }, a7 a% @* v3 y. J
' Q1 w9 U1 Y& E0 v5 G' L$ d! JSTM32提供了16个可编程的优先等级(使用了4位中断优先级),优先级分组可以使用库函数提供的NVIC_PriorityGroupConfig()设置。1 F, ^( l6 _ A" U7 m! j
H5 F, c5 m/ J Y0 e' Z: w9 \# m2.2 中断嵌套4 t' ?8 E2 \- B o e
一些低优先级的中断可以被高优先级中断打断,这种情况叫做中断嵌套。
; q3 a& h. A& ^! D1 N; O; c2 L
' Q% h9 q7 E1 ^2.3 中断服务函数+ n! m) V& s' O) y" e* S6 U
中断服务函数就是在进入中断后需要执行的内容。
( p! a0 j& N2 J/ M3 c- D, j$ b# T" R- f
中断服务函数有特定的函数名,可以在下图文件中搜索“IRQ”找到。1 F" T$ D1 j, A& V3 o/ z
' R/ J0 k$ u$ c/ e" W0 s( u1 X0 m' P3 L6 f o3 H1 e. ?( W p1 c
1 }' i5 c; R1 c中断服务函数名 ) h! H# n( v" K+ y- M
* O, f, z4 {: F
! E' }1 O5 R: T2.4 中断标志位6 G1 U5 _4 R: W: Q1 |/ O
不同的中断会有对应的中断标志位,通常标志位默认值为0。当产生中断请求时,标志位被置1。比如设置一个串口接收完成中断,串口接收完成标志位初始值为0。当串口接收完成后对应的串口接收完成标志位会被置1。在中断服务函数中检测该标志位的值,来确定是否是串口接收完成中断产生了。每次中断服务函数执行结束后,需要清除一下对应的中断标志位。+ ~% \- `! @$ b5 i& }3 k. K4 _
: C# [4 c) D0 b c Q( f
1 V$ O" Y% F9 k3 C5 R
三、外部中断EXIT" E3 @% C$ X3 C2 M& v
STM32F103ZET6有一个外部中断控制器(EXIT),可以支持20个软件的中断/事件请求,其中外部中断的EXIT0~EXIT15同坐IO中断。
& B1 g$ P8 n) Q# W4 B2 v! n: ~6 R4 b5 y {
4 W5 X1 |8 [. N外部中断IO对应
0 Y& Y; @7 q( ~ r0 u9 F0 \: o+ s& \' `6 r
$ |" A# ^, c% S& ?1 M% t其他详细的介绍这里就不再说明。4 P# u, U2 ?% i' E
& t4 u$ m1 B1 i. B5 V( Y: A
) X' D; p l& P四、中断程序配置
' a% }4 L% ]! V: [& l/ k3 V: J这里以配置PA0(按键WK UP)的外部中断为例,展示一下库函数开发时,外部中断的配置流程。关于其他中断的配置,后续使用其他外设时会单独介绍。
' v) A3 L7 ~6 D' i4 ]: I" r+ J! Y1 `; L9 q
) t' L, e' `9 O$ b1 q/ L, F想要实现的效果是,利用外部中断实现按下WK UP,LED1点亮。$ E2 i, z; a5 z: i/ f. Z
% r7 G% v- \% f# _. D4.1 设置中断分组并使能中断
7 u$ }! F+ t: u4 y' D: ?3 Q这里使用外部中断,需要开启AFIO时钟,设置IO与外部中断线的映射关系。; M& c" n$ S" O" u% A) C
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); // 开启AFIO时钟
- i' Z% ~7 O- E( k - " @7 {# W, V( q( N' y, ~. T& T8 G
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); //选择GPIO管脚用作外部中断线路
复制代码
% y% f- t; Y A" [( x3 e( H! {设置中断分组并使能中断时,库函数提供了一个结构体,我们直接配置这个结构体就可以了。- ^2 A0 B# [/ n9 [, T* A
- //EXTI0 NVIC 配置) o7 G$ V( M6 A( k
- NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //EXTI0中断通道
- j& g' }. g8 P' O1 G - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级: u+ ^1 W# D! K {
- NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级
: P* M6 |: \% V' w9 k. {$ @* @ - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能$ t6 H& T! z: {2 S
- NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
1 s/ x4 w+ @- @* P8 r0 ~
复制代码 + v: e; o) g# I$ D$ I
需要注意的是,配置优先级时,数值越大,优先级越低。
6 z& r; a$ b( e3 D G! d b
2 U2 f+ y3 S) Z& e" x5 a6 J4.2 初始化EXIT
* L) S& g1 j% @; G# I" b0 B$ h初始化EXIT时,库函数也提供了一个结构体,其中包括中断线,EXIT模式,触发方式以及EXIT使能或者失能。由按键检测一节了解到,WK UP按下时,会产生一个上升沿。因此触发方式我们选择上升沿触发。
5 v+ F- j1 I, Y: S4 v- EXTI_InitStructure.EXTI_Line=EXTI_Line0; // EXIT08 O& v/ E$ v# L8 d& { ]6 Y* M
- EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt; // 中断& F @# U5 ^+ \& B) ], o
- EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising; // 上升沿触发
! I0 I: o, ?+ w& I: X - EXTI_InitStructure.EXTI_LineCmd=ENABLE; // 使能7 i3 t8 I$ E" z. O9 N
- EXTI_Init(&EXTI_InitStructure);
复制代码 6 X# W% v Z1 R: m6 h* D7 w6 A* B- _
整体配置函数如下$ B- F+ |- V9 a$ s
- /*5 A" |1 ?5 j- n. r3 v* R4 \. D4 d
- *==============================================================================
# \7 [/ y2 W1 c" h2 J, I& B - *函数名称:Exit_Init
# E. n. j3 D3 B) H* @ - *函数功能:初始化外部中断
" C8 l( b D" S% ? - *输入参数:无: S) @ M5 q, f7 j! G
- *返回值:无
% `7 W: A1 `, I# g6 v: B! G# U - *备 注:无 P+ `2 \. }7 J" Q
- *==============================================================================4 E3 b# K- m+ x2 j+ Q3 d$ A0 U
- */! x# S( Q% m+ p6 Q6 d2 W: }, _! N- Z
- void Exit_Init (void)
# d' m: o6 G, o/ N: ]* p- M - {7 G% }5 r- o% C+ l5 a) C- Y q! W
- NVIC_InitTypeDef NVIC_InitStructure;7 m- e- i' T$ \' {
- EXTI_InitTypeDef EXTI_InitStructure;
5 h8 K* f; _! G -
7 X( U2 K' K! |( H+ I( W9 o - RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); // 开启AFIO时钟1 {9 D! l' ^& }4 l7 w. O' j
- " c. m- s" y6 u, H% Y
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); //选择GPIO管脚用作外部中断线路
- j8 P+ f' f4 u. i _ -
p8 `: s: t# u2 U - //EXTI0 NVIC 配置
^+ B+ d/ I. b+ {' K! D - NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //EXTI0中断通道
8 m* T( T, i. v( W( W/ ~9 I - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级4 T0 c2 { ]; T* O; }8 v5 }3 A
- NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级- M4 K8 e9 v0 j. E E# x: o7 E# ~. B) y
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
" u5 l, I* C4 a/ _4 V - NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
5 n7 P. w/ g4 J -
4 Y1 \8 c# ?) O - EXTI_InitStructure.EXTI_Line=EXTI_Line0; // EXIT0% h! n$ l) O% x% O( T2 p5 {8 x
- EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt; // 中断
+ j- N/ _; c( S; Y5 m7 S. u. v - EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising; // 上升沿触发
& \" O f+ U. L% E - EXTI_InitStructure.EXTI_LineCmd=ENABLE; // 使能' P: Z1 b- r* q% l9 T' g
- EXTI_Init(&EXTI_InitStructure);3 q' s9 R; `7 q. o
- }
复制代码 9 h' N0 O/ I( K2 O+ I* G- U" {
4.3 编写中断服务函数
. Y+ p- j- L- O8 E- Z7 [上面介绍了如何找中断服务函数的函数名,这里直接开始写中断服务函数。这里的中断服务函数比较简单,直接点亮LED1即可。9 o8 n+ K2 [! n( e
- /*
# p3 k: c, P, a1 f$ X( H - *==============================================================================
) G1 d* m! ^8 o% e+ \ - *函数名称:EXTI0_IRQHandler3 o5 Y8 _7 c* w
- *函数功能:外部中断0中断服务函数
R _3 R. M/ [5 _ - *输入参数:无
& w: p$ m+ |: L7 j! p! R - *返回值:无8 x4 @% _2 J% ~9 D. X- [* d
- *备 注:无
$ s0 r" I5 y5 R" G - *==============================================================================, ~3 O8 c& W/ l8 t: o6 l# N$ R0 q
- */! r0 C; g' P. X- ?1 @. T5 \- C6 `2 e; T
- void EXTI0_IRQHandler(void)" ^# E7 t6 J9 Y X
- {
* o5 [" C. J, B8 y3 U - // 如果EXIT0中断标志位被置11 U$ X) z1 _) o; x* p. L( m
- if(EXTI_GetITStatus (EXTI_Line0)==1)
- r* ^0 c. E5 g: q1 t( ?3 z - {
$ p# O9 o4 H) n3 i7 D4 d! m - Med_Led_StateCtrl (LED1,LED_ON); // 点亮LED1, D( V( S8 f, t4 N4 R2 ?
- }! [( _4 Q! Q2 U+ A/ @
- EXTI_ClearITPendingBit (EXTI_Line0); // 清除中断标志位
+ V6 ^& i( D) }. l - }
复制代码
6 ?7 E6 ?. ?3 L8 Q9 q至此,按下WK UP后,LED1会点亮。这种方法与之前的按键点亮LED有什么区别?之前的按键点亮LED是在main函数的while中实现的,而利用外部中断的方法,是在外部中断的中断服务函数中实现的。即使main函数的while轮询业务中没有按键业务,按键依旧可以起作用。- Z1 Y8 Z/ H. j+ H
2 ?/ F5 V3 J; V8 ]0 |
6 {6 F; E9 W9 t五、注意事项
/ ?, a, U! e$ m( i7 o; P% }% Y% l; m• 中断服务函数无需在.h文件中声明
' b8 i$ G. I1 x1 J }• 中断服务函数中不要有过长的业务
, C0 ^7 K: [9 N) l8 s) x. `: ?, t• 中断服务函数最后需要清除中断标志位4 v0 _0 y8 A# [9 v' o0 y. w
# J c% D' n+ X+ s$ O
: a2 q; }' B$ Q* H7 g/ N$ x9 Y转载自: 二土电子
3 _9 ]% l7 p% W: s/ { a如有侵权请联系删除/ e- D! i' o! E/ M
! t! i E( y* C n+ w) M
/ C S% Z4 a* s# w9 S |