|
STM32具有十分强大的中断系统,将中断分为了两个类型:内核异常和外部中断。并将所有中断通过一个表编排起来,下面是stm32中断向量表的部分内容:
* j6 J6 t+ Y2 f- @1 `, m* C
# |' C& n0 }% e
* j( M1 h" X5 t$ o$ N& Z
上图-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();
复制代码
+ @" ?! P) m9 z+ J. v. F$ i( e6 R* x其中括号内可以输入以下一个参数,代表不同的分配方式: NVIC_PriorityGroup_0 NVIC_PriorityGroup_1 NVIC_PriorityGroup_2 NVIC_PriorityGroup_3 NVIC_PriorityGroup_4 $ r1 @) G' \ y' H" A3 Y4 H
EXTI外部中断STM32的所有GPIO都引入到了EXTI外部中断线上,也就是说,所有的IO口经过配置后都能够触发中断。下图就是GPIO和EXTI的连接方式: 从上图我们可以看出,一共有16个中断线: EXTI0到EXTI15. 每个中断线都对应了从PAx到PGx一共7个GPIO。也就是说,在同一时刻每个中断线只能相应一个GPIO端口的中断,不能够同时相应所有端口的中断事件,但是可以分时复用,在程序执行过程中,这个不需要我们太多的去关心。我们关心最多的是中断触发的方式: 6 N# Q$ d" @5 `1 L2 {$ T' s$ [1 |
1 i- {. c: \0 i+ b1 Y" W8 O; I/ J C
在EXTI中,有三种触发中断的方式:上升沿触发,下降沿触发,双边沿触发。根据不同的电路,我们选择不同的触发方式,以确保中断能够被正常触发。 9 f |8 o/ p) T2 p, @! D5 W
实例为了便于理解,这里我们将中断配置代码贴上来。 - void NVIC_Configuration(void)
; |1 |- V7 L/ L: K - {. X+ E) o* N, \6 m
- NVIC_InitTypeDef NVIC_InitStructure;8 _ u& h H* m( l2 \6 `
- . l, T, V w$ L2 S( w! d
- /* 配置NVIC为优先级组1 */9 t6 P2 e1 \1 w" u/ B" [3 `$ b
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);2 z2 @/ `7 G, q4 d$ n; w9 n
- ( g& M, t) U- j, E% A
- /* 配置中断源:按键1 */$ Q5 q& k `1 q% v2 [- |. |3 B
- NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //配置为EXTI0通道; W- r* G, Y" i- U
- /* 配置抢占优先级 */6 a. L$ s, W N. |( r7 k# n0 C
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;# v6 Q5 ?! w* e
- /* 配置子优先级 */
; v7 x4 o. K( ~( k: j - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;9 _. B& p/ k+ V: h
- /* 使能中断通道 *// S9 n! f+ v Y1 ?
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;; W3 S4 }1 Q3 j3 R3 q4 J1 z9 H1 Q; e
- NVIC_Init(&NVIC_InitStructure); //将上述配置参数传入中断初始化函数
; B9 S/ g/ Z4 X - }
1 e: M" P3 S; a+ V/ W - ; @( s, F4 z% P' Z8 {
复制代码
0 U6 Q7 V, p9 |' `9 J除了中断线的配置,我们还要配置对应引脚 - void EXTI_Key_Config(void)
% W, ` Z- w& W - {) U2 S I% U( }, e4 p9 _" e! m- T
- GPIO_InitTypeDef GPIO_InitStructure; # n7 d) M) T4 T7 E2 b9 z
- EXTI_InitTypeDef EXTI_InitStructure;
2 x5 K- C) W T: A0 a! f5 r
3 L3 d$ Y7 o* b+ [( [- /*开启按键GPIO口的时钟*/6 S6 A: y% S7 B
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);
* m, ]* ^2 l. x. u* M -
8 g. |9 X# R7 o' M4 P& ] - /* 配置 NVIC 中断*/
; ]9 ?4 C9 E7 m& C. P - NVIC_Configuration();
8 C. U1 }8 ~- a% h - /*--------------------------KEY1配置-----------------------------*/ U6 k( K5 y5 W4 c7 F8 H- V
- /* 选择按键用到的GPIO */ 0 i7 `8 C5 m* e2 i% [
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;( M9 j8 C ^, R
- /* 配置为浮空输入 */
3 a: j6 B" W: }: J7 j8 y1 e - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
! B7 ?7 V$ Z7 x - GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure);
# y5 L; p, H. t0 n1 x) \ - / p- a, K% L* |# T' ?: p; n$ e
- /* 选择EXTI的信号源 */* M: v0 j8 L# v
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
" H/ p# Y, C# j8 |, ~7 y - EXTI_InitStructure.EXTI_Line = EXTI_Line0;8 ?$ T4 r# [' R
-
, ^1 e: t# x: ?7 I! f - /* EXTI为中断模式 */! c6 N) N% H6 Q2 ?% Z2 L6 a5 J
- EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
# }- ]! F: v+ }- M - /* 上升沿中断 */ L( r1 C# }3 }. _
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;4 b7 G# G! @$ Q" r
- /* 使能中断 */ 9 Q; W7 @ H; v0 ]7 D5 D
- EXTI_InitStructure.EXTI_LineCmd = ENABLE;
0 q$ y/ U; y# Q8 r8 Q8 s% Q - EXTI_Init(&EXTI_InitStructure);$ W7 h: f! A& A
- }
u: `& j+ ]* c
复制代码 6 g: N0 D* d& R/ E2 Y# `
至此,中断的配置完毕。相信你已经看出来,上述代码是将PA0配置为上升沿中断。不过,现在只能够说该中断已经配置完毕,但我们还不能使用它。我们还缺少一个中断的执行函数。 当中断被触发后,程序要马上跳转到中断函数去执行中断操作,这个函数在工程创建时默认时没有的。需要你自己去添加。而且需要注意的是,中断函数的名称必须是由标准库提供的,否则无法识别。 我们打开startup_stm32f10x_hd.s这个文件,在里面能找到这么一段代码: - ; External Interrupts# e$ K/ Z W* `7 Z3 s6 I" j
- DCD WWDG_IRQHandler ; Window Watchdog: G( c) }5 f1 a$ D) ?1 w( Q: v' E% q1 o
- DCD PVD_IRQHandler ; PVD through EXTI Line detect7 r$ ?& j% J# u
- DCD TAMPER_IRQHandler ; Tamper5 @) o8 c4 L4 E3 m9 ]: J1 \/ j' V
- DCD RTC_IRQHandler ; RTC
. J, n* d5 p3 d) p- C3 K - DCD FLASH_IRQHandler ; Flash
% ]$ P" H8 w; q. Q) H - DCD RCC_IRQHandler ; RCC
A V# j% R0 M5 R - DCD EXTI0_IRQHandler ; EXTI Line 0
$ `1 k5 c/ P- U - DCD EXTI1_IRQHandler ; EXTI Line 1
" b) ]$ p+ A& W2 [ - DCD EXTI2_IRQHandler ; EXTI Line 2
& {: {' q% L3 ^& g4 O( E - DCD EXTI3_IRQHandler ; EXTI Line 3
' ?+ }; z: R; l8 w& c - .... b! d: u( V8 Z- W9 @! G: w, ^! z
- ...- e6 w0 q6 c* _/ X3 J
- ...$ @' C# _- _* N$ f" e4 Z
复制代码
4 x: n/ J* _) Y9 D* P9 _# k0 q& m' D不难看出,EXTI0_IRQHandler 就是中断线0的中断函数,所以,我们把这个函数添加到工程中即可。最好添加到stm32f10x_it.c 这个文件中,方便管理。 可以在这个函数中添加你想要的功能,代码如下: - void EXTI0_IRQHandler(void)/ }# g, @5 r5 k8 d: l4 n" Q0 \& N
- {, V# W" I7 d2 d7 C" E
- //确保是否产生了EXTI Line中断
0 _- Z9 x5 u" e. |! \2 C7 a - if(EXTI_GetITStatus(EXTI_Line0) != RESET)
: @2 X8 d1 j1 w. u# J4 k9 T k - {5 J0 Q; u/ @9 X5 ^% u% V
- /******/
& i. C; f: J3 U. R - //LED闪烁相关代码
, L% }" j2 R( q - /******// P& e- C4 i) _* z3 h D$ @( x8 I; `
- //清除中断标志位# z% p$ D9 H* X
- EXTI_ClearITPendingBit(EXTI_Line0);
& ]4 V. s1 d% v$ e - }
/ }! W, }" L9 e# b - }" E' z L, M$ z0 F& B* l+ n* t
复制代码
7 _7 W: \) W& `" D' O; E% |, U' o- E至此,整个中断的流程梳理完毕
G' `. `- v( R3 M+ r |