一、实验目的
1. 深入理解GPIO外部中断处理机制;
2. 掌握STM32外部中断工作原理和配置方式;
3. 掌握中断固件库函数使用方法;
4. 掌握外部中断应用程序及中断服务程序的编写方法。
二、实验内容
使用库函数开发模版编写GPIO外部中断应用程序,能够通过按键K1控制LED灯的点亮和熄灭,通过按键K2控制蜂鸣器的鸣叫。
三、实验设备仪器及材料
硬件:STM32开发板, PC 机;
软件:MDK5集成开发环境,Windows 操作系统;
四、实验原理及接线
1. 中断相关概念
中断就是当CPU执行程序时,由于发生了某种随机的事件(内部或外部),引起CPU暂时中断正在运行的程序,转去执行一段特殊的服务程序(中断服务子程序或中断处理程序),以处理该事件,该事件处理完后又返回被中断的程序继续执行,这一过程就被称为中断,引发中断的称为中断源。
2. NVIC介绍
NVIC是嵌套向量中断控制器,控制着整个STM32芯片中断相关的功能。在配置中断时,一般使用ISER、ICER和IP这三个寄存器,ISER是中断使能寄存器,ICER是中断清除寄存器,IP是中断优先级寄存器。
固件库头文件core_cm3.h中,提供了NVIC的一些函数,这些函数遵循CMSIS规则,只要是Cortex-M3的处理器都可以使用,具体如下:
NVIC库函数 | 描述 |
void NVIC_EnableIRQ(IRQn_Type IRQn) | 使能中断 |
void NVIC_DisableRQ(IRQn_Type IRQn) | 失能中断 |
void NVIC_SetPendingIRQ(IRQn_Type IRQn) | 设置中断悬起位 |
void NVIC_ClearPendingIRQ(IRQn_Type IRQn) | 清楚中断悬起位 |
unit32_t NVIC_GetPendingIRQ(IRQn_Type IRQn) | 获取悬起中断编号 |
void NVIC_SetPriority(IRQn_Type IRQn,unit32_t priority) | 设置中断优先级 |
unit32_t NVIC_GetPriority(IRQn_Type IRQn) | 获取中断优先级 |
void NVIC_SystemReset(void) | 系统复位 |
3. 中断优先级
具有高抢占式优先级的中断可以在具有低抢占式欧县级的中断服务程序执行过程中被相应,即中断嵌套(高抢占式优先级的中断可以抢占低抢占式优先级的中断的执行)
在抢占式优先级相同的情况下,如果有低子优先级中断正在执行,高子优先级的中断要等待已被相应的低子优先级中断执行结束后才能得到相应,即子优先级不支持中断嵌套。
4. EXIT介绍
(1)STM32F4外部中断/时间控制器(EXIT)包含23个用于产生事件/中断请求的边沿检测器。EXTI可监测指定GPIO口的电平信号,当GPIO口的电平变化时,EXTI就立刻向NVIC发出中断申请,经过NVIC裁决后,让CPU执行中断程序
(2)触发方式:上升沿(低变高),下降沿(高变低),双边沿(前两个都可以)
(3)GPIO:支持所有GPIO口,但是相同的Pin不能触发中断
(4)通道数 :16个Pin(外加PVD输出,RTC闹钟,USB唤醒,以太网唤醒)
(5)触发响应方式:中断响应,事件响应
五、实验操作步骤
1. 编写led.c文件
#include "led.h"
void LED_Init()
{
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量,该语句的作用是将GPIO_InitTypeDef结构体命名为GPIO_InitStructure
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE); //使能端口F时钟,
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;//设置输出模式
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9|GPIO_Pin_10;//管脚设置为F9和F10
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//IO口速度为100M
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOF,&GPIO_InitStructure); //初始化结构体,初始化PF口
GPIO_SetBits(GPIOF,GPIO_Pin_9|GPIO_Pin_10);//输出高电平
}
2. 编写key.c文件
#include "key.h"
#include "SysTick.h"
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE|RCC_AHB1Periph_GPIOA,ENABLE); //使能端口PORTE、PORTA时钟
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN; //设置GPIO工作模式为输入
GPIO_InitStructure.GPIO_Pin=KEY_LEFT_Pin|KEY_DOWN_Pin|KEY_RIGHT_Pin;//设置管脚
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;//上拉
GPIO_Init(KEY_Port,&GPIO_InitStructure); //初始化结构体
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN; //设置GPIO工作模式为输入
GPIO_InitStructure.GPIO_Pin=KEY_UP_Pin;//设置管脚
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_DOWN;//下拉
GPIO_Init(KEY_UP_Port,&GPIO_InitStructure); //初始化结构体
}
KEY_Scan函数用于按键的扫描检测,mode为0时表示不支持连续按键,mode为1时可支持连续按键,按键K_UP是高电频有效,而K_DOWN、K_LEFT、K_RIGHT都是低电频有效,所以当K_UP为1时表示按下K_UP,而其它按键为0表示按下,由此可以完成按键的扫描检测,具体代码如下:
u8 KEY_Scan(u8 mode)
{
static u8 key=1;
if(key==1&&(K_UP==1||K_DOWN==0||K_LEFT==0||K_RIGHT==0)) //任意一个按键按下
{
delay_ms(10); //减少抖动
key=0;
if(K_UP==1)
{
return KEY_UP;
}
else if(K_DOWN==0)
{
return KEY_DOWN;
}
else if(K_LEFT==0)
{
return KEY_LEFT;
}
else
{
return KEY_RIGHT;
}
}
else if(K_UP==0&&K_DOWN==1&&K_LEFT==1&&K_RIGHT==1) //无按键按下
{
key=1;
}
if(mode==1) //连续按键按下
{
key=1;
}
return 0;
}
3. 编写keep.c文件
#include "beep.h"
void BEEP_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE); //使能端口F时钟
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT; //输出模式
GPIO_InitStructure.GPIO_Pin=BEEP_Pin;//管脚设置F8
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//速度为100M
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;//上拉
GPIO_Init(BEEP_Port,&GPIO_InitStructure); //初始化结构体
GPIO_SetBits(BEEP_Port,BEEP_Pin); //关闭蜂鸣器
}
4. 编写exit.c文件
#include "exti.h"
#include "led.h"
#include "SysTick.h"
#include "beep.h"
#include "key.h"
void My_EXTI_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;//定义结构体变量
EXTI_InitTypeDef EXTI_InitStructure;//定义外部中断的结构体变量
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA,EXTI_PinSource0);//PA0与EXTI0进行连接
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource2);//PE2管脚
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource3);//PE3管脚
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource4);//PE4管脚
//EXTI0 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;//设置通道:EXTIO中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//EXTI2 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;//EXTI2中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =2; //子优先级(相应优先级)
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//EXTI3 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;//EXTI3中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =1; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//EXTI4 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;//EXTI4中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =0; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
EXTI_InitStructure.EXTI_Line=EXTI_Line0;//中断线0
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//设置外部中断模式
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;//上升沿触发方式
EXTI_InitStructure.EXTI_LineCmd=ENABLE;//使能
EXTI_Init(&EXTI_InitStructure);//初始化外部中断结构体
EXTI_InitStructure.EXTI_Line=EXTI_Line2|EXTI_Line3|EXTI_Line4; //中断线2 3 4
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//设置外部中断模式
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;//下降沿触发模式
EXTI_InitStructure.EXTI_LineCmd=ENABLE;//使能
EXTI_Init(&EXTI_InitStructure);
}
//外部中断0函数
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0)==1)
{
delay_ms(10);
if(K_UP==1)
{
led2=0;
}
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
//外部中断3函数
void EXTI3_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line3)==1)
{
delay_ms(10);
if(K_DOWN==0)
{
beep = !beep;
}
}
EXTI_ClearITPendingBit(EXTI_Line3);
}
//外部中断2函数
void EXTI2_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line2)==1)
{
delay_ms(10);
if(K_RIGHT==0)
{
led2 = !led2;
}
}
EXTI_ClearITPendingBit(EXTI_Line2);
}
//外部中断4函数
void EXTI4_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line4)==1)
{
delay_ms(10);
if(K_LEFT==0)
{
beep=1;
}
}
EXTI_ClearITPendingBit(EXTI_Line4);
}
5. 编写main.c文件
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "beep.h"
#include "key.h"
#include "exti.h"
int main()
{
u8 key,i=0;
SysTick_Init(168);
LED_Init();
BEEP_Init();
KEY_Init();
while(1)
{
key=KEY_Scan(0); //扫描按键
switch(key)
{
//GPIO_ResetBits(GPIOF,GPIO_Pin_9);//复位F9,点亮D1
case KEY_UP:
while(1){
led1=!led1;
delay_ms(500);
led2=!led2;
delay_ms(500);}
break; //按下K_UP按键 点亮D1指示灯
case KEY_DOWN:
led2=1;
break; //按下K_DOWN按键,点亮D2指示灯
case KEY_LEFT: beep=1;break; //按下K_LEFT按键,关闭蜂鸣器
case KEY_RIGHT: beep=0;break; //按下K_RIGHT按键,打开蜂鸣器
}
i++;
if(i%20==0)
{
led1=!led1; //LED1状态取反
}
delay_ms(10);
}
}
六、实验结果
未按下任何键(K_DOWN和K_RIGHT):
按下K1(K_RIGHT)键:
可以看到led2常亮,再按一次K1键,led2熄灭,led1灯设置为一直闪烁状态,当按下K2(K_DOWN)键,可以听到蜂鸣器声响。
总结:
使用中断一般需要先使能外设某个中断,然后设置中断优先级,初始化NVIC_InitTypeDef结构体,并且设置抢占优先级和响应优先级,使能中断请求,然后再编写中断函数,当有中断发生的时候,会进入到中断函数中实现函数功能。而外部中断EXTI配置的一般步骤是先对IO口进行使能,将IO口设置为输入模式,然后开启时钟,设置IO口语中断线的映射关系,然后配置中断分组(NVIC),使能中断,接着再初始化EXTI,选择对应的触发方式,最后编写中断服务函数。