目录
前言
一、中断和事件的区别
二、功能框图
三、外部中断配置
四、源码分析
总结
在嵌入式系统的世界里,微控制器(MCU)是许多应用的核心。要想充分发挥芯片的性能,熟练掌握其中断管理系统是必不可少的。中断,作为MCU与外界交互的重要手段,能够让我们的程序实时响应外部事件,从而提高系统的整体效率和实时性。
在本文中,我们将深入探讨STM32F103VET6的中断系统,包括其基本概念、工作原理、配置方法以及实际应用案例。通过本文的学习,将能够理解和掌握STM32F103VET6的中断管理,提升你的嵌入式系统设计能力。
中断:中断是指当CPU执行程序时,由于发生了某种随机的事件(外部或内部),引起CPU暂时中断正在运行的程序,转去执行一段特殊的服务程序(中断服务子程序或中断处理程序),以处理该事件,该事件处理完后又返回被中断的程序继续执行。其中,引发中断的随机事件被称为中断源。
事件:在嵌入式系统中,事件本质上就是一个触发信号,用来触发特定的外设模块或核心本身(唤醒),它可以触发中断,也可以不触发。事件只是一个触发信号(脉冲),用于表示某个特定的条件已经发生或满足。事件通常与中断相关,但二者在概念和处理方式上有所区别。
二者的区别:用一个简单的例子来说明,想象你正在看一本有趣的书,突然电话铃响了。在这个例子中,电话铃响就是一个事件,它打断了你的阅读,需要你做出响应。你可以选择忽略电话继续阅读,也可以选择接听电话。如果你选择接听电话,那么你就响应了这个事件。这个过程类似于中断的处理。中断是一种特殊的事件,当它发生时,系统会立即停止当前的任务,转而处理这个中断。就像你选择接听电话一样,系统会跳转到专门的中断处理程序来处理这个中断。
中断处理程序就像是一个专门用来接听电话的房间。一旦你进入这个房间,你就不能再继续阅读了,而是专注于电话通话。同样地,当中断处理程序运行时,它会暂停其他任务的执行,专注于处理中断。
所以事件只是表明发生了某件事情,而中断则需要系统立即响应。在这个例子中,电话铃响是一个事件,你可以选择忽略它;但是当中断发生时,系统必须立即响应,不能选择忽略。可以把事件看作是一个提醒或者通知,而中断则是一种需要立即行动的紧急情况。在嵌入式系统中,这种紧急情况可能是外部设备发出的请求、传感器的信号等等,需要系统立即处理以确保系统的实时性和稳定性。
外部中断EXTI:外部中断是指当某种外部事件发生时,单片机的中断系统将迫使CPU暂停正在执行的程序,转而去进行中断事件的处理。例如,在单片机实时地处理外部事件时,如果某个外部事件发生(如按键输入、定时器溢出等),系统会暂停当前执行的程序,跳转到相应的中断处理程序去处理这个外部事件。处理完中断事件后,程序会返回到被中断的位置继续执行。
由外部中断/事件控制器框图能看出,主要分为两大功能,控制中断和事件的产生。图中的双向箭头上的斜杠,旁边标注20代表有20个线路,可以将其配置成中断源或者事件源,但是通道19只适用于互联型产品,对于非互联型产品只有19个线路。
首先来看中断时如何产生的,从右至左看,首先时输入线,对于非互联型产品,有19个中断/事件输入线,可以通过配置任意一个可用的GPIO作为输入线,输入线确定之后,来到边沿检测电路,可以通过配置相关寄存器来选择上升沿触发、下降沿触发或者上升/下降均触发;然后来到一个或门电路,一端来自边沿检测电路,另一端来自软件中断事件寄存器。那什么是软件中断呢?
软件中断:软件中断是一种由软件程序触发的中断,通常用于系统调用、进程切换、异常处理等任务。与硬件中断不同,软件中断不是由硬件设备触发的,而是由软件程序主动发起的。软件中断需要在程序中进行调用,其响应速度和实时性相对较差,但是具有灵活性。
举个例子,假如你正在家里看电视,突然电话铃响了。
硬件中断就像是电话铃响,它是由电话设备(硬件设备)自动触发的,打断了你正在做的事情(看电视),需要你立即响应(接电话)。电话铃响的优先级是很高的,因为它是一个紧急的事件,需要你立即处理。
软件中断就像是你想换一个电视频道,这是你自己主动发起的一个请求(软件触发),你需要按下遥控器上的按钮(执行相应的代码),然后电视才会切换到新的频道(处理中断事件)。这个过程的优先级相对较低,因为它不是紧急事件,你可以在任何时候进行频道切换,而且即使稍微延迟一下也不会造成太大的影响。
所以,硬件中断和软件中断的主要区别在于触发方式(自动触发还是主动发起)、优先级(紧急程度)和实时性(处理速度)。
回到框图中,或门电路任意一端有1就输出1,所以当检测到外部线路有电平变化时,就输出1,紧接着来到中断挂起寄存器,当在外部中断线上发生了选择的边沿事件,该位被置’1’。将中断屏蔽器配置为开放来自外部线路上的中断请求,两端都置位时,与门电路输出1,最后输出到NVIC中,实现中断的产生,在中断服务函数中执行相应的操作。
事件的产生也是同样的原理,通过边沿检测电路检测外部线路电平变化,经过或门电路输出,将事件屏蔽器置位,开放来自外部线路的事件请求,最后输出到脉冲发生器进行控制。
外部中断的配置总结为以下几个步骤:
这里所提及的AFIO是什么呢?
在STM32中,AFIO(Advanced Function Input/Output)是指GPIO端口的复用功能。GPIO除了用作普通的输入输出(主功能)外,还可以作为片上外设的复用输入输出,如串口、ADC等,这些就是复用功能。大多数GPIO都有一个默认复用功能,有的GPIO还有重映射功能。重映射功能是指把原来属于A引脚的默认复用功能转移到了B引脚进行使用,前提是B引脚具有这个重映射功能。当把GPIO用作EXTI外部中断或使用重映射功能的时候,必须开启AFIO时钟。而在使用默认复用功能的时候,则不必开启AFIO时钟。
exti.c
#include "exti.h"
static void KEY1_NVIC_Config()
{
//配置中断分组为第2组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//配置中断源、抢占优先级、子优先级
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void KEY_EXTI_Cogfig()
{
// 使能GPIOA时钟、AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
//PA0配置
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置PA0重映射到外部中断输入线
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
//配置外部中断的通道,触发方式
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
//外部中断初始化
EXTI_Init(&EXTI_InitStructure);
//配置NVIC分组
KEY1_NVIC_Config();
}
首先来看外部中断配置函数KEY_EXTI_Cogfig(),先打开GPIOA和AFIO的时钟,因为配置GPIO重映射需要用到外部中断配置寄存器 1(AFIO_EXTICR1),所以必须打开AFIO的时钟;然后将对应GPIO端口配置成浮空输入,端口电平的到底取决于外部设备输入的电平,然后调用GPIO_EXTILineConfig()函数将PA0重映射到外部中断输入线,右键跳转到定义处。
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
{
uint32_t tmp = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_EXTI_PORT_SOURCE(GPIO_PortSource));
assert_param(IS_GPIO_PIN_SOURCE(GPIO_PinSource));
tmp = ((uint32_t)0x0F) << (0x04 * (GPIO_PinSource & (uint8_t)0x03));
AFIO->EXTICR[GPIO_PinSource >> 0x02] &= ~tmp;
AFIO->EXTICR[GPIO_PinSource >> 0x02] |= (((uint32_t)GPIO_PortSource) << (0x04 * (GPIO_PinSource & (uint8_t)0x03)));
}
在此函数内部,首先会进行参数检查。如果GPIO_PortSource
和GPIO_PinSource
符合要求,则会继续执行配置操作。函数会使用位运算和位移操作来设置AFIO->EXTICR
寄存器。这个寄存器用于配置每个外部中断线路的源。我们配置PA0引脚作为外部中断输入,并选择TIM3作为触发源。
具体来说,函数会执行以下操作:
GPIO_PinSource & (uint8_t)0x03
确定)。PA0引脚对应的是第0位。 这样,通过一系列的位操作和位移运算,AFIO->EXTICR
寄存器就被配置为预期的值,将PA0引脚映射到外部中断输入。
接下来,通过给EXTI_InitStructure
的成员变量赋值,我们设置了以下参数:
EXTI_Line
:设置为EXTI_Line0
,表示我们要配置的是Line0的中断线。EXTI_Mode
:设置为EXTI_Mode_Interrupt
,表示我们希望将该中断线配置为中断模式。EXTI_Trigger
:设置为EXTI_Trigger_Rising
,表示我们希望配置为上升沿触发。EXTI_LineCmd
:设置为ENABLE
,表示我们希望使能该中断线。然后,我们调用EXTI_Init()函数来初始化外部中断控制器。这个函数会根据传入的参数来配置对应的硬件寄存器。
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)
{
uint32_t tmp = 0;
/* Check the parameters */
assert_param(IS_EXTI_MODE(EXTI_InitStruct->EXTI_Mode));
assert_param(IS_EXTI_TRIGGER(EXTI_InitStruct->EXTI_Trigger));
assert_param(IS_EXTI_LINE(EXTI_InitStruct->EXTI_Line));
assert_param(IS_FUNCTIONAL_STATE(EXTI_InitStruct->EXTI_LineCmd));
tmp = (uint32_t)EXTI_BASE;
if (EXTI_InitStruct->EXTI_LineCmd != DISABLE)
{
/* Clear EXTI line configuration */
EXTI->IMR &= ~EXTI_InitStruct->EXTI_Line;
EXTI->EMR &= ~EXTI_InitStruct->EXTI_Line;
tmp += EXTI_InitStruct->EXTI_Mode;
*(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
/* Clear Rising Falling edge configuration */
EXTI->RTSR &= ~EXTI_InitStruct->EXTI_Line;
EXTI->FTSR &= ~EXTI_InitStruct->EXTI_Line;
/* Select the trigger for the selected external interrupts */
if (EXTI_InitStruct->EXTI_Trigger == EXTI_Trigger_Rising_Falling)
{
/* Rising Falling edge */
EXTI->RTSR |= EXTI_InitStruct->EXTI_Line;
EXTI->FTSR |= EXTI_InitStruct->EXTI_Line;
}
else
{
tmp = (uint32_t)EXTI_BASE;
tmp += EXTI_InitStruct->EXTI_Trigger;
*(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
}
}
else
{
tmp += EXTI_InitStruct->EXTI_Mode;
/* Disable the selected external lines */
*(__IO uint32_t *) tmp &= ~EXTI_InitStruct->EXTI_Line;
}
}
具体来说,此函数可能会执行以下操作:
EXTI_InitStructure.EXTI_Line
来决定要配置哪个中断线(在这里是Line0);EXTI_InitStructure.EXTI_Mode
的值(在这里是EXTI_Mode_Interrupt
),设置IMR和EMR中对应的中断线位,以将该中断线配置为中断模式;EXTI_InitStructure.EXTI_Trigger
的值(在这里是EXTI_Trigger_Rising
),设置RTSR(上升沿触发选择寄存器)和FTSR(下降沿触发选择寄存器)中对应的中断线位,以将该中断线配置为上升沿触发。这些操作都是直接针对STM32的硬件寄存器进行的,通过位操作来设置或清除特定的位,从而实现对外部中断控制器的配置。
然后配置NVIC的分组,这里设置为2,意味着2位抢占优先级和2位响应优先级。然后,创建了一个NVIC_InitTypeDef
类型的结构体变量NVIC_InitStructure
,并设置了以下属性:
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
:指定了这个中断通道是EXTI0。NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
:设置了抢占优先级为1。抢占优先级高的中断可以打断正在处理的抢占优先级低的中断。NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
:设置了响应优先级为1。NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
:使能了这个中断通道。最后,通过NVIC_Init(&NVIC_InitStructure)
把以上配置应用到NVIC。
static void KEY1_NVIC_Config()
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
我这里只配置了一个外部中断源,对于中断的分组可以不那么严谨,如果当中断源较多时,我们就需要合理分配优先级来管理中断,具体来说主要遵循
上述一整个流程就完成了外部中断的配置,当外部中断产生时,系统就会停下目前正在处理的事件,转而去执行中断服务函数,执行完成之后再回到主程序继续执行之前搁置的事件。
exti.h
#ifndef __EXTI_H
#define __EXTI_H
#include "stm32f10x.h"
static void KEY1_NVIC_Config(void);
void KEY_EXTI_Cogfig(void);
#endif
中断服务函数
void EXTI0_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line0)!=RESET)
{
LED_ON;
EXTI_ClearFlag(EXTI_Line0);
}
}
main.c
int main(void)
{
LED_Init();//LED初始化
KEY_EXTI_Cogfig();//配置外部中断
while(1)
{
}
}
总而言之,我们需要理解STM32F103VET6的中断系统如何工作,包括中断源,中断优先级,中断处理流程等基本概念。这有助于我们确定何时以及如何使用中断,以最大限度地提高微控制器的效率和性能。其次,我们需要根据实际应用的需求来配置和使用中断系统。例如,我们可以配置不同的中断源和优先级以满足特定的实时性需求,或者使用中断嵌套来处理多个同时发生的中断。我们还可以在中断处理程序中执行特定的任务,例如读取或清除标志位,以控制中断的响应和恢复过程。通过以上知识的学习,我们可以充分利用STM32F103VET6的中断系统来创建高效,实时和可靠的嵌入式应用程序。这不仅可以帮助我们实现复杂的功能,还可以提高系统的性能和响应速度。