一、介绍
中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的。中断是指CPU在正常运行程序时,由于内部或外部事件的发生,导致CPU中断当前运行的程序,转而去执行其他程序的过程。 中断可以是硬件产生的,也可以是软件产生的。硬件中断通常由外围设备触发,而软件中断则是通过CPU指令集中的一个指令来实现。
二、原理
(1)中断请求
中断源以硬件信号形式通过中断控制线路向CPU提出中断请求。 中断源:引起中断事件的原因。 中断源可以是外部的硬件设备,如键盘、打印机等输入/输出设备和各种控制设备;也可以是软件指令,如中断指令(软中断,访管指令);还可能是由各种故障和出错引起的中断(自陷),如计算溢出等。
⑵ 中断判优及响应,中断控制器根据中断优先权进行判断,择优予以响应。
⑶ 保护现场 ,CPU保护主程序的运行现状,如PC值、PSW、寄存器和内存中的重要数据。
⑷ 中断服务, 按中断源的工作要求,查询中断向量表,执行相应的中断服务程序。
⑸ 恢复现场, 为了正确返回原程序,需要进行恢复现场的工作,即将前面保存的寄存器的内容送回原寄存器。
⑹ 中断返回 返回被中断的程序 ,继续执行。
外部中断/事件控制器
中断控制器NVIC
- 中断请求:称为中断源或中断事件、是指外部设备或内部模块发出的信号,通知微控制器发生了一个特定的事件。
- 中断控制器:中断控制器负责对中断请求进行管理和分配优先级,如NVIC。
- 中断优先级:每个中断都具有一个优先级,用于确定中断处理程序的执行顺序。较高优先级的中断会打断正在执行的较低优先级中断或主程序。
- 中断处理程序:中断服务函数,是一段用于处理特定中断的代码。当中断发生时,微控制器会跳转到相应的中断处理程序执行相关操作,处理完毕后返回主程序。
- 中断使能:通过设置相应的中断使能位,可以启用或禁用特定的中断。禁用中断后,即使中断请求发生,微控制器也不会响应。
- 中断标志:中断标志用于指示特定中断是否已经发生。在处理完中断后,需要清除相应的中断标志,以便再次触发相同中断。
- 中断向量:是指中断服务程序入口地址的偏移量与段基值,一个中断向量占据4字节空间。
- 中断向量表:中断向量表是存储中断处理程序地址的表格,当中断发生时微控制器会根据中断向量表中相应中断的地址跳转到对应的中断处理函数。
三、实例
STM32处理中断的步骤
外设发出中断请求
处理器暂停当前执行的任务,保护现场,将当前位置的程序计数器(PC)地址压栈。
程序跳转到中断服务程序, 执行中断服务程序
恢复现场, 将栈顶的值会送到PC
跳转到被中断的位置执行下一个指令。
配置定时器的定时中断
cpp
#include "stm32f10x.h" // Device header
/**
* 函 数:定时中断初始化
* 参 数:无
* 返 回 值:无
*/
void Timer_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
/*中断输出配置*/
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
//TIM_TimeBaseInit函数末尾,手动产生了更新事件
//若不清除此标志位,则开启中断后,会立刻进入一次中断
//如果不介意此问题,则不清除此标志位也可
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//即抢占优先级范围:0~3,响应优先级范围:0~3
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在main函数内,while循环之前
//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
*/
cpp
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num; //定义在定时器中断里自增的变量
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Timer_Init(); //定时中断初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "Num:"); //1行1列显示字符串Num:
while (1)
{
OLED_ShowNum(1, 5, Num, 5); //不断刷新显示Num变量
}
}
/**
* 函 数:TIM2中断函数
* 参 数:无
* 返 回 值:无
* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
* 函数名为预留的指定名称,可以从启动文件复制
* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
*/
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) //判断是否是TIM2的更新事件触发的中断
{
Num ++; //Num变量自增,用于测试定时中断
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIM2更新事件的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}
四、中断和DMA有什么区别
中断传输方式是在数据缓冲区满后发生中断请求,CPU进行中断处理将数据传输到内存,而DMA方式则是以数据块为单位传输的,在所要求传送的数据块全部传送结束时要求CPU进行中断处理。DMA运输的大部分时间,CPU和输入输出都处于并行操作,因此,整个计算机系统的效率大大提高,但DMA也是要利用中断的,否则CPU无法获知数据已经传输结束。
参考: