中断介绍
- 轮询缺点:如果没有人操作按键,CPU轮询死等,不能干其他的事情,CPU利用率大大降低
- 解决方法:中断方式,也就是当CPU发现按键无操作,可以干其他的事情,如果一旦发现有按键操作,按键给CPU发送一个中断电信号,告诉CPU有人操作了按键,CPU停止当前的工作转去处理中断,中断处理完毕再返回到原先被打断的位置继续运行
中断和轮询对比
- 轮询:等待时间短,纳秒ns,微秒us,10毫秒ms以内
- 中断:等待时间长,随机场合
中断硬件连接
NVIC介绍
NVIC英文全称是Nested Vectored Interrupt Controller,中文意思就是嵌套向量中断控制器,它
属于M3内核的一个外设,实现中断优先级分组、中断优先级的配置、读中断请求标志、清除中断请求标志、使能中断、清除中断等
STM32F103处理器的NVIC支持60个外部中断:
NVIC中断优先级
优先级
优先级:抢夺CPU资源的能力,优先级越高,抢夺CPU资源能力越强,就能及早运行,优先级对应的数值越小,优先级越高
优先级分类
STM32F103芯片支持60个可屏蔽中断通道,每个中断通道都具备自己的中断优先级,中断优先级分:
- 抢占式优先级
- 响应优先级通常也把响应优先级称为“亚优先级”或“副优先级”或者”子优先级”
- 每个中断源都需要被指定这两种优先级
优先级执行顺序
两种优先级的执行顺序是:抢占式>响应式。
- 抢占式:即当中断来临时,可立马中断此时的操作而去执行抢占式优先级的任务。高抢占式优先级可以中断低抢占式优先级(抢占式优先级可以中断嵌套)。
- 响应式:即当两个相同抢占式优先级的中断而响应式优先级不同的的中断来临时,先执行高响应式优先级,再执行低响应式优先级的中断。
- 当两个中断源的抢占式优先级相同时,这两个中断将没有嵌套关系,当一个中断到来后,如果正在处理另一 个中断,这个后到来的中断就要等到前一个中断处理完之后才能被处理。 如果这两个中断同时到达,则中断控制器根据他们的响应优先级高低来决定先处理哪一个;
- 如果他们的抢占式优先级和响应优先级都相等,则根据他们在中断表中的排位顺序决定先处理那一个,越靠前的先执行
外部EXTI配置
- 使能IO口时钟,配置IO口模式为输入
- 开启 AFIO 时钟,设置 IO 口与中断线的映射关系
- 选择触发方式,使能中断
- 配置中断分组(NVIC),使能中断
中断步骤
中断处理
一旦CPU核接收到NVIC发送的中断信号,CPU核立马停止执行当前程序,CPU核立刻跳转到对应的外 设中断的处理函数里去运行,而外设对应的中断处理函数名有ST公司帮咱们定义好,咱直接编写好对应的中断处理函数即可,各个外设对应的中断处理函数名称位于startup_stm32f10x_hd.s 启动文件,将来一旦外设产生中断,CPU核立刻执行对应的函数:
- 中断处理函数框架:
void 中断处理函数名(void) {
1.根据用户需求完成相关硬件操作:例如:扫描按键,开关灯
2.最后清除中断挂起位、到来的标志位, 否则中断一直触发
}
中断处理流程
KEY0按键
- 使能GPIOE4口时钟,配置GPIOE4口模式为上拉输入
// 打开GPIOE控制器时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
// 配置GPIOE4为上拉输入
GPIO_InitTypeDef GPIO_Config;
GPIO_Config.GPIO_Pin = KEY0_PIN; // KEY0引脚
GPIO_Config.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_Init(KEY0_PORT, &GPIO_Config);
- 开启 AFIO 时钟,设置 GPIOE4 与EXTI4中断线的映射关系
// 打开AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// 将GPIOE4映射到EXTI4
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource4);
- 选择EXTI4中断线的触发方式,下降沿触发,使能EXTI4中断
//配置EXTI4控制器
EXTI_InitTypeDef EXTI_Config;
EXTI_Config.EXTI_Line = EXTI_Line4; // EXTI2,EXTI1,EXTI0中断线
EXTI_Config.EXTI_Mode = EXTI_Mode_Interrupt; // 中断模式
EXTI_Config.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发中断
EXTI_Config.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_Config);
- 配置EXTI4中断分组(NVIC),指定EXTI4中断的抢占优先级和子优先级,使能EXTI4中断
//EXTI4 NVIC配置
NVIC_InitTypeDef NVIC_Config;
NVIC_Config.NVIC_IRQChannel = EXTI4_IRQn ; // EXTI4中断通道
NVIC_Config.NVIC_IRQChannelPreemptionPriority = 0; // 主优先级
NVIC_Config.NVIC_IRQChannelSubPriority = 2; // 子优先级
NVIC_Config.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_Config);
- 中断处理函数,在这里设置key0可以控制LED0的开关转换
// EXTI4触发的中断处理函数
void EXTI4_IRQHandler(void){
// 判断是否是EXTI4触发的中断
if(EXTI_GetITStatus(EXTI_Line4) == SET){
delay_ms(10);//去抖动
if(KEY0 == 0)
LED0 = !LED0;
}
// 清除中断到来位
EXTI_ClearITPendingBit(EXTI_Line4);
}
- 主函数,这里只需要对中断进行初始化,再作出按键这个动作后,系统会自动调用中断处理函数,就去执行中断函数里的内容,即会去调用IRQHandler 。中断处理函数 IRQHandler 是嵌入式里非常特殊的一类函数,它们是嵌入式系统能够实时完成任务的关键所在
int main(void){
LED_Init(); // led灯的初始化
Systick_init(); // 滴答定时器的初始化
KEY_Init(); // 按键的初始化
My_EXTI_Init(); // EXTI初始化
while(1){
}
}
编译后烧入开发板,按key0可以转换LED0的状态
实验
要求KEY0按下转换LED0灯,KEY1按下转换LED1灯,KEY2按下转换蜂鸣器
KEY_UP按下同步转换LED0+LED1+蜂鸣器状态
key.c
// @file key.c
#include "key.h"
#include "systick.h"
void KEY_Init(void)
{
// 1.打开GPIOE控制器时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
// 1.1.打开GPIOA控制器时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2.配置GPIOE4为上拉输入
GPIO_InitTypeDef GPIO_Config;
GPIO_Config.GPIO_Pin = KEY0_PIN; // KEY0引脚
GPIO_Config.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_Init(KEY0_PORT, &GPIO_Config);
// 2.1.配置GPIOA0为下拉输入
GPIO_Config.GPIO_Pin = KEY_UP_PIN; // KEY_UP引脚
GPIO_Config.GPIO_Mode = GPIO_Mode_IPD; // 下拉输入
GPIO_Init(KEY_UP_PORT, &GPIO_Config);
// 2.2.配置GPIOE3为上拉输入
GPIO_Config.GPIO_Pin = KEY1_PIN; // KEY1引脚
GPIO_Config.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_Init(KEY1_PORT, &GPIO_Config);
// 2.3.配置GPIOE2为上拉输入
GPIO_Config.GPIO_Pin = KEY2_PIN; // KEY1引脚
GPIO_Config.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_Init(KEY1_PORT, &GPIO_Config);
}
exti.c
// @file exti.c
#include "exti.h"
#include "systick.h"
#include "led.h"
#include "key.h"
#include "beep.h"
/*
KEY0 - GPIOE4 - 上拉输入(默认高电平,按下按键低电平)
KEY1 - GPIOE3 - 上拉输入(默认高电平,按下按键低电平)
KEY2 - GPIOE2 - 上拉输入(默认高电平,按下按键低电平)
KEY_UP - GPIOA0 - 下拉拉输入(默认低电平,按下按键高电平)
KEY0, KEY1, KEY2检测 - 高电平->低电平 -> 下降沿
KEY_UP 检测 - 低电平->高电平 -> 上升沿
*/
void My_EXTI_Init(void)
{
// 1.配置GPIOE4,GPIOE3,GPIOE2为上拉输入,GPIOA0为下拉输入- key.h中完成
// 2.将GPIO映射到EXTI硬件中断线
// 2.1.打开AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// 2.2.将GPIOE映射到EXTI
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource4);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource3);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource2);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
// 3.配置EXTI控制器
// 3.1 配置EXTI2,EXTI1,EXTI0控制器,这三个按键中断模式都是一样的,在同一个配置文件中进行配置
EXTI_InitTypeDef EXTI_Config;
EXTI_Config.EXTI_Line = EXTI_Line2 | EXTI_Line3 | EXTI_Line4; // EXTI2,EXTI1,EXTI0中断线
EXTI_Config.EXTI_Mode = EXTI_Mode_Interrupt; // 中断模式
EXTI_Config.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发中断
EXTI_Config.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_Config);
// 3.2 配置EXTI0控制器
EXTI_Config.EXTI_Line = EXTI_Line0; // EXTI0中断线
EXTI_Config.EXTI_Mode = EXTI_Mode_Interrupt; // 中断模式
EXTI_Config.EXTI_Trigger = EXTI_Trigger_Rising; // 上升沿触发中断
EXTI_Config.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_Config);
// 4.配置NVIC控制器
// 4.1 EXTI4 NVIC配置
NVIC_InitTypeDef NVIC_Config;
NVIC_Config.NVIC_IRQChannel = EXTI4_IRQn ; // EXTI4中断通道
NVIC_Config.NVIC_IRQChannelPreemptionPriority = 0; // 主优先级
NVIC_Config.NVIC_IRQChannelSubPriority = 2; // 子优先级
NVIC_Config.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_Config);
// 4.2 EXTI3 NVIC配置
NVIC_Config.NVIC_IRQChannel = EXTI3_IRQn; // EXTI3中断通道
NVIC_Config.NVIC_IRQChannelPreemptionPriority = 0; // 主优先级
NVIC_Config.NVIC_IRQChannelSubPriority = 2; // 子优先级
NVIC_Config.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_Config);
// 4.3 EXTI2 NVIC配置
NVIC_Config.NVIC_IRQChannel = EXTI2_IRQn; // EXTI2中断通道
NVIC_Config.NVIC_IRQChannelPreemptionPriority = 0; // 主优先级
NVIC_Config.NVIC_IRQChannelSubPriority = 0; // 子优先级
NVIC_Config.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_Config);
// 4.4 EXTI0 NVIC配置
NVIC_Config.NVIC_IRQChannel = EXTI0_IRQn; // EXTI0中断通道
NVIC_Config.NVIC_IRQChannelPreemptionPriority = 0; // 主优先级
NVIC_Config.NVIC_IRQChannelSubPriority = 3; // 子优先级
NVIC_Config.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_Config);
}
// EXTI4触发的中断处理函数
void EXTI4_IRQHandler(void){
// 判断是否是EXTI4触发的中断
if(EXTI_GetITStatus(EXTI_Line4) == SET){
delay_ms(10);
if(KEY0 == 0)
LED0 = !LED0;
}
// 清除中断到来位
EXTI_ClearITPendingBit(EXTI_Line4);
}
void EXTI3_IRQHandler(void){
// 判断是否是EXTI3触发的中断
if(EXTI_GetITStatus(EXTI_Line3) == SET){
delay_ms(10);
if(KEY1 == 0)
LED1 = !LED1;
}
// 清除中断到来位
EXTI_ClearITPendingBit(EXTI_Line3);
}
void EXTI2_IRQHandler(void){
// 判断是否是EXTI2触发的中断
if(EXTI_GetITStatus(EXTI_Line2) == SET){
delay_ms(10);
if(KEY2 == 0)
BEEP = !BEEP;
}
// 清除中断到来位
EXTI_ClearITPendingBit(EXTI_Line2);
}
void EXTI0_IRQHandler(void){
// 判断是否是EXTI0触发的中断
if(EXTI_GetITStatus(EXTI_Line0) == SET){
if(KEY_UP == 1)
delay_ms(10);
LED1 =!LED1;
delay_ms(10);
LED0 =!LED0;
delay_ms(10);
BEEP = !BEEP;
}
// 清除中断到来位
EXTI_ClearITPendingBit(EXTI_Line0);
}
main.c
// @file : main.c
#include "stm32f10x.h"
#include "led.h"
#include "beep.h"
#include "system.h"
#include "systick.h"
#include "key.h"
#include "exti.h"
int main(void){
LED_Init(); // led灯的初始化
BEEP_Init(); // BEEP的初始化
Systick_init(); // 滴答定时器的初始化
KEY_Init(); // 按键的初始化
My_EXTI_Init(); // EXTI初始化
while(1){
}
}