00. 目录

 

 

01. 外部中断简介

外部中断的代码主要分布在固件库的 stm32f4xx_exti.h 和 stm32f4xx_exti.c 文件中。

STM32F4 的每个 IO 都可以作为外部中断的中断输入口,这点也是 STM32F4 的强大之处。STM32F407 的中断控制器支持 22
个外部中断/事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。

STM32F407 的 22 个外部中断为:

  • EXTI 线 0~15:对应外部 IO 口的输入中断。
  • EXTI 线 16:连接到 PVD 输出。
  • EXTI 线 17:连接到 RTC 闹钟事件。
  • EXTI 线 18:连接到 USB OTG FS 唤醒事件。
  • EXTI 线 19:连接到以太网唤醒事件。
  • EXTI 线 20:连接到 USB OTG HS(在 FS 中配置)唤醒事件。
  • EXTI 线 21:连接到 RTC 入侵和时间戳事件。
  • EXTI 线 22:连接到 RTC 唤醒事件。

从上面可以看出,STM32F4 供 IO 口使用的中断线只有 16 个,但是 STM32F4 的 IO 口却远远不止 16 个,那么 STM32F4 是怎么把 16 个中断线和 IO 口一一对应起来的呢?于是 STM32就这样设计,GPIO 的管教 GPIOx.0~GPIOx.15(x=A,B,C,D,E,F,G,H,I)分别对应中断线 0~15。这样每个中断线对应了最多 9 个 IO 口,以线 0 为例:它对应了 GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0,GPIOH.0,GPIOI.0。而中断线每次只能连接到 1 个 IO口上,这样就需要通过配置来决定对应的中断线配置到哪个 GPIO 上了。下面我们看看 GPIO跟中断线的映射关系图:

【STM32】外部中断_stm32

02. 外部中断配置

2.1 使能 IO口时钟,初始化 IO

我们要使用 IO 口作为中断输入,所以我们要使能相应的 IO 口时钟,以及初始化相应的 IO 口为输入模式

2.2 开启 SYSCFG 时钟,设置 IO 口与中断线的映射关系

只要我们使用到外部中断,就必须打开 SYSCFG 时钟。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);//使能 SYSCFG 时钟

配置 GPIO 与中断线的映射关系。在库函数中,配置 GPIO 与中断线的映射关系的函数 SYSCFG_EXTILineConfig ()来实现的:

void SYSCFG_EXTILineConfig(uint8_t EXTI_PortSourceGPIOx, uint8_t EXTI_PinSourcex);

该函数将 GPIO 端口与中断线映射起来,程序示例:

SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);

2.3 初始化线上中断,设置触发条件

中断线上中断的初始化是通过函数 EXTI_Init()实现的。EXTI_Init()函数的定义是:

void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);

程序示例

EXTI_InitTypeDef EXTI_InitStructure;
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); //初始化外设 EXTI 寄存器

2.4 配置中断分组(NVIC ),并使能中断

我们设置好中断线和 GPIO 映射关系,然后又设置好了中断的触发模式等初始化参数。既然是外部中断,涉及到中断我们当然还要设置 NVIC 中断优先级。

NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; //使能按键外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;  //响应优先级 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //中断优先级分组初始化

2.5 编写中断服务函数

我们配置完中断优先级之后,接着要做的就是编写中断服务函数。中断服务函数的名字是在 MDK 中事先有定义的。这里需要说明一下,STM32F4 的 IO 口外部中断函数只有 7 个,分别为:

DCD     EXTI0_IRQHandler                  ; EXTI Line0                                             
DCD     EXTI1_IRQHandler                  ; EXTI Line1                                             
DCD     EXTI2_IRQHandler                  ; EXTI Line2                                             
DCD     EXTI3_IRQHandler                  ; EXTI Line3                                             
DCD     EXTI4_IRQHandler                  ; EXTI Line4   
DCD     EXTI9_5_IRQHandler                ; External Line[9:5]s   
DCD     EXTI15_10_IRQHandler              ; External Line[15:10]s  

中断线 0-4 每个中断线对应一个中断函数,中断线 5-9 共用中断函数 EXTI9_5_IRQHandler,中断线 10-15 共用中断函数EXTI15_10_IRQHandler。在编写中断服务函数的时候会经常使用到两个函数,第一个函数是判断某个中断线上的中断是否发生(标志位是否置位)

ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);

这个函数一般使用在中断服务函数的开头判断中断是否发生。另一个函数是清除某个中断线上的中断标志位:

void EXTI_ClearITPendingBit(uint32_t EXTI_Line);

这个函数一般应用在中断服务函数结束之前,清除中断标志位。

03. 外部中断步骤

使用 IO 口外部中断的一般步骤:
1)使能 IO 口时钟,初始化 IO 口为输入。
2)使能 SYSCFG 时钟,设置 IO 口与中断线的映射关系。
3)初始化线上中断,设置触发条件等。
4)配置中断分组(NVIC),并使能中断。
5)编写中断服务函数。

04. 硬件设计

探索者 STM32F4 开发板上载有的 4 个按钮(KEY_UP、KEY0、KEY1 和 KEY2),来控制板上的 2 个 LED(DS0 和 DS1)和蜂鸣器,其中 KEY_UP 控制蜂鸣器,按一次叫,再按一次停;KEY0 控制 DS0,按一次亮,再按一次灭;KEY1 控制 DS1,
效果同 KEY2;KEY2 则同时控制 DS0 和 DS1,按一次,它们的状态就翻转一次。
【STM32】外部中断_stm32f4_02

05. 程序示例一

KEY0控制LED1的翻转

exti.h文件

#ifndef __EXTI_H__
#define __EXTI_H__

#include "sys.h"

//中断初始化函数
 void EXTIX_Init(void);

#endif /*__EXTI_H__*/

exti.c文件

#include "exti.h"
#include "key.h"
#include "led.h"
#include "delay.h"
#include "usart.h"

//中断初始化函数
 void EXTIX_Init(void)
 {
	 EXTI_InitTypeDef EXTI_InitStruct;
	 
	 NVIC_InitTypeDef NVIC_InitStruct;
	 
	 //按键初始化
	 KEY_Init();
	 
	 //初始化SYSCFG时钟
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
	 
	 //KEY0 --> PE4
	 SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource4);
	 
	 //配置中断
	 EXTI_InitStruct.EXTI_Line = EXTI_Line4;
	 EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
	 EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
	 EXTI_InitStruct.EXTI_LineCmd = ENABLE;
	 EXTI_Init(&EXTI_InitStruct);
	 
	 //配置中断分组
	 NVIC_InitStruct.NVIC_IRQChannel = EXTI4_IRQn;
	 NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
	 NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
	 NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

	 NVIC_Init(&NVIC_InitStruct);
 }

//中断线4处理函数
void EXTI4_IRQHandler(void)
{
	printf("KEY0按下啦....\r\n");
	//消抖
	delay_ms(10);
	
	if (0 == KEY0)
	{
		LED1 = !LED1;
	}

	//清中断
	EXTI_ClearITPendingBit(EXTI_Line4);
}

main.c文件

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "beep.h"
#include "key.h"
#include "exti.h"

int main(void)
{ 
	
	BEEP = 0;
	LED1 = 0;
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	BEEP_Init();
	LED_Init();
	
	delay_init(168);
	uart_init(115200);


	//外部中断初始化
	EXTIX_Init();
	
	while(1)
	{
		printf("程序正在运行.....\r\n");
		
		delay_ms(1000);
	}	
}

06. 程序示例二

exti.h文件

#ifndef __EXTI_H__
#define __EXTI_H__

#include "sys.h"

//中断初始化函数
 void EXTIX_Init(void);

#endif /*__EXTI_H__*/

exti.c文件

#include "exti.h"
#include "key.h"
#include "led.h"
#include "beep.h"
#include "delay.h"
#include "usart.h"

//中断初始化函数
 void EXTIX_Init(void)
 {
	 EXTI_InitTypeDef EXTI_InitStruct;
	 
	 NVIC_InitTypeDef NVIC_InitStruct;
	 
	 //按键初始化
	 KEY_Init();
	 
	 //初始化SYSCFG时钟
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
	 
	 //KEY0 --> PE4
	 SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource4);
	 
	 //KEY1 --> PE3
	 SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource3);
	 
	 //KEY1 --> PE2
	 SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource2);
	 
	 //KEY_UP --> PA0
	 SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
	 
	 //配置中断 KEY0 KEY1 KEY2
	 EXTI_InitStruct.EXTI_Line = EXTI_Line4 | EXTI_Line3 | EXTI_Line2;
	 EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
	 EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
	 EXTI_InitStruct.EXTI_LineCmd = ENABLE;
	 EXTI_Init(&EXTI_InitStruct);
	 
 
	 //配置中断 KEY_UP
	 EXTI_InitStruct.EXTI_Line = EXTI_Line0;
	 EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
	 EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
	 EXTI_InitStruct.EXTI_LineCmd = ENABLE;
	 EXTI_Init(&EXTI_InitStruct);
	 
	 
	 //配置中断分组 KEY0
	 NVIC_InitStruct.NVIC_IRQChannel = EXTI4_IRQn;
	 NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
	 NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
	 NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

	 NVIC_Init(&NVIC_InitStruct);
	 
	 //配置中断分组 KEY1
	 NVIC_InitStruct.NVIC_IRQChannel = EXTI3_IRQn;
	 NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
	 NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
	 NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

	 NVIC_Init(&NVIC_InitStruct);
	 
	 //配置中断分组 KEY2
	 NVIC_InitStruct.NVIC_IRQChannel = EXTI2_IRQn;
	 NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
	 NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
	 NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

	 NVIC_Init(&NVIC_InitStruct);
	 
	 //配置中断分组 WK_UP
	 NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;
	 NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
	 NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
	 NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

	 NVIC_Init(&NVIC_InitStruct);
 }

//中断线4处理函数
void EXTI4_IRQHandler(void)
{
	printf("KEY0按下啦....\r\n");
	//消抖
	delay_ms(10);
	
	if (0 == KEY0)
	{
		LED1 = !LED1;
	}

	//清中断
	EXTI_ClearITPendingBit(EXTI_Line4);
}


//中断线3处理函数
void EXTI3_IRQHandler(void)
{
	printf("KEY1按下啦....\r\n");
	//消抖
	delay_ms(10);
	
	if (0 == KEY1)
	{
		LED2 = !LED2;
	}

	//清中断
	EXTI_ClearITPendingBit(EXTI_Line3);
}


//中断线2处理函数
void EXTI2_IRQHandler(void)
{
	printf("KEY2按下啦....\r\n");
	//消抖
	delay_ms(10);
	
	if (0 == KEY2)
	{
		LED1 = !LED1;
		LED2 = !LED2;
	}

	//清中断
	EXTI_ClearITPendingBit(EXTI_Line2);
}


//中断线0处理函数
void EXTI0_IRQHandler(void)
{
	printf("KEY_UP按下啦....\r\n");
	//消抖
	delay_ms(10);
	
	if (1 == WK_UP)
	{
		BEEP = !BEEP;
	}

	//清中断
	EXTI_ClearITPendingBit(EXTI_Line0);
}

main.c文件

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "beep.h"
#include "key.h"
#include "exti.h"

int main(void)
{ 
	
	BEEP = 0;
	LED1 = 0;
	LED2 = 0;
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	BEEP_Init();
	LED_Init();
	
	delay_init(168);
	uart_init(115200);


	//外部中断初始化
	EXTIX_Init();
	
	while(1)
	{
		printf("程序正在运行.....\r\n");
		
		delay_ms(1000);
	}	
}

07. 附录

7.1 【STM32】STM32系列教程汇总

网址:【STM32】STM32系列教程汇总

08. 声明

该教程参考了正点原子的《STM32 F4 开发指南》