一.异常类型
中断就是异常,32支持为数众多的系统异常和外部中断。
其中系统异常有
8
个(如果把
Reset
和
HardFault
也算上的话就是
10
个),外部中断有
60
个,如下表所示部分的异常。
EXTI外设的异常只是NVIC异常处理里面的一种。
...............
二. NVIC简介
NVIC 是嵌套向量中断控制 器,控制着整个芯片中断相关的功能,是内核里面的一个外设。
,其寄存器部分,具体 使用了多少可参考《 Cortex-M3 内核编程手册》
-4.3.11:NVIC
寄存器映射。
1
代码
17-1 NVIC 寄存器
结构体定义,来自固件库头文件:
core_cm3.h
1 typedef
struct
{
2
__IO
uint32_t
ISER[8];
//
中断使能寄存器
3
uint32_t
RESERVED0[24];
4
__IO
uint32_t
ICER[8];
//
中断清除寄存器
5
uint32_t
RSERVED1[24];
6
__IO
uint32_t
ISPR[8];
//
中断使能悬起寄存器
7
uint32_t
RESERVED2[24];
8
__IO
uint32_t
ICPR[8];
//
中断清除悬起寄存器
9
uint32_t
RESERVED3[24];
10
__IO
uint32_t
IABR[8];
//
中断有效位寄存器
11
uint32_t
RESERVED4[56];
12
__IO
uint8_t
IP[240];
//
中断优先级寄存器
(8Bit wide)
13
uint32_t
RESERVED5[644];
14
__O
uint32_t
STIR;
//
软件触发中断寄存器
15
} NVIC_Type;
在配置中断的时候我们一般只用
ISER
、
ICER
和
IP
这三个寄存器,
ISER
用来使能中
断,
ICER
用来失能中断,
IP
用来设置中断优先级。
2
NVIC
中断配置固件库 固件库文件 core_cm3.h
的最后,还提供了
NVIC
的一些函数
void NVIC_EnableIRQ(IRQn_Type IRQn) 使能中断
void NVIC_DisableIRQ(IRQn_Type IRQn) 失能中断
void NVIC_SetPendingIRQ(IRQn_Type IRQn) 设置中断悬起位
void NVIC_ClearPendingIRQ(IRQn_Type IRQn) 清除中断悬起位
uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn) 获取悬起中断编号
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority) 设置中断优先级
uint32_t NVIC_GetPriority(IRQn_Type IRQn) 获取中断优先级
void NVIC_SystemReset(void) 系统复位
具体函数操作哪一个NVIC寄存器,需要看函数体怎么实现的。
三 中断优先级定义
在
NVIC
有一个专门的寄存器:中断优先级寄存器
NVIC_IPRx
,用来配置外部中断的
优先级,
IPR
宽度为
8bit
,原则上每个外部中断可配置的优先级为
0~255
,数值越小,优先
级越高。
这个分组的库函数是由内核外设SCB里面写的,不在NVIC库函数里面
四 中断编程
在配置每个中断的时候一般有
3
个编程要点:
1
、使能对应外设某个中断,这个具体由每个外设的相关中断使能位控制。比如串口有发送
完成中断,接收完成中断,这两个中断都由串口控制寄存器的相关中断使能位控制。相当于打开小门。
2
、初始化
NVIC_InitTypeDef
结构体,配置中断优先级分组,设置抢占优先级和子优
先级,使能中断请求。
NVIC_InitTypeDef
结构体在固件库头文件
misc.h
中定义。相当于打开大门。
代码
17-3 NVIC
初始化结构体
1 typedef
struct
{
2
uint8_t
NVIC_IRQChannel;
//
中断源
3
uint8_t
NVIC_IRQChannelPreemptionPriority;
//
抢占优先级
4
uint8_t
NVIC_IRQChannelSubPriority;
//
子优先级
5
FunctionalState NVIC_IRQChannelCmd;
//
中断使能或者失能
6
} NVIC_InitTypeDef;
有关
NVIC
初始化结构体的成员我们一一解释下:
1
)
NVIC_IROChannel
:用来设置中断源,不同的中断中断源不一样,且不可写错,即
使写错了程序也不会报错,只会导致不响应中断。具体的成员配置可参考
stm32f10x.h
头文
件里面的
IRQn_Type
结构体定义,这个结构体包含了所有的中断源。
2
)
NVIC_IRQChannelPreemptionPriority
:抢占优先级,具体的值要根据优先级分组来
确定,具体参考表格
17-5
优先级分组真值表 。
3
)
NVIC_IRQChannelSubPriority
:子优先级,具体的值要根据优先级分组来确定,具
体参考表格
17-5
优先级分组真值表 。
4
)
NVIC_IRQChannelCmd
:中断使能(
ENABLE
)或者失能(
DISABLE
)。操作的
是
NVIC_ISER
和
NVIC_ICER
这两个寄存器。
3
、编写中断服务函数
在启动文件
startup_stm32f10x_hd.s
中我们预先为每个中断都写了一个中断服务函数,
只是这些中断函数都是为空,为的只是初始化中断向量表。实际的中断服务函数都需要我
们重新编写,为了方便管理我们把中断服务函数统一写在
stm32f10x_it.c
这个库文件中。
关于中断服务函数的函数名必须跟启动文件里面预先设置的一样,如果写错,系统就
在中断向量表中找不到中断服务函数的入口,直接跳转到启动文件里面预先写好的空函数,
并且在里面无限循环,实现不了中断。
五 .EXTI 简介
EXTI
(
External interrupt/event controller
)—外部中断
/
事件控制器,管理了控制器的
20
个中断
/事件线。
每个中断
/
事件线都对应有一个边沿检测器,可以实现输入信号的上升沿
检测和下降沿的检测。
EXTI
可以实现对每个中断
/
事件线进行单独配置,可以单独配置为
中断或者事件,以及触发事件的属性。
EXTI
的功能框图包含了
EXTI
最核心内容,掌握了功能框图,对
EXTI
就有一个整体
的把握,在编程时思路就非常清晰。
可以看到很多在信号线上打一个斜杠并标注“
20
”字样,这个表示在控制器 内部类似的信号线路有 20
个,这与
EXTI
总共有
20
个中断
/事件线是吻合的。
EXTI
可分为两大部分功能,一个是产生中断,另一个是产生事件,这两个功能从硬件
上就有所不同。
首先我们来看图
18-1
中红色虚线指示的电路流程。它是一个产生中断的线路,最终信
号流入到
NVIC
控制器内。
编号
1
是输入线,
EXTI
控制器有 20
个中断
/
事件输入线,这些输入线可以通过寄存器
设置为任意一个
GPIO
,也可以是一些外设的事件,这部分内容我们将在后面专门讲解。
输入线一般是存在电平变化的信号。
编号
2
是一个边沿检测电路,它会根据上升沿触发选择寄存器
(EXTI_RTSR)
和下降沿
触发选择寄存器
(EXTI_FTSR)
对应位的设置来控制信号触发。边沿检测电路以输入线作为
信号输入端,如果检测到有边沿跳变就输出有效信号
1
给编号
3
电路,否则输出无效信号
0
。而
EXTI_RTSR
和
EXTI_FTSR
两个寄存器可以控制器需要检测哪些类型的电平跳变过
程,可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。
编号
3
电路实际就是一个或门电路,它一个输入来自编号
2
电路,另外一个输入来自
软件中断事件寄存器
(EXTI_SWIER)
。
EXTI_SWIER
允许我们通过程序控制就可以启动中
断
/
事件线,这在某些地方非常有用。我们知道或门的作用就是有
1
就为
1
,所以这两个输
入随便一个有有效信号
1
就可以输出
1
给编号
4
和编号
6
电路。
编号
4
电路是一个与门电路,它一个输入是编号
3
电路,另外一个输入来自中断屏蔽
寄存器
(EXTI_IMR)
。与门电路要求输入都为
1
才输出
1
,导致的结果是如果
EXTI_IMR
设
置为
0
时,那不管编号
3
电路的输出信号是
1
还是
0
,最终编号
4
电路输出的信号都为
0
;
如果
EXTI_IMR
设置为
1
时,最终编号
4
电路输出的信号才由编号
3
电路的输出信号决定,
这样我们可以简单的控制
EXTI_IMR
来实现是否产生中断的目的。编号 3
电路输出的信号
会被保存到挂起寄存器
(EXTI_PR)
内,如果确定编号 3
电路输出为
1
就会把
EXTI_PR
对应
位置
1
。
编号
5
是将
EXTI_PR
寄存器内容输出到
NVIC
内,从而实现系统中断事件控制。
接下来我们来看看绿色虚线指示的电路流程。它是一个产生事件的线路,最终输出一
个脉冲信号。
产生事件线路是在编号
3
电路之后与中断线路有所不同,之前电路都是共用的。
编号 6 电路是一个与门,它一个输入来自编号
3
电路,另外一个输入来自事件屏蔽寄存器
(EXTI_EMR)
。如果
EXTI_EMR
设置为
0
时,那不管编号
3
电路的输出信号是
1
还是
0
,
最终编号
6
电路输出的信号都为
0
;如果
EXTI_EMR
设置为
1
时,最终编号
6
电路输出的
信号才由编号
3
电路的输出信号决定,这样我们可以简单的控制
EXTI_EMR
来实现是否产
生事件的目的。
编号
7
是一个脉冲发生器电路,当它的输入端,即编号
6
电路的输出端,是一个有效
信号
1
时就会产生一个脉冲;如果输入端是无效信号就不会输出脉冲。
编号
8
是一个脉冲信号,就是产生事件的线路最终的产物,这个脉冲信号可以给其他
外设电路使用,比如定时器
TIM
、模拟数字转换器
ADC
等等,这样的脉冲信号一般用来
触发
TIM
或者
ADC
开始转换。
产生中断线路目的是把输入信号输入到
NVIC
,进一步会运行中断服务函数,实现功
能,这样是软件级的。而产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且
是电路级别的信号传输,属于硬件级的。
另外,
EXTI
是在
APB2
总线上的,在编程时候需要注意到这点。
中断
/
事件线
EXTI
有
20
个中断
/
事件线,每个
GPIO
都可以被设置为输入线,占用
EXTI0
至
EXTI15,还有另外七根用于特定的外设事件,
EXTI0
至
EXTI15
用于
GPIO
,通过编程控制可以实现任意一个
GPIO
作为
EXTI
的输
入源。由表
18-1
可知,
EXTI0
可以通过
AFIO
的外部中断配置寄存器
1(AFIO_EXTICR1)
的
EXTI0[3:0]
位选择配置为
PA0
、
PB0
、
PC0
、
PD0
、
PE0
、
PF0
、
PG0
、
PH0
或者
PI0,
EXTI
初始化结构体详解
标准库函数对每个外设都建立了一个初始化结构体,比如
EXTI_InitTypeDef
,结构体
成员用于设置外设工作参数,并由外设初始化配置函数,比如
EXTI_Init()
调用,这些设定
参数将会设置外设相应的寄存器,达到配置外设工作环境的目的。
初始化结构体和初始化库函数配合使用是标准库精髓所在,理解了初始化结构体每个
成员意义基本上就可以对该外设运用自如了。初始化结构体定义在
stm32f4xx_exti.h
文件中,
初始化库函数定义在
stm32f4xx_exti.c
文件中,编程时我们可以结合这两个文件内注释使用。
EXTI
初始化结构体
1 typedef
struct
{
2
uint32_t
EXTI_Line;
//
中断
/
事件线
3
EXTIMode_TypeDef EXTI_Mode;
// EXTI
模式
4
EXTITrigger_TypeDef EXTI_Trigger;
//
触发类型
5
FunctionalState EXTI_LineCmd;
// EXTI
使能
6
} EXTI_InitTypeDef;
1) EXTI_Line
:
EXTI
中断
/
事件线选择,可选
EXTI0
至
EXTI19
。
2) EXTI_Mode
:
EXTI
模式选择,可选为产生中断
(EXTI_Mode_Interrupt)
或者产生事
件
(EXTI_Mode_Event)
。
3) EXTI_Trigger
:
EXTI
边沿触发事件,可选上升沿触发
(EXTI_Trigger_Rising)
、下
降沿触发
( EXTI_Trigger_Falling)
或者上升沿和下降沿都触发
( EXTI_Trigger_Rising_Falling)
。
4) EXTI_LineCmd
:控制是否使能
EXTI
线,可选使能
EXTI
线
(ENABLE)
或禁用
(DISABLE)
六 外部中断控制实验
中断在嵌入式应用中占有非常重要的地位,几乎每个控制器都有中断功能。中断对保
证紧急事件得到第一时间处理是非常重要的。
我们设计使用外接的按键来作为触发源,使得控制器产生中断,并在中断服务函数中
实现控制
灯的任务。
软件设计
中断服务函数放在
stm32f10x_it.h
文件中。
编程要点
1)
初始化用来产生中断的
GPIO
;
2)
初始化
EXTI
;
3)
配置
NVIC
;
4)
编写中断服务函数;
static void EXTI_NVIC_Config(void){
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_1);//分组
NVIC_InitStruct.NVIC_IRQChannel = EXTI4_IRQn;//中断源
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;//主优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;//子优先级;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;//打开大门
NVIC_Init(&NVIC_InitStruct);
void EXTI_Key0_Config(void){//中断外设事件线初始化
EXTI_InitTypeDef EXTI_InitStructure;//变量定义放最前面
KEY0_GPIO_Config();//浮空输入:电平完全由外部决定
RCC_APB2PeriphClockCmd( RCC_APB2Periph_AFIO, ENABLE );//使用 GPIO 之前必须开启 GPIO 端口的时钟;用到 EXTI 必须开启 AFIO 时钟。EXIT外设的小时钟,APB2线上的是大时钟
GPIO_EXTILineConfig( GPIO_PortSourceGPIOE, GPIO_PinSource4);//功能框图输入线源配置:对应ARIO外设
EXTI_InitStructure.EXTI_Line = EXTI_Line4;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//选择中断而非事件
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //假设默认高电平,按键按下拉低。注意这里引脚跳接线自己接高电平一下
EXTI_InitStructure.EXTI_LineCmd = ENABLE;//打开小门
EXTI_Init(&EXTI_InitStructure);
void delay(uint32_t count){