STM32学习-Day5-中断
目录
1、程序运行方式
(1)轮询系统
(2)前后台系统
(3)多任务系统
2、外部中断概述
(1)中断概念
(2)中断源
(3)NVIC概述
(4)中断的优先级设置
3、EXTI外部中断
4、外部中断映射
5、外部中断程序设计
(1)添加外设源文件
(2)阅读头文件注释或官方参考例程
(3)打开外设时钟
(4)配置外部中断
(5)配置NVIC
(6)中断服务函数
1、程序运行方式
(1)轮询系统
指的是在程序运行时,首先对所有的硬件进行初始化,然后在主程序中写一个死循环,需要运行的功能按照顺序进行执行,轮询系统是一种简单可靠的方式,一般适用于在只需要按照顺序执行的并且没有外部事件的影响的情况下。
程序的运行过程中出现如按键等需要外部检测的事件,轮询系统的实时响应能力变得很差。
int main(void)
{
//1.硬件初始化(LED、KEY、BEEP......)
//2.定义一个死循环
while(1)
{
//功能1 LED
//功能2 KEY
//....
}
}
(2)前后台系统
相比于轮询系统,前后台系统增加中断的概念,如果外部事件发生,则在中断中进行处理,主程序在轮询系统中运行,中断被称为前台,主程序中的while(1)就称为后台。中断会终止后台程序的运行,然后跳转到对应的中断服务函数中去处理,处理完成后,在继续执行后台的程序。
如果使用前后台系统,可以极大程度的提高程序的实时响应能力,避免造成外部事件的缺失。
int main(void)
{
//1.硬件初始化
//2.注册中断,配置中断的优先级
//3.设计后台程序 while(1)
while(1)
{
//执行功能1
//执行功能2
//.....
}
}
//设计中断服务函数
void xxx_irq(void)
{
//执行中断程序
}
(3)多任务系统
相比于前后台系统,多任务系统的外部事件也是在中断中进行响应,但是外部事件的处理是任务中进行处理。任务具有优先级,优先级高的任务先处理,所以程序就会被分割为一个个的任务,任务是一个独立的死循环,并且不能返回,可以由操作系统进行任务的调度,程序段的实时响应能力又得到提升。
void task1(void)
{
while(1)
{
//执行功能
}
}
void task2(void)
{
while(1)
{
//执行功能
}
}
int main(void)
{
//1.硬件的初始化(LED、BEEP、KEY......)
//2.创建任务,并为任务分配资源(CPU的使用权、内存空间、任务的优先级)
//3.交给操作系统(RTOS)进行调度,直接进入死循环
}
2、外部中断概述
(1)中断概念
中断指的是CPU来处理和响应外部发生的异常,中断也就意味着打断,比如打断正在做的事,然后去处理一个紧急的事,处理完成后在继续做刚才没做完的事。比如打游戏,女朋友来电话。
(2)中断源
中断源指的是中断发生的源头,中断源在内核中已经定义好了,中断源也称为向量表,向量表在STM32F1中文参考手册参考。
(3)NVIC概述
NVIC指的是嵌套向量中断控制器,属于内核中的外设,作用是管理所有的中断,比如中断的使能或失能、中断的优先级.....。不管是Cortex A系列还是Cortex M系列的内核内部均有NVIC,通过NVIC来管理内核异常和外部异常。NVIC管理中断通道的打开与关闭,可以把NVIC理解为所有中断的开关,想要使用中断发送中断请求,就必须提前打开中断的通道。关于NVIC的使用都存储在一个结构体中,这个结构体和NVIC的函数接口都定义在misc.c和misc.h中。
(4)中断的优先级设置
NVIC利用4bit的优先级来管理所有的中断通道,STM32中断的优先级分为两种:抢占式优先级(主优先级) + 响应式优先级(子优先级),每种都有16个优先级(0~15),数字越小,优先级越高。
意义:如果同时发生多个中断请求,但是又不能同时处理,就根据中断请求的优先级来处理和响应中断。
抢占优先级(主优先级):抢占优先级高的中断可以打断正在执行的抢占优先级低的中断!!!
响应优先级(子优先级):在同时发生多个中断的情况下,响应优先级高的中断可先执行!!!
- 抢占优先级高的中断可以打断正在执行的抢占优先级低的中断
- 抢占优先级相同的中断同时发生,响应优先级高的中断先执行
- 抢占优先级相同的多个中断发生,响应优先级高的中断不能打算响应优先级低的中断
- 抢占优先级和响应优先级相同的多个中断同时发生,则按照向量表的中断编号来执行
为了方便用户管理和响应中断,NVIC可以对中断优先级进行分组,这样用户可以方便配置.
注意:该函数必须在主程序的入口进行调用,并且整个项目应该只调用一次,因为设置好优先级分组之后就不应该随意更改分组,否则中断管理比较混乱。
3、EXTI外部中断
EXTI指的是外部中断/事件控制器,一共有19个,每个都有一个内部的边沿检测器,可以检测上升沿或者下降沿,每根线都可以产生事件或者中断。
注意:每个GPIO引脚都可以配置为外部中断,但是和GPIO相关的外部中断线一共有16根,分别为EXTI0~EXTI15。
4、外部中断映射
注意:每个GPIO引脚都可以配置为外部中断,但是和GPIO相关的外部中断线一共有16根,分别为EXTI0~EXTI15。
思考:STM32F103系列有48个GPIO口,那如何和外部中断线进行关联?通过映射的方式
注意:STM32F103所有IO口都可以设置为外部中断,但是必须把GPIO引脚配置为输入模式。
5、外部中断程序设计
(1)添加外设源文件
(2)阅读头文件注释或官方参考例程
(3)打开外设时钟
打开GPIOA端口时钟(因为KEY0按键对应的引脚PA0)。配置GPIOA端口PA0引脚的模式为输入模式,因为需要利用该引脚检测外部事件。
void Key_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
(4)配置外部中断
void Key_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
EXTI_InitTypeDef key_InitStructure;
key_InitStructure.EXTI_Line = EXTI_Line0;
key_InitStructure.EXTI_LineCmd = ENABLE;
key_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
key_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
}
(5)配置NVIC
void Key_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Init(&EXTI_InitStructure);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = 0;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
(6)中断服务函数
对EXTI中断通道配置完成后,需要编写对应的中断服务函数,中断服务函数的格式固定。
void EXTI0_IRQHandler(void)
{
//1.判断外部中断线是否触发中断
if(EXTI_GetITStatus(EXTI_Line0) != RESET)
{
//清楚EXTI中断线的状态
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
注意:中断服务函数的名字是已经提前定义在启动文件中,启动文件是在程序运行之前先运行的一个汇编文件(xxx.s结尾的),用户在选择使用某个外设的中断的时候,必须从启动文件中复制中断服务函数的名称,比如EXTI0外部中断线的中断服务函数名字是EXTI0_IRQHandler。如果用户自定义中断服务函数的名称,编译程序是不会报错的,但是如果此时满足了中断的触发条件,此时程序会进入死循环,原因是因为汇编文件的中断服务函数默认是进入死循环的。