|
STM32具有十分强大的中断系统,将中断分为了两个类型:内核异常和外部中断。并将所有中断通过一个表编排起来,下面是stm32中断向量表的部分内容: / K k! l" n# P- Y7 S. ^
- p Y+ D- ]& n' R5 \3 t
; r; h; t* J; K3 R2 e" s! b
上图-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();
复制代码
% e; X- i$ M# t/ X2 }8 R3 o其中括号内可以输入以下一个参数,代表不同的分配方式: NVIC_PriorityGroup_0 NVIC_PriorityGroup_1 NVIC_PriorityGroup_2 NVIC_PriorityGroup_3 NVIC_PriorityGroup_4 9 S; F# z/ p& H8 }, |
EXTI外部中断STM32的所有GPIO都引入到了EXTI外部中断线上,也就是说,所有的IO口经过配置后都能够触发中断。下图就是GPIO和EXTI的连接方式: 从上图我们可以看出,一共有16个中断线: EXTI0到EXTI15. 每个中断线都对应了从PAx到PGx一共7个GPIO。也就是说,在同一时刻每个中断线只能相应一个GPIO端口的中断,不能够同时相应所有端口的中断事件,但是可以分时复用,在程序执行过程中,这个不需要我们太多的去关心。我们关心最多的是中断触发的方式: 1 A& G E6 @" r4 B1 [! X8 T
2 r: i) t3 p0 E) M) Z
在EXTI中,有三种触发中断的方式:上升沿触发,下降沿触发,双边沿触发。根据不同的电路,我们选择不同的触发方式,以确保中断能够被正常触发。 " L4 s9 l' q# l
实例为了便于理解,这里我们将中断配置代码贴上来。 - void NVIC_Configuration(void)
- u2 c" a7 I0 P( ~1 u, @ - {: F h% e: n: K3 q( J: |$ R6 x7 I
- NVIC_InitTypeDef NVIC_InitStructure;
" A/ w; D& h& ]/ ^8 ^* Q - 3 g* J& y" Y4 Q3 D, m, ~
- /* 配置NVIC为优先级组1 */
. ?. ?# T; f6 v) _% b - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
7 _* C1 A% Y- D" v# X) S - [) j" _$ `; C# ^
- /* 配置中断源:按键1 */9 D# p2 g; T U) {% |
- NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //配置为EXTI0通道( K% W. M2 N5 Z$ m' d- |
- /* 配置抢占优先级 */& p5 q7 y7 Y* G; a. z3 K
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;7 g8 q8 h, S) E6 x
- /* 配置子优先级 */7 P6 T$ O7 `+ |; T& X
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
9 m, A) y* \" \) h - /* 使能中断通道 */
, C; S! H0 k |; ]* U8 ^ - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
* v. m. f) R7 V1 c - NVIC_Init(&NVIC_InitStructure); //将上述配置参数传入中断初始化函数
2 |, L4 |3 s% x - }
% r* O- A8 F* i$ v/ h0 P. f - # D) F% q6 l, H, V d' |
复制代码
6 a- n3 p9 {! D% l! h4 a除了中断线的配置,我们还要配置对应引脚 - void EXTI_Key_Config(void)3 z! R! g' X( l3 F0 P0 z% M
- {; E7 |% a/ | ~/ h# l
- GPIO_InitTypeDef GPIO_InitStructure; ) r( G3 w: b; H1 u& h) }
- EXTI_InitTypeDef EXTI_InitStructure;4 A; d' K: |* Z/ s4 j
- ( l8 W: I" D4 {* K, w5 {9 g
- /*开启按键GPIO口的时钟*/1 K5 W% v; @- ^: _9 g3 H
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);
) u: t' w) o5 h. \! M, M -
, B& ~4 U* n0 C+ c% h) w1 D Z - /* 配置 NVIC 中断*/
* |$ C$ X/ `8 Z* A: S# H( k - NVIC_Configuration();
6 H2 E6 z6 {. E3 x - /*--------------------------KEY1配置-----------------------------*/
# H( u1 n* ^7 k; f/ y" s/ ?- t - /* 选择按键用到的GPIO */
, |5 i5 v% O/ h" J/ W' H1 O - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
* N! a" _+ f" w3 Y( X9 Z - /* 配置为浮空输入 */
$ \/ \' j$ P; B- @0 M6 X - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
: j% f! n D1 o$ u5 H+ a+ t2 R - GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure);
* b& Z! }! \, {$ N - 1 J+ g; L) |. E6 z& Y
- /* 选择EXTI的信号源 */8 S2 A9 j0 Q$ ?9 |3 q5 s
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); 9 W X; H& Q* ~: k
- EXTI_InitStructure.EXTI_Line = EXTI_Line0;4 I% I% }* k3 Q
-
6 h! T9 c6 u8 _0 m% ^3 A' f - /* EXTI为中断模式 */
% c- Q/ p- Y* v3 ~+ e0 @( f7 L9 z - EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
' s' I5 p- Q6 | ^0 U1 d - /* 上升沿中断 */& a0 k3 r5 b* d% }: m m
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
1 Q5 k! _, X/ ^$ n4 { - /* 使能中断 */ & D* V: n# J5 `4 m
- EXTI_InitStructure.EXTI_LineCmd = ENABLE;
! H& N/ q, \# X' \) d - EXTI_Init(&EXTI_InitStructure);
* n- N/ j3 U9 m' K - }
( i9 T2 h+ X, S" {( M# }+ E" t
复制代码
8 K# e8 A$ Y- @$ b+ J至此,中断的配置完毕。相信你已经看出来,上述代码是将PA0配置为上升沿中断。不过,现在只能够说该中断已经配置完毕,但我们还不能使用它。我们还缺少一个中断的执行函数。 当中断被触发后,程序要马上跳转到中断函数去执行中断操作,这个函数在工程创建时默认时没有的。需要你自己去添加。而且需要注意的是,中断函数的名称必须是由标准库提供的,否则无法识别。 我们打开startup_stm32f10x_hd.s这个文件,在里面能找到这么一段代码: - ; External Interrupts$ \& W" C% K1 b( ^# @
- DCD WWDG_IRQHandler ; Window Watchdog! K: l" p# ^5 \! V( a
- DCD PVD_IRQHandler ; PVD through EXTI Line detect
# M( e: _( z6 B# K4 s2 l8 O - DCD TAMPER_IRQHandler ; Tamper
9 P- x/ O$ b$ X& P. T - DCD RTC_IRQHandler ; RTC9 L5 V L2 _9 A; a
- DCD FLASH_IRQHandler ; Flash
$ v0 G! w0 c9 H, x# X' `/ D - DCD RCC_IRQHandler ; RCC
. f' ?. L2 u" @3 s - DCD EXTI0_IRQHandler ; EXTI Line 0
) W% {$ K( X7 v+ s: G# \ - DCD EXTI1_IRQHandler ; EXTI Line 1
+ |9 p, C" t- } - DCD EXTI2_IRQHandler ; EXTI Line 2
2 D9 ]: u e6 T5 u - DCD EXTI3_IRQHandler ; EXTI Line 3' i! @: K6 Y" S& q$ {) d- H
- ...
9 I0 n. M6 Q1 }/ d - ... O& }2 ?9 m9 T. k8 c
- ...
$ [3 |* ^$ R" n; z# l7 v; f9 I* L0 C
复制代码 - T. g5 w* y( T5 } n
不难看出,EXTI0_IRQHandler 就是中断线0的中断函数,所以,我们把这个函数添加到工程中即可。最好添加到stm32f10x_it.c 这个文件中,方便管理。 可以在这个函数中添加你想要的功能,代码如下: - void EXTI0_IRQHandler(void)
( W+ @/ U. P6 O" U - {
( F* E2 e. a' G: j* { - //确保是否产生了EXTI Line中断3 n% W3 t6 ~2 Z4 J* U) l) w+ V
- if(EXTI_GetITStatus(EXTI_Line0) != RESET) 9 V5 u# `2 q' A' j0 H& b4 I9 a
- {+ u/ V7 e7 [0 L: h
- /******/
! F h6 S) d2 t( T - //LED闪烁相关代码
5 k! N+ w! Z( w3 L4 L - /******/ v0 {0 y5 _0 {; `! x
- //清除中断标志位9 a8 H+ I7 a5 x) V6 w, `
- EXTI_ClearITPendingBit(EXTI_Line0);
5 u6 J' p o3 B. N4 w- q - } 1 j/ F* q* X# e8 j0 o0 I
- }
2 ^3 A) ^& p% v( |# @& v! b1 I- u
复制代码 8 a/ Y/ ^+ j4 c8 O. h" }- n
至此,整个中断的流程梳理完毕
; E( r2 T8 Z% q. C. p9 l1 S5 D |