嵌入式—中断
在嵌入式系统中,中断是一种核心机制,中断是指在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行。接下来我会简要介绍中断系统,中断优先级以及中断嵌套。NVIC:全称嵌套中断向量控制器,是一个内核外设,是CPU的小助手作用:统一分配中断优先级以及管理中断图2 NVIC基本结构。
本文主要讲解中断、EXTI外部中断、NVIC、以及相应代码介绍。
一、简介
在嵌入式系统中,中断是一种核心机制,中断是指在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行。接下来我会简要介绍中断系统,中断优先级以及中断嵌套。
1.中断系统
中断系统是管理和执行中断的逻辑结构。
2.中断优先级
中断优先级是指当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源(根据程序设计的需求,自己设置)。
3.中断嵌套
中断嵌套是指当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回。
4.中断执行流程

图1 中断执行流程
图一前部分在断点位置发生中断,图1后半部分有多个断点属于嵌套中断。
5.中断其余知识
中断函数不需要调用,由硬件自动调用这个函数。
STM中断为68个可屏蔽中断通道,包含EXTI外部中断、TIM定时器、ADC模数转换器、USART串口、SPI通信、I2C通信、RTC实时时钟等多个外设。
二、NVIC
1.NVIC定义及结构
NVIC:全称嵌套中断向量控制器,是一个内核外设,是CPU的小助手
作用:统一分配中断优先级以及管理中断

图2 NVIC基本结构
在中断其余知识中我们可以了解到,STM32的中断非常多,如果将这些中断全部接到CPU上,CPU还得引出很多线进行适配,设计很麻烦,并且如果很多中断同时申请,中断很多产生拥挤,CPU会很难处理,以及中断分配的任务就交给了NVIC。
图二中的红色圈出位置是指,一个外设可能会同时占用多个中断通道,所有这里有n条线。
NVIC具有多个输入口,一个输出口,NVIC根据中断优先级,告知CPU先处理哪个中断。
2.NVIC优先级分组
使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级。
NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级。(值越小,优先级越高)
抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队。

三、EXTI
1.EXTI基本概念
EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
支持的触发方式:上升沿/下降沿/双边沿/软件触发
支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断
通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒
触发响应方式:中断响应/事件响应
总结:中断响应是正常的流程,引脚电平变化触发中断,事件响应不会触发中断,而是触发别的外设操作,属于外设之间的联合工作

图3 EXTI基本结构
每个GPIO有16个引脚(也就是PIN),所以进来了16根线,但是前面了解到EXTI模块只有16个GPIO的通道,如果每个引脚都占用一个通道,那么EXTI的16个通道显然不够用。所以我们引入了AFIO中断引脚选择的电源模块。
2.AFIO
AFIO就是一个数据选择器,将GPIO外设的16个引脚选择其中一个连接到后面的EXTI的通道里。所以说相同的Pin不能同时触发中断,因为类似与PA0,PB0,PC0经过AFIO选择之后只有其中一个能接到EXTI的通道0上。
3.举例介绍
对EXTI有了基本的概念了解后,我们来举例子来具体讲解执行过程,现在假设我们将对外式红外传感器连接到GPIOB的PIN14引脚上,我们需要在检测成功时显示检测成功数(从0依次加1),首先EXTI检测GPIOB的PIN14引脚的电平变换,当检测成功时,AFIO中断引脚选择模块将PIN14传到EXTI模块,然后EXTI模块通过EXTI10_15,将其传递给NVIC,然后NVIC根据优先级,传递给CPU。
注意:这里我们可以看到本来EXTI有20个通道输出,但是在结构图中可以看到,ST公司将NVIC的9~5和15~10分到了一个通道里。下面的20就是指事件响应。
四、代码讲解
我们继续采用之前的例子,将对外式红外传感器连接到GPIOB的PIN14引脚上,我们需要在检测成功时显示检测成功数(从0依次加1)。
1.外部中断配置
第一步:配置RCC,将涉及的外设时钟打开
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //配置RCC时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
EXIT与NVIC的时钟都是打开的
第二步:配置GPIO,选择端口为输入模式
GPIO_InitTypeDef GPIO_InitStructure; // 配置GPIO口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
第三步:配置AFIO, 选择所用的GPIO,连接到后面的EXTI
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14); //配置AFIO
这里使用的函数是GPIO函数,上一篇对于GPIO的讲解缺少了四个函数讲解,现在对其补充。
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
作用:锁定GPIO配置,调用该函数,参数指定某个引脚,那这个引脚的配置就会被锁定,防止意外更改
void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
作用:配置AFIO的事件输出功能
void GPIO_EventOutputCmd(FunctionalState NewState);
作用:配置AFIO的事件输出功能
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
第一个参数:重映射的方式 第二个参数:新的状态
作用:进行引脚重映射
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
作用:配置AFIO的数据选择器,选择想要的中断引脚
void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);
作用:和以太网相关
第四步:配置EXTI,选择边沿触发方式,比如上升沿、下降沿或双边沿,选择触发响应方式,可以选择中断响应或事件响应
EXTI_InitTypeDef EXTI_InitStructure; //配置EXIT
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
EXTI_Init(&EXTI_InitStructure);
第五步、配置NVIC,选择中断的合适优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
最后,经过NVIC,外部中断信号就能进入CPU了
2.配置EXTI
1.EXTI库函数
void EXTI_DeInit(void);
作用:将EXTI的配置清除,恢复成上电默认的状态
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
作用:根据EXTI_InitStruct结构体里的参数配置EXTI外设(初始化EXTI用这个函数)
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
作用:可以把参数传递的结构体变量赋一个默认值
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
作用:软件触发外部中断,调用该函数,参数给一个指定的中断线(EXTI_Line)
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);
作用:获取指定的标志位是否被置1了
总结:主程序里查看和清除标志位
void EXTI_ClearFlag(uint32_t EXTI_Line);
作用:对置1的标志位清除
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
作用:获取中断标志位是否被置1了
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
作用:清除中断挂起标志位
总结:中断函数里查看和清除标志位
2.配置EXTI
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = ;
作用:指定需要配置的中断线
EXTI_InitStructure.EXTI_LineCmd = ;
ENABLE或DISENABLE
作用:指定选择的中断线新状态
EXTI_InitStructure.EXTI_Mode = ;
EXTI_Mode_Interrupt = 0x00,(中断模式)
EXTI_Mode_Event = 0x04(事件模式)
作用:指定外部中断线的模式,选择中断模式或事件模式
EXTI_InitStructure.EXTI_Trigger = ;
EXTI_Trigger_Rising = 0x08,(上升沿触发)(0->1)
EXTI_Trigger_Falling = 0x0C, (下降沿触发)(1->0)
EXTI_Trigger_Rising_Falling = 0x10(双边沿触发)(0->1或1->0)
作用:指定触发信号的有效边沿
EXTI_Init(&EXTI_InitStructure);
3.配置NVIC
1.NVIC库函数
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
参数:中断分组的方式
作用:中断分组
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
作用:根据结构体里指定的参数初始化NVIC(初始化NVIC用这个函数)
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);
作用:设置中断向量表
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);
作用:系统低功耗配置
2.配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断分组
NVIC_InitTypeDef NVIC_InitStructure; // 配置NVIC
NVIC_InitStructure.NVIC_IRQChannel = 通道;
作用:指定中断通道开启还是关闭
NVIC_InitStructure.NVIC_IRQChannelCmd = ;
作用:指定中断通道是使能还是失能,ENABLE或DISENABLE
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = ;
作用:指定通道的抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = ;
作用:指定通道的响应优先级
NVIC_Init(&NVIC_InitStructure);
5.对外式红外传感器总代码
依旧是模块化编程,该代码只是中断的.c文件
#include "stm32f10x.h" // Device header
uint16_t CountSensor_Count;
void CountSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //配置RCC时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure; // 配置GPIO口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14); //配置AFIO
EXTI_InitTypeDef EXTI_InitStructure; //配置EXIT
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
EXTI_Init(&EXTI_InitStructure);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
}
uint16_t CountSensor_Get(void) // 获取次数
{
return CountSensor_Count;
}
void EXTI15_10_IRQHandler(void) //中断函数
{
if (EXTI_GetITStatus(EXTI_Line14) == SET) //中断标志位的判断
{
CountSensor_Count++;
EXTI_ClearITPendingBit(EXTI_Line14); //清除中断标志位
}
}
ps:中断函数的名字需要在启动文件中获取,并非自己定义
6.main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"
int main(void)
{
OLED_Init();
CountSensor_Init();
OLED_ShowString(1, 1, "Count:");
while (1)
{
OLED_ShowNum(1, 7, CountSensor_Get(), 5);
}
}
7.OLED.c
这里我外接了一个屏幕来进行计数操作,代码很简单直接调用就好,因此不过多介绍。

五、总结
其实,代码本质上和上一篇Hello world没有过多区别,本篇需要理解中断的配置,以及在做实际项目是如何正确考虑优先级,以及正确使用中断。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐
所有评论(0)