|
STM32具有十分强大的中断系统,将中断分为了两个类型:内核异常和外部中断。并将所有中断通过一个表编排起来,下面是stm32中断向量表的部分内容:
. }2 w: {4 l$ n1 W ; {# Q6 ]5 u, l2 y# y3 r) O+ n
& N1 z \; Z, k4 \6 c
上图-3到6这个区域被标黑了,这个区域就是内核异常。内核异常不能够被打断,不能被设置优先级(也就是说优先级是凌驾于外部中断之上的)。常见的内核异常有以下几种:复位(reset),不可屏蔽中断(NMI),硬错误(Hardfault),其他的也可以在表上找到。 从第7个开始,后面所有的中断都是外部中断。外部中断是我们必须学习掌握的知识,包含线中断,定时器中断,IIC,SPI等所有的外设中断,可配置优先级。外部中断的优先级分为两种:抢占优先级和响应优先级。 什么是抢占优先级?抢占优先级比较霸道,一言不和就插队。抢占优先级高的,能够打断优先级低的任务,等优先级较高的任务执行完毕后,再回来继续执行之前的任务。所以当存在多个抢占优先级不同的任务时,很有可能会产生任务的嵌套。 什么是响应优先级?响应优先级则稍微谦逊些,比较有礼貌。响应优先级又被称为次优先级,若两个任务的抢占式优先级一样,那么响应优先级较高的任务则先执行,且在执行的同时不能被下一个响应优先级更高的任务打断,所以我说它比较有有礼貌。。 中断控制器(NVIC)因为stm32的中断系统比较复杂,所以在内核中有一个专门管理中断的控制器:NVIC. NVIC负责除了SYSTICK之外的所有中断的控制,十分重要! 在标准库中,提供了一套通过NVIC来控制中断的API,我们首先来看NVIC_Init()函数,这套函数首先要定义并填充一个结构体:NVIC_InitTypeDef 该结构体的定义如下: NVIC_IRQChannel 需要配置的中断向量 NVIC_IRQChannelCmd 使能或者关闭相应中断向量的中断响应 NVIC_IRQChannelPreemptionPriority 配置相应中断向量的抢占优先级 NVIC_IRQChannelSubPriority 配置相应中断的响应优先级
结构体的四个成员都比较好理解,这里就不再累述了。 不过要注意一点的是,NVIC只可以配置16种中断向量的优先级,其抢占优先级和响应优先级都用一个4位的数字来决定。在库函数中,将其分为了5中不同的分配方式: 第0组:所有的4位都有来表示响应优先级,能够配置16种不同的响应优先级。中断优先级则都相同。 第1组:最高一位用来配置抢占优先级,剩余三位用来表示响应优先级。那么就有两种不同的抢占优先级(0和1)和8种不同的响应优先级(0~7)。 第2组:高两位用来配置抢占优先级,低位用来配置响应优先级。那么两种优先级就各有4种。 第3组:高三位用来配置抢占优先级,低位用来配置响应优先级。有8种抢占优先级和2种相应优先级。 第4组:所有位都用来配置抢占优先级,即有16种抢占优先级,没有响应属性。
这5种不同的分配方式,根据项目的实际需求来配置。 配置的API如下: - NVIC_PriorityGroupConfig();
复制代码 : w7 I/ z6 _4 t$ i2 z
其中括号内可以输入以下一个参数,代表不同的分配方式: NVIC_PriorityGroup_0 NVIC_PriorityGroup_1 NVIC_PriorityGroup_2 NVIC_PriorityGroup_3 NVIC_PriorityGroup_4
% F. h& p* H' D6 N- r7 U. V
EXTI外部中断STM32的所有GPIO都引入到了EXTI外部中断线上,也就是说,所有的IO口经过配置后都能够触发中断。下图就是GPIO和EXTI的连接方式: 从上图我们可以看出,一共有16个中断线: EXTI0到EXTI15. 每个中断线都对应了从PAx到PGx一共7个GPIO。也就是说,在同一时刻每个中断线只能相应一个GPIO端口的中断,不能够同时相应所有端口的中断事件,但是可以分时复用,在程序执行过程中,这个不需要我们太多的去关心。我们关心最多的是中断触发的方式: 1 c6 S# W- W, e' V
7 e* w7 l) e# d+ f! J9 O: t
在EXTI中,有三种触发中断的方式:上升沿触发,下降沿触发,双边沿触发。根据不同的电路,我们选择不同的触发方式,以确保中断能够被正常触发。 2 H( _ F2 C: }9 A# ?% N
实例为了便于理解,这里我们将中断配置代码贴上来。 - void NVIC_Configuration(void)% S' c9 ]8 A- q! L5 Z
- {
6 P0 o; n% H$ q. l - NVIC_InitTypeDef NVIC_InitStructure;
. Z' G4 @ Z; W) O0 L - , C8 S7 E+ _6 _7 Z
- /* 配置NVIC为优先级组1 */
/ @! d. W* J' \ - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
0 ~; J/ G8 ?6 l) x - , |* g2 A2 h) `# R: I# U5 }
- /* 配置中断源:按键1 */' ]9 S# q0 p$ h1 @+ z* _4 F3 J" {5 F. {6 W
- NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //配置为EXTI0通道* v) v- @: ~& k" D$ ^
- /* 配置抢占优先级 */
* ?6 N; h {6 i# o - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
7 I5 j; N3 @* E) f2 a - /* 配置子优先级 */, t8 f5 t; o* Y q6 V4 ~
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;. |+ L4 W4 F5 q$ F. U2 L5 u
- /* 使能中断通道 */) n( A/ t3 G F4 w0 q6 x% F; S
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
& m5 r- K' @1 C$ I3 G7 ?$ ` - NVIC_Init(&NVIC_InitStructure); //将上述配置参数传入中断初始化函数# F* r; O9 _8 N' @$ ^ m$ w1 w
- }' B* Z7 ~; X- X# t, b
- + P9 |; W3 }: W, O1 c8 k& l: C9 _
复制代码 : {7 G4 w- |* z& d) c8 _# o
除了中断线的配置,我们还要配置对应引脚 - void EXTI_Key_Config(void), P% p. C2 i/ s# y$ s
- {
8 j1 b9 d. v/ I8 p - GPIO_InitTypeDef GPIO_InitStructure; 0 U! k/ D1 j% M2 F% o w. g8 W
- EXTI_InitTypeDef EXTI_InitStructure;7 B4 V y4 j; C
- * F# x9 k" N3 H" o3 K
- /*开启按键GPIO口的时钟*/( C: k8 b& H- z+ X6 l9 y
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);
# K& L; ^# p) [8 Q8 E -
: |* S0 K* p0 e9 Q) w& B+ f: I - /* 配置 NVIC 中断*// b/ o7 i6 \$ z5 Y3 w0 U
- NVIC_Configuration();% c3 t3 m* p6 z
- /*--------------------------KEY1配置-----------------------------*/, [' |& o/ ~0 A" }
- /* 选择按键用到的GPIO */ . t8 `& X0 T- f. x
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
6 G' H1 }/ _, ?& V1 N$ ~ - /* 配置为浮空输入 */
, _! O6 g- \6 @ - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
: k0 \) o$ ^" ^; `5 |7 f - GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure);* d6 \, x& R2 f4 D
- " Z& A- h. }( a2 F
- /* 选择EXTI的信号源 */
/ B( r. \1 J! p6 ]. q3 Z - GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); ; [0 t; I9 [. Q0 e
- EXTI_InitStructure.EXTI_Line = EXTI_Line0;3 p" l8 `3 r* ~4 m3 L/ H; {
- 0 S9 f" _( V2 u. M+ e3 |
- /* EXTI为中断模式 */
8 ~6 ^( M% [, U {" S* l - EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;: J1 I2 I3 B: N
- /* 上升沿中断 */1 N ~1 U1 r/ f; a3 w9 u- {
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
6 z. L# j( H Y/ i, g - /* 使能中断 */
5 s' C3 K& N( F, ] - EXTI_InitStructure.EXTI_LineCmd = ENABLE;1 p# _( f4 Z; g6 K) f
- EXTI_Init(&EXTI_InitStructure);
, s/ x) H0 ~# l, y8 p - }- L3 G! x6 K8 ?" M0 L* w' `6 q
复制代码
( C, A3 G1 F# Z& Q8 J& m0 j至此,中断的配置完毕。相信你已经看出来,上述代码是将PA0配置为上升沿中断。不过,现在只能够说该中断已经配置完毕,但我们还不能使用它。我们还缺少一个中断的执行函数。 当中断被触发后,程序要马上跳转到中断函数去执行中断操作,这个函数在工程创建时默认时没有的。需要你自己去添加。而且需要注意的是,中断函数的名称必须是由标准库提供的,否则无法识别。 我们打开startup_stm32f10x_hd.s这个文件,在里面能找到这么一段代码: - ; External Interrupts
" t& }" i8 q7 _+ z" g - DCD WWDG_IRQHandler ; Window Watchdog
, _* O1 N/ L$ X& Z" M5 u - DCD PVD_IRQHandler ; PVD through EXTI Line detect
6 ^0 i N b" B3 ~" M: d - DCD TAMPER_IRQHandler ; Tamper+ A0 P9 V, }2 M6 b7 I8 F
- DCD RTC_IRQHandler ; RTC
4 F8 K: I' e8 G1 u- w. w - DCD FLASH_IRQHandler ; Flash9 M& m" M& O; z# |; l
- DCD RCC_IRQHandler ; RCC( D/ ]$ h! f" u) p: k1 v4 Z) t
- DCD EXTI0_IRQHandler ; EXTI Line 06 W1 k$ Q# X5 [* b( s1 g8 T
- DCD EXTI1_IRQHandler ; EXTI Line 1
+ F9 r- {% S+ _7 a, w1 X - DCD EXTI2_IRQHandler ; EXTI Line 2
, e) w6 N, b: Z - DCD EXTI3_IRQHandler ; EXTI Line 3
2 y: J" i$ u5 Q( r/ ^$ I9 p, @# ?2 z2 u# @ - ...
8 t# f( ]! U0 k7 z8 q - ...
3 `. D% f! R+ v) Q. O; D - ...# k- F) g' }* Y1 ~/ f9 y! q8 ]
复制代码
- v) i4 x7 \1 V" p不难看出,EXTI0_IRQHandler 就是中断线0的中断函数,所以,我们把这个函数添加到工程中即可。最好添加到stm32f10x_it.c 这个文件中,方便管理。 可以在这个函数中添加你想要的功能,代码如下: - void EXTI0_IRQHandler(void)
1 l$ ~/ t7 l# ^9 N) H - {$ Z$ R4 X" D" Q8 s- c
- //确保是否产生了EXTI Line中断
. c, ]. |, F+ N& M - if(EXTI_GetITStatus(EXTI_Line0) != RESET) * x! M& P0 C7 L
- {
# U- Z, \, B0 B7 d9 h - /******/
- u) ~. Q, C) R+ K - //LED闪烁相关代码6 f$ `5 y0 {1 e. @! u6 U, W
- /******/8 O# F, u% h; \" p
- //清除中断标志位
7 j" `/ t8 u4 A, P, O" F - EXTI_ClearITPendingBit(EXTI_Line0);
* v6 I2 V( E' J2 g! O - }
1 r$ [+ I0 K7 g/ [" ?0 G - }
- Y2 y+ M6 U0 y! b
复制代码 / n! k3 x+ d# J
至此,整个中断的流程梳理完毕
$ x% `5 q4 M: O% z( C+ E( {1 Z- g |