|
STM32具有十分强大的中断系统,将中断分为了两个类型:内核异常和外部中断。并将所有中断通过一个表编排起来,下面是stm32中断向量表的部分内容: % ]+ c3 e0 N; S$ @$ W& ~
: H( }: K6 n" j
1 S9 o# i$ z1 ~4 O$ j
上图-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();
复制代码
* O! S# h" s% v9 ]) Z其中括号内可以输入以下一个参数,代表不同的分配方式: NVIC_PriorityGroup_0 NVIC_PriorityGroup_1 NVIC_PriorityGroup_2 NVIC_PriorityGroup_3 NVIC_PriorityGroup_4
" k* G' ~2 ? [1 L6 l
EXTI外部中断STM32的所有GPIO都引入到了EXTI外部中断线上,也就是说,所有的IO口经过配置后都能够触发中断。下图就是GPIO和EXTI的连接方式: 从上图我们可以看出,一共有16个中断线: EXTI0到EXTI15. 每个中断线都对应了从PAx到PGx一共7个GPIO。也就是说,在同一时刻每个中断线只能相应一个GPIO端口的中断,不能够同时相应所有端口的中断事件,但是可以分时复用,在程序执行过程中,这个不需要我们太多的去关心。我们关心最多的是中断触发的方式: ) x* G! F7 K" u$ o7 r8 e' w
; N/ ?. Q, O7 _( i. X
在EXTI中,有三种触发中断的方式:上升沿触发,下降沿触发,双边沿触发。根据不同的电路,我们选择不同的触发方式,以确保中断能够被正常触发。
( y: c% X1 L' S4 \/ [2 O# f- L" J D& u 实例为了便于理解,这里我们将中断配置代码贴上来。 - void NVIC_Configuration(void)5 m- D2 k3 K' q& v
- {
. U! R# `! H0 ?9 Q* O% m# w - NVIC_InitTypeDef NVIC_InitStructure;
' T: P8 B h6 O* f& ~7 w1 B& ^ - : F1 v, ?; {7 }" `3 u; `7 v* O9 D' F5 U
- /* 配置NVIC为优先级组1 */ O- K# d; U, h7 S9 f
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);5 v8 H6 \9 ?. U/ \0 T
- $ x! {* L) U3 ^+ E
- /* 配置中断源:按键1 */& t5 B& D" E5 y$ j
- NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //配置为EXTI0通道
8 r9 \& |, `8 E* ^, o' ?6 Q - /* 配置抢占优先级 */
6 M4 G) a, q. z- [4 c8 _ - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;) P# ?& B3 ^2 J1 R: o9 | |; U. m: {
- /* 配置子优先级 */0 ]" v7 M* S" m, `
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
% O3 {8 L: b7 B9 r& a$ z+ h - /* 使能中断通道 */6 p0 C, i* G+ X. [
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
" n, |) N4 p2 ?( {7 \: o - NVIC_Init(&NVIC_InitStructure); //将上述配置参数传入中断初始化函数- j o7 m- D) v0 B
- }
G! {; d ~' R) e3 G2 ]8 y" l
' n% {- z; A Q- i
复制代码
' x/ T6 k+ T$ _ @* N/ T除了中断线的配置,我们还要配置对应引脚 - void EXTI_Key_Config(void)& u/ |- B K- u1 k( ?
- { O" D k" U) X7 M6 e B* Z
- GPIO_InitTypeDef GPIO_InitStructure;
* k; n/ r3 P) z F4 z8 L$ e - EXTI_InitTypeDef EXTI_InitStructure;4 k4 @2 K5 [* q! t# @1 X' y
- + C; b* @' U( @2 h
- /*开启按键GPIO口的时钟*/
1 B9 L9 J. I3 t& m; d: F; X - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);
1 U" E" F; F P- j - / ?' w, ~ A N5 p% m1 P
- /* 配置 NVIC 中断*/
% R% y; ~$ j" S, ?8 Y - NVIC_Configuration();
3 i4 _/ f5 [9 I% t" o% m - /*--------------------------KEY1配置-----------------------------*/. L$ X" j. Q {9 i1 F
- /* 选择按键用到的GPIO */
7 G0 _% E7 x6 L# X/ } y - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
5 X( l: x; I5 `: x: F( h - /* 配置为浮空输入 */ - p3 Q9 F8 O* W- a; b/ K; ?
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;; u/ G7 m. @# p. S" }6 ?5 L
- GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure);2 v+ c/ l1 _" ~, d
- 3 w H7 P$ Q4 N4 A
- /* 选择EXTI的信号源 */
& Y# ?4 k5 ?% |( I - GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
5 T, b! }: Y( R7 v e - EXTI_InitStructure.EXTI_Line = EXTI_Line0;( @* P4 O# w& K" Q
-
5 X- g1 \0 {6 j9 O& |; k [ - /* EXTI为中断模式 */
+ n; X; D% w# a- |6 |& ~; G% y - EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;% f$ Z1 ?" m/ M
- /* 上升沿中断 */9 H3 r1 w1 \3 M( T# s* B! x; d
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;7 U1 G# [7 N3 J% z$ i N; d5 C# d
- /* 使能中断 */ ( p/ M% q5 @+ W5 I$ ~) S
- EXTI_InitStructure.EXTI_LineCmd = ENABLE;
; `% {0 T1 _* I7 M9 L2 }. y - EXTI_Init(&EXTI_InitStructure);
* F& S8 z" B3 _9 ?4 e9 y - }- _' Z8 i; K$ E, p5 f
复制代码
# {5 M+ A5 P! d0 O至此,中断的配置完毕。相信你已经看出来,上述代码是将PA0配置为上升沿中断。不过,现在只能够说该中断已经配置完毕,但我们还不能使用它。我们还缺少一个中断的执行函数。 当中断被触发后,程序要马上跳转到中断函数去执行中断操作,这个函数在工程创建时默认时没有的。需要你自己去添加。而且需要注意的是,中断函数的名称必须是由标准库提供的,否则无法识别。 我们打开startup_stm32f10x_hd.s这个文件,在里面能找到这么一段代码: - ; External Interrupts
. m' M! R- ^+ i6 X$ c% `9 Y - DCD WWDG_IRQHandler ; Window Watchdog
/ s2 P2 Q) }; B6 F - DCD PVD_IRQHandler ; PVD through EXTI Line detect! y/ {% {3 }, A4 q% N* B
- DCD TAMPER_IRQHandler ; Tamper
3 N% [" x7 y/ o$ v1 `6 R - DCD RTC_IRQHandler ; RTC
* _; S* x6 h; C6 m, c; A8 z& i! z" a - DCD FLASH_IRQHandler ; Flash
: m' X! w+ F4 |: M4 r1 V. H - DCD RCC_IRQHandler ; RCC
" V# |( o, d. t. V! B0 u8 u - DCD EXTI0_IRQHandler ; EXTI Line 0- w: z' I+ k# M
- DCD EXTI1_IRQHandler ; EXTI Line 11 e7 H! y4 y; a0 m9 L
- DCD EXTI2_IRQHandler ; EXTI Line 2
6 y- y- H6 H( S - DCD EXTI3_IRQHandler ; EXTI Line 3
+ d9 a+ Q* Z2 y0 `# G - ...
3 ^( s l; f0 E - ...
" Z9 B; z) D0 T - ...8 O. R' f8 _5 @' i( ^; z1 @" V
复制代码
s4 p* V* C& O不难看出,EXTI0_IRQHandler 就是中断线0的中断函数,所以,我们把这个函数添加到工程中即可。最好添加到stm32f10x_it.c 这个文件中,方便管理。 可以在这个函数中添加你想要的功能,代码如下: - void EXTI0_IRQHandler(void)' @/ W! Z# \# h% \( Z
- {+ z! E1 M9 \+ }8 j& R
- //确保是否产生了EXTI Line中断9 U# D' O. _" P( F) ]: j" j
- if(EXTI_GetITStatus(EXTI_Line0) != RESET) 6 [) c+ @) z8 O$ c! B% j
- {
4 d( x8 `. B/ v3 t - /******/& }2 b0 O5 ~# X, ]$ K$ ^) b- M; m
- //LED闪烁相关代码
1 Z: J W& w& f9 y/ o h/ p - /******/, O" m l- i; c' R- {# r
- //清除中断标志位3 ]6 K. H. R+ x" ]' G: u' R
- EXTI_ClearITPendingBit(EXTI_Line0);
0 b2 R5 @6 B7 \' s0 Z - } 8 d: _( \. N0 g. ]
- }# u N+ V* t7 x, L: i' R! A1 i
复制代码
3 c1 d9 ^4 C1 ?2 b至此,整个中断的流程梳理完毕
' Y; h, s4 z. V9 M5 _) I |