一、外部中断
在上一节我们介绍了STM32f103
的嵌套向量中断控制器,其中包括中断的使能、失能、中断优先级分组以及中断优先级配置等内容。
1.1 外部中断/事件控制器
在STM32f103
支持的60
个可屏蔽中断中,有一些比较特殊的中断:
-
中断编号
13 EXTI0
:EXTI
线0中断; -
中断编号
14 EXTI1
:EXTI
线1中断; -
中断编号
15 EXTI2
:EXTI
线2中断; -
中断编号
16 EXTI3
:EXTI
线3中断; -
中断编号
17 EXTI4
:EXTI
线4中断; -
中断编号
30 EXTI9_5
:EXTI
线[9:5]
中断; -
中断编号
47 EXTI15_10
:EXTI
线[15:10]
中断; -
中断编号
8 PVD
:连到EXTI
的电源电压检测(PVD
)中断; -
中断编号
48 RTCAlarm
:连到EXTI
线16
的RTC
闹钟时间; -
中断编号
49 USB
唤醒:连到EXTI
的线17
的USB
待机唤醒事件; -
对于
STM32F107
来说,还有一个连到EXTI
线18
的以太网唤醒事件;
对于STM32f103
来说,有19
个能产生事件/中断请求的边沿检测器。每个输入线可以独立地配置输入类型(脉冲或挂起)和对应的触发事件(上升沿或下降沿或者双边沿都触发)。每个输入线都可以独立地被屏蔽。挂起寄存器保持着状态线的中断请求。
1.1.1 EXTI
线与IO
映射关系4
EXTI
线0~15
:对应外部IO
口的输入中断。STM32F103
供给IO
口使用的中断线只有16
个,但是 STM32F103
的IO
口 却远远不止16
个,所以STM32
把GPIO
管脚GPIOx.0~GPIOx.15
(x=A,B,C,D,E,F,G
)分别对应中断线0~15
。
这样子每个中断线对应了最多9个IO
口,以线0
为例:它对应了GPIOA0
、GPIOB0
、 GPIOC0
、GPIOD0
、GPIOE0
、GPIOF0
和 GPIOG0
。而中断线每次只能连接到1个IO
口上, 这样就需要通过配置决定对应的中断线配置到哪个GPIO
上了。
GPIO
和中断线映射关系是在寄存器AFIO_EXTICR1
~ AFIO_EXTICR4
中配置的。
1.1.2 框图
其中按照控制功能划分,共分为4个部分,有两条主线:
- 一条是由输入线到
NVIC
中断控制器; - 一条是由输入线到脉冲发生器。
其中:
- 输入线:是线路的信息输入端,它可以通过配置寄存器设置为任何一个
GPIO
口,或者是一些外设的事件。输入线一般都是存在电平变化的信号; - 边沿检测电路(标号1):上升沿触发选择寄存器和下降沿触发选择寄存器。边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号1,就输出有效信号1到标号2部分电路,否则输入无效信号0。边沿跳变的标准在于对两个触发选择寄存器的设置;
- 或门电路(标号2):它的两个信号输入端分别是软件中断事件寄存器和边沿检测电路的输入信号。或门电路只要输入端有信号1,就会输出1,所以就会输出信号1到标号3电路和标号4电路;
- 与门电路(标号3):两个信号输入端分别是中断屏蔽寄存器和标号2电路信号;-
- 如果中断屏蔽寄存器设置为0时,不管从标号2电路输出的信号特性如何,最终标号3电路输出的信号都是0;
- 如果中断屏蔽寄存器设置为1时,最终标号3电路输出的信号才由标号2电路输出信号决定;
- 与门电路(标号4):输入端来自标号2电路以及来自于事件屏蔽寄存器。可以简单的控制事件屏蔽寄存器来实现是否产生事件的目的。标号4电路输出有效信号1就会使脉冲发生器电路产生一个脉冲,而无效信号就不会使其产生脉冲信号。脉冲信号产生可以给其他外设电路使用,例如定时器,模拟数字转换器等,这样的脉冲信号一般用来触发TIM或者ADC开始转换。
1.2 功能说明
1.2.1 硬件中断选择
要产生中断,必须先配置好并使能中断线;
- 根据需要的边沿检测设置2个触发寄存器(
EXTI_RTSR
和EXTI_FTSR
),同时在中断屏蔽寄存器(EXTI_IMR
)的相应位写1允许中断请求; - 配置对应到外部中断控制器(
EXTI
)的NVIC
中断通道的使能和屏蔽位,使得20
个中断线中的请求可以被正确地响应; - 当外部中断线上发生了期待的边沿时,将产生一个中断请求,对应的挂起位(
EXTI_PR
)也随之被置1; - 在挂起寄存器(
EXTI_PR
)的对应位写1,将清除该中断请求。
1.2.2 硬件事件选择
如果需要产生事件,必须先配置好并使能事件线;
-
根据需要的边沿检测通过设置2个触发寄存器(
EXTI_RTSR
和EXTI_FTSR
),同时在事件屏蔽寄存器(EXTI_EMR
)的相应位写1允许事件请求; -
当事件线上发生了需要的边沿时,将产生一个事件请求脉冲,对应的挂起位(
EXTI_PR
)不被置1。
1.2.3 软件中断/事件的选择
通过在软件中断/事件寄存器写1,也可以通过软件产生中断/事件请求;
- 配置
20
个中断/事件线屏蔽位(EXTI_IMR
,EXTI_EMR
); - 设置软件中断寄存器的请求位(
EXTI_SWIER
)。
二、EXTI
相关寄存器
在前面的介绍中,所涉及的寄存器共有7个:
- 上升沿触发选择寄存器(
EXTI_RTSR
); - 下降沿触发选择寄存器(
EXTI_FTSR
); - 挂起寄存器(
EXTI_PR
); - 软件中断事件寄存器(
EXTI_SWIER
); - 中断屏蔽寄存器(
EXTI_IMR
); - 事件屏蔽寄存器(
EXTI_EMR
); - 外部中断配置寄存器 (
AFIO_EXTICRx
)。
2.1 上升沿触发选择寄存器(EXTI_RTSR
)
该寄存器主要用于控制输入线进来的输入信号,上升沿时是否在边沿检测电路被检测出,20
位共控制20
条EXTI
线;
2.2 下降沿触发选择寄存器(EXTI_FTSR
)
该寄存器主要用于控制输入线进来的输入信号,下升沿时是否在边沿检测电路被检测出,20
位共控制20
条EXTI
线;
2.3 挂起寄存器(EXTI_PR
)
该寄存器的作用主要有两个:
- 检测外部中断线上是否发生了选择的边沿事件,如果发生了该位置1,并将信号传递给与门电路,进而进入
NVIC
中; - 在该位手动(软件)写入1,可以清除之前中断信号的1,主要作用是进入中断后,清除中断位,防止多次进入中断。
2.4 软件中断事件寄存器(EXTI_SWIER
)
2.5 中断屏蔽寄存器(EXTI_IMR
)
该寄存器的主要作用只有一个,就是是否允许来自输入线上的中断信号进入NVIC
中断控制器。
2.6 事件屏蔽寄存器(EXTI_EMR
)
该寄存器的主要作用只有一个,就是是否允许来自输入线线上的事件进入脉冲发生器。
2.7 外部中断配置寄存器 (AFIO_EXTICRx
)
AFIO_EXTICR1
寄存器配置EXTI0
到EXTI3
线,包含的外部中断的引脚包括PAx
到PGx
, x=0
到3
。
AFIO_EXTICR2
寄存器配置EXTI4
到EXTI7
线;
AFIO_EXTICR3
寄存器配置EXTI8
到EXTI11
线;
AFIO_EXTICR4
寄存器配置EXTI12
到EXTI15
线;
特别注意:配置AFIO
寄存器之前需要使能AFIO
的时钟。
2.7.1 AFIO_EXTICR1
2.7.2 AFIO_EXTICR2
2.7.3 AFIO_EXTICR3
2.7.4 AFIO_EXTICR4
三、外部中断源码
3.1 外部中断的初始化步骤
EXTI
中断配置流程如下:
(1) GPIO
口初始化;
- 外设时钟配置:通过配置
RCC_APB2ENR
寄存器使能GPIO
时钟; GPIO
配置:设置GPIO
工作模式为上拉/下拉输入模式、浮空输入模式,即配置GPIOx_CRL
、GPIOx_CRH
寄存器;
(2) EXTI
外部中断配置;
AFIO
时钟配置:通过配置RCC_APB2ENR
寄存器使能AFIO
时钟;- 设置
GPIO
与EXTI
映射关系:通过AFIO_EXTICRx
寄存器配置IO
对应的EXTI
输入线; - 设置
EXTI
屏蔽、上升沿、下降沿:即设置EXTI_EMR
、EXTI_RTSR
、EXTI_FTSR
寄存器;
(3) 设置NVIC
;
- 参考《
STM32F103
嵌套向量中断控制器》:设置中断优先级分组、设置响应优先级和抢断优先级、使能相应中断位;
(34) 中断处理函数;
- 设置中断服务函数(包括清除中断标志)。
3.2 源码实现
3.2.1 GPIO
口初始化
首先调用gpio_init
函数进行GPIO
初始化,具体参考《STM32F103 GPIO
和串口配置》。
比如配置GPIOC5
为上拉输入:
gpio_init(PC5,GPI_UP,HIGH); // PC5接按键KEY0
3.2.2 外部中断配置函数
typedef enum //外部中断触发方式
{
FALLING = 0x01,
RISING = 0x02,
LEVEL = 0x03
}TRIGGER_MODE;
/********************************************************************************************************
*
* Description:外部中断配置函数
* 决定中断线配置到哪个GPIO口
* AFIO->EXTICR[0~3] 每个寄存器的低16位每4位决定1路中断线
* 0000:PAx 0001:PBx 0010:PCx
0011:PDx 0100:PEx 0101:PFx
* Parameter:portx_pinx形如PA0,PA1................
TRIGGER_Mode: 0x01 下降沿触发
0x02 上升沿触发
0x03 任意电平触发
* Example : Ex_NVIC_Congig(PA8,0x01); PA8下降沿触发
*********************************************************************************************************/
void Ex_NVIC_Congig(PORTx_PINx portx_pinx,TRIGGER_MODE trigger_mode)
{
u8 address; //外部中断触发寄存器编号选择
u8 offset; //外部中断触发寄存器偏移位选择
u8 portx=portx_pinx/16; //端口号
u8 pinx=portx_pinx%16; //引脚号
address = pinx/4;
offset = (pinx%4)*4;
RCC->APB2ENR |= 1<<0; //使能I/O复用时钟
AFIO->EXTICR[address]|=portx<<offset; //EXTI.BITx映射到GPIOx.BITx
// EXTI->PR |= 1<<pinx; //清除线x上的中断标志位
EXTI->IMR |= 1<<pinx; //开放来自线x上的中断请求
EXTI->EMR |= 1<<pinx; //开放来自线x上的事件请求
if(trigger_mode==0x01)
EXTI->FTSR |= 1<<pinx; //设置线x上下降沿触发
if(trigger_mode==0x02)
EXTI->RTSR |= 1<<pinx; //设置线x上上降沿触发
}
比如配置GPIOC5
为下降沿触发:
Ex_NVIC_Congig(PC5,FALLING); // 按键KEY0按下触发 高电平->低电平
3.2.3 NVIC
中断优先级初始化
调用STM32_NVIC_Init
函数初始化PC5
中断优先级,具体参考《STM32F103
嵌套向量中断控制器》;
GPIOC5
对应的EXTI
线[9:5]
,这里设置EXTI9_5_IRQn
中断优先级分组2,抢占优先级为2,响应优先级为2;
STM32_NVIC_Init(2,EXTI9_5_IRQn,2,2);
3.2.4 中断处理函数
这里依然以EXTI9_5_IRQn
中断为例,对应的中断处理函数为EXTI9_5_IRQHandler
;
/*********************************************************************************************************************
* Function Name : EXTI9_5_IRQHandler
* Description : This function handles External lines 9 to 5 interrupt request.
* Input : None
* Output : None
* Return : None
*************************************************************************************************************************/
void EXTI9_5_IRQHandler(void)
{
if( EXTI->PR & 1<<5 ) //来自中断线5上的中断
{
//**********************自定义用户任务****************************//
//*****************************************************************//
EXTI->PR = 1<<5; //清中断线5上的中断标志
}
if( EXTI->PR & 1<<6 ) //来自中断线6上的中断
{
//**********************自定义用户任务****************************//
//*****************************************************************//
EXTI->PR = 1<<6; //清中断线6上的中断标志
}
if( EXTI->PR & 1<<7 ) //来自中断线7上的中断
{
//**********************自定义用户任务****************************//
//*****************************************************************//
EXTI->PR = 1<<7; //清中断线7上的中断标志
}
if( EXTI->PR & 1<<8 ) //来自中断线8上的中断
{
//**********************自定义用户任务****************************//
usart_sendStr(USART_1, "坏人");
//*****************************************************************//
EXTI->PR = 1<<8; //清中断线8上的中断标志
}
if( EXTI->PR & 1<<9 ) //来自中断线9上的中断
{
//**********************自定义用户任务****************************//
//*****************************************************************//
EXTI->PR = 1<<9; //清中断线9上的中断标志
}
}
3.3 实现功能
这里我们就利用手头开发板的独立按键kKEY0
来控制LED1
灯的亮灭;
其中:
KEY0
连接STM32f103
的GPIOC5
,按键无上拉电阻,需要在GPIOC5
上设为上拉状态,提供一个确定电平;- 按键按下时,
GPIOC5
输出低电平; - 当按键未按下时,
GPIOC5
应为高电平;
- 按键按下时,
LED1
连接STM32f103
的GPIOA8
引脚,低电平点亮;
我们要实现的功能也很简单:即按下KEY0
点亮LED1
,源码实现包括以下两个步骤;
- 配置
GPIOA8
通用推挽输出,最大速度50MHz
; - 配置
GPIOC
对应的EXTI
线[9:5]
外部中断,下降沿触发;
3.3.1 main
函数实现
int main()
{
u32 i=0;
STM32_Clock_Init(9); //系统时钟初始化
while(RTC_Init()); //RTC初始化
STM32_NVIC_Init(2,USART1_IRQn,0,1); //串口中断优先级初始化,其中包括中断使能
usart_init(USART_1,115200); //串口1初始化,波特率115200 映射到PA9 PA10
// LED1初始化
gpio_init(PA8,GPO_SpeedMax_50,HIGH); //PA8接入LED1
// 按键KEY0初始化
gpio_init(PC5,GPI_UP,HIGH); //PC5接按键KEY0
Ex_NVIC_Congig(PC5,FALLING); //按键KEY0按下触发 高电平->低电平
STM32_NVIC_Init(2,EXTI9_5_IRQn,2,2); //EXTI线[9:5]中断优先级初始化,其中包括中断使能
while(1)
{
delay_ms(100);
PAout(8) = 1; // 熄灭LED1
}
}
3.3.2 EXTI9_5_IRQHandler
函数实现
/*********************************************************************************************************************
* Function Name : EXTI9_5_IRQHandler
* Description : This function handles External lines 9 to 5 interrupt request.
* Input : None
* Output : None
* Return : None
*************************************************************************************************************************/
void EXTI9_5_IRQHandler(void)
{
if( EXTI->PR & 1<<5 ) //来自中断线5上的中断
{
//**********************自定义用户任务****************************//
PAout(8) = 0; // 点亮LED1
//*****************************************************************//
EXTI->PR = 1<<5; //清中断线5上的中断标志
}
if( EXTI->PR & 1<<6 ) //来自中断线6上的中断
{
//**********************自定义用户任务****************************//
//*****************************************************************//
EXTI->PR = 1<<6; //清中断线6上的中断标志
}
if( EXTI->PR & 1<<7 ) //来自中断线7上的中断
{
//**********************自定义用户任务****************************//
//*****************************************************************//
EXTI->PR = 1<<7; //清中断线7上的中断标志
}
if( EXTI->PR & 1<<8 ) //来自中断线8上的中断
{
//**********************自定义用户任务****************************//
//*****************************************************************//
EXTI->PR = 1<<8; //清中断线8上的中断标志
}
if( EXTI->PR & 1<<9 ) //来自中断线9上的中断
{
//**********************自定义用户任务****************************//
//*****************************************************************//
EXTI->PR = 1<<9; //清中断线9上的中断标志
}
}
四、源码下载
源码下载路径:stm32f103
。
参考文章
[2] STM32
--中断使用