一、什么是中断+ V5 C1 Q' y. C
首先介绍一下什么是中断。在实际开发过程中,中断是很有必要的。比如需要针对某种特殊情况进行快速响应,单纯的使用一个while轮询似乎并不能满足。中断的概念非常好理解,举个经典例子。比如你在家里看电视,忽然有人敲门,你临时把电视暂停了,转去开门。开完门之后再次回来继续看电视。中断也就是这种流程。看电视的行为就类似于程序中main函数的while,轮询执行业务。忽然有人敲门,对应程序运行过程中忽然产生了一个中断请求。此时暂停电视,对应于此时程序中断当前的业务,转而去处理中断业务(开门)。最后,中断业务处理完成后,再继续执行main函数while轮询中的业务。简单用一个图来表示一下; n$ V6 x. A' v. l w) b
0 N. ?, X( s3 B8 h- b! e# `" ^/ r2 ?9 G6 h& [5 m1 a. \; M6 u9 F
. g) n& u( ^6 R) Q3 m# G. d
中断概念示意图
' }) ~* K& F5 u2 Z3 v6 g' r
& A, [0 i6 {' `8 s根据中文参考手册的介绍,STM32F103ZET6除了一些特殊的中断外,常用的中断有60个,这些中断是通过中断控制器来有条不紊地分配执行的。7 M# ]7 _& P8 y
6 V! z3 r4 I3 T7 B# t' _+ J8 m2 `
二、中断的相关概念
! ^. m9 N* a7 V% ^& N$ N2.1 中断优先级* o: n0 e1 \% j
从字面意思来讲,优先级用来区分中断的响应顺序。当同时接收到多个中断请求时,中断控制器会根据中断优先级来决定中断处理的顺序,优先级高的会先被处理。如果在处理某个中断请求时又来了一个中断,这时会根据两个中断的中断优先级来确定处理方式。如果新来的中断优先级比当前中断的优先级高,则会停止对当前中断的处理,转而处理新的中断。反之,如果新来的中断优先级比当前中断的优先级低,则需要等到当前中断处理完成后,再去处理新来的中断。
/ D& N5 h+ Z3 k6 S& c
: _* U0 q) F. h; i中断优先级有两种,一种是抢占优先级,一种是响应优先级。响应优先级通常又被称为“亚优先级”或者“副优先级”。当两个中断的抢占优先级相同时,用相应优先级来决定中断的处理顺序。如果两个中断的抢占优先级和相应优先级相同,则根据芯片手册中的中断向量号来决定中断的处理顺序。比如同时来了两个中断请求,在抢占优先级和响应优先级均相同时,中断向量号为41的中断会比中断向量号为42的中断先被处理。( D* Q" }- F) y
9 W( R3 E; t& D4 W& s
( t" A. t8 c* J/ s- q+ m6 GSTM32提供了16个可编程的优先等级(使用了4位中断优先级),优先级分组可以使用库函数提供的NVIC_PriorityGroupConfig()设置。
+ [, [6 |, }% Z# ]! f# V. `0 V
: V5 F2 r3 ?* k9 m* I2.2 中断嵌套
8 ^$ [& o7 Y3 o- Q$ J" W0 [一些低优先级的中断可以被高优先级中断打断,这种情况叫做中断嵌套。
* T+ m0 L; J* B
" d1 g1 \! n; k0 t2.3 中断服务函数+ M: ^ L' e+ M5 y Q& m
中断服务函数就是在进入中断后需要执行的内容。/ |: g: W: E) e7 N
, P& y$ e6 J. A
中断服务函数有特定的函数名,可以在下图文件中搜索“IRQ”找到。' Z( x. s4 o, O- `
+ j! e3 ?, Z5 i
3 v- u2 E$ h/ a) T+ N7 t8 `
H9 v% ~$ X. @5 @中断服务函数名 5 I' t" _! ~7 z0 H* z7 w+ ?+ u
2 h# v7 X3 X8 _" ?
! d( E0 V1 n% R- a2 `% q1 S2.4 中断标志位/ V8 r5 ?" C: O
不同的中断会有对应的中断标志位,通常标志位默认值为0。当产生中断请求时,标志位被置1。比如设置一个串口接收完成中断,串口接收完成标志位初始值为0。当串口接收完成后对应的串口接收完成标志位会被置1。在中断服务函数中检测该标志位的值,来确定是否是串口接收完成中断产生了。每次中断服务函数执行结束后,需要清除一下对应的中断标志位。
* ~. k- R; R& @! o( r- Q* @$ L6 _8 p% t, \
" |! @# C. s7 c8 d* k. m( H
三、外部中断EXIT" v& C9 w+ U) I& B
STM32F103ZET6有一个外部中断控制器(EXIT),可以支持20个软件的中断/事件请求,其中外部中断的EXIT0~EXIT15同坐IO中断。
+ }% n0 `. v4 ` n$ W: {" @% z$ N- A
5 G m- ~: a; X: U3 @外部中断IO对应 v: t7 Q& Z8 Z, F$ f) V( C
7 W$ k( l& \: F: ^! Z
' I' E1 j2 `+ C其他详细的介绍这里就不再说明。5 [/ }; Y: |& f; G; e# W* ]6 w
+ e, {: ]+ J2 b
$ s$ T: J+ G9 x1 {
四、中断程序配置
7 s% ^8 }1 O5 n这里以配置PA0(按键WK UP)的外部中断为例,展示一下库函数开发时,外部中断的配置流程。关于其他中断的配置,后续使用其他外设时会单独介绍。
: J Q" e/ s$ g; r6 r3 K, I0 i- X- L. ^: ?( e+ m0 E' W0 T; ^
想要实现的效果是,利用外部中断实现按下WK UP,LED1点亮。
" \5 g0 q: G" D+ i. j% A" U% w, O$ V1 ?( n( n2 W- O) r$ [( }0 y* |- i
4.1 设置中断分组并使能中断
$ e; q. B- Y+ w* c+ }# P/ ~这里使用外部中断,需要开启AFIO时钟,设置IO与外部中断线的映射关系。
+ T6 g6 t7 p; t$ |0 \& Z B- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); // 开启AFIO时钟
5 A) z9 _' B w - % m2 Z5 v' f% {9 f! t: ?8 ?: l
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); //选择GPIO管脚用作外部中断线路
复制代码
( k- ]* }4 R; r# m5 v设置中断分组并使能中断时,库函数提供了一个结构体,我们直接配置这个结构体就可以了。" M$ O: j) s! u @+ h. x
- //EXTI0 NVIC 配置; l" c- A9 y! w# z
- NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //EXTI0中断通道
* V3 F: U; }/ @; x' X - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级% u7 x+ c# F0 c+ `; v+ Q7 l
- NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级( \) ?& s$ T/ f" ]4 E$ N! O
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
$ W# U9 s2 G4 {2 q - NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
9 Z/ v K, i) `1 g- I1 S
复制代码 * ]/ ?- H2 C7 L& q8 [. \1 t
需要注意的是,配置优先级时,数值越大,优先级越低。
; v4 |) o; V* a) H
) _+ o) s; H2 ]4 A8 }3 [* I9 h4.2 初始化EXIT
" m6 @# I* ?. J3 C+ r2 O初始化EXIT时,库函数也提供了一个结构体,其中包括中断线,EXIT模式,触发方式以及EXIT使能或者失能。由按键检测一节了解到,WK UP按下时,会产生一个上升沿。因此触发方式我们选择上升沿触发。
N6 `# ?. _! G, N" ?- EXTI_InitStructure.EXTI_Line=EXTI_Line0; // EXIT0/ a( }9 s/ n" {9 f, G7 W
- EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt; // 中断
t/ M, t$ I( ` - EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising; // 上升沿触发; u/ T6 z8 e. b, K# y+ I( k
- EXTI_InitStructure.EXTI_LineCmd=ENABLE; // 使能1 e* J9 Y1 \: X t
- EXTI_Init(&EXTI_InitStructure);
复制代码 9 ~$ h, [3 y" z3 F* @5 N$ |
整体配置函数如下3 |/ Q1 h2 Z3 V6 x
- /*
% _& J9 Y/ w# O. J1 D( H8 i1 N - *==============================================================================+ f5 g' ^1 V# w1 s& O+ V
- *函数名称:Exit_Init
* z! H0 n, V5 _1 U" a+ z4 ~ J( | - *函数功能:初始化外部中断
! Q" ?- a# Z I7 B# l8 ~0 F - *输入参数:无
/ {5 h( O# U W6 c4 x: j4 ^; Y - *返回值:无5 J Y6 r# n& c& y6 A8 D. v4 B9 Y
- *备 注:无- u. I" D3 Q" K8 ~
- *==============================================================================
( ]# S* e) T6 ^4 W, R - */
5 L" e. ^ w$ ?% U: j - void Exit_Init (void)
9 G# B1 q$ Z; v: d - {
. ]- g# `* }( T - NVIC_InitTypeDef NVIC_InitStructure;
3 C" V, }& ?" G+ b - EXTI_InitTypeDef EXTI_InitStructure;9 m4 u' z# v' }. H2 F, e, f
- i3 [( k/ H0 ~: b+ q
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); // 开启AFIO时钟
o$ g+ F/ N2 M4 T* K( S2 M - , d- Y6 {( W, h9 W. G6 V; B% W
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); //选择GPIO管脚用作外部中断线路
) n, T( ~- u3 h6 I/ Q; r w% c# [ -
0 _% p7 z' d5 [ - //EXTI0 NVIC 配置
! Q! e+ b2 l/ y6 b" L" G3 G8 X - NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //EXTI0中断通道
; z- O# \5 q* `% t - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级
8 }7 }2 S" @0 ]9 O; h- i! V5 D - NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级4 v$ w$ P: B, E T2 s
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能: s+ `7 W4 y% S. q9 b8 d7 z
- NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
) @8 \0 T! D# o9 W - * f4 G# `; D/ J" Q6 e3 A4 N; P. |
- EXTI_InitStructure.EXTI_Line=EXTI_Line0; // EXIT0
) C& ~/ x/ \1 V% G1 ? - EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt; // 中断
. d# e0 o$ \! Q5 {9 D1 W& R- Y - EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising; // 上升沿触发% t' ^( w. e( e# V" ]
- EXTI_InitStructure.EXTI_LineCmd=ENABLE; // 使能/ L( [# h/ h1 @3 F2 @7 B+ Y; z
- EXTI_Init(&EXTI_InitStructure);% n3 z% H' v' C. `% h
- }
复制代码 " i$ ^& ~& O/ o6 ^$ N
4.3 编写中断服务函数
) h& g, c- ^3 c, z! ^上面介绍了如何找中断服务函数的函数名,这里直接开始写中断服务函数。这里的中断服务函数比较简单,直接点亮LED1即可。& K0 \$ L& c' b1 o/ G
- /*4 O, @, X0 b, k. ?
- *==============================================================================
6 [. @. z3 a# M - *函数名称:EXTI0_IRQHandler
" f. {( ^; H9 z* |* m - *函数功能:外部中断0中断服务函数. z9 l& U- I4 p' y
- *输入参数:无
- \; I( k! O6 m4 N) L. [) L$ S - *返回值:无
# H" ]# U3 n6 ]+ D% r# \3 y& B - *备 注:无% e9 Z" O6 \$ z- W0 D) l3 Q. I
- *==============================================================================% c, Z( H6 x7 V5 d
- */# ?8 Z% E1 k' R) ^
- void EXTI0_IRQHandler(void)/ k G; [( A8 C( L
- { \, d2 b0 C/ n1 b
- // 如果EXIT0中断标志位被置17 [0 i/ `; g5 x2 ?$ m
- if(EXTI_GetITStatus (EXTI_Line0)==1)) K J) ?# ^2 L. c2 U& f' L& y
- {
/ n# `( ~7 |2 V6 Y0 M - Med_Led_StateCtrl (LED1,LED_ON); // 点亮LED1
' s0 n4 K, B& h: ~2 m0 k$ e( A - }
8 K& y! s8 v$ E - EXTI_ClearITPendingBit (EXTI_Line0); // 清除中断标志位
, I- P/ i7 i/ M2 e* ]3 l- W( X - }
复制代码 " [; v x! M/ O& z
至此,按下WK UP后,LED1会点亮。这种方法与之前的按键点亮LED有什么区别?之前的按键点亮LED是在main函数的while中实现的,而利用外部中断的方法,是在外部中断的中断服务函数中实现的。即使main函数的while轮询业务中没有按键业务,按键依旧可以起作用。
2 {, b s% t! \5 A) g r' z0 y
/ ?3 ^/ I) m9 k* J8 {5 p
: @% e/ z! N0 W0 z五、注意事项. S1 f5 W6 g3 N2 q# S3 S; D$ `
• 中断服务函数无需在.h文件中声明, R1 @ D( _2 w1 } i+ O
• 中断服务函数中不要有过长的业务
; n+ ` f8 t' P' C" J! L• 中断服务函数最后需要清除中断标志位* c6 p: A- R% r: R& T2 p9 U. n/ t
" W3 h5 n' r1 p6 d. I. }9 \! j. [. K
转载自: 二土电子
, }8 ?# k- i/ g+ G# G- ^如有侵权请联系删除5 u9 X! `2 R' U. W/ X
$ P! |; _& i8 L7 j/ t
9 C Q/ D% O: g" d9 { |