中断

在主程序运行过程中,出现特定的中断触发条件,CPU会暂停当前运行的程序,转而运行中断程序,中断程序执行完成后返回暂停的位置继续执行。

外部中断通用I/O映像如下图

STM32中有20条外部中断线,其中16条是GPIO中断线(0~15),EXTI线16连接PVD输出,EXTI线17连接RTC闹钟事件,EXTI线18连接USB唤醒事件,EXTI线19连接以太网唤醒事件(只适用于互联产品)

外部中断/事件控制器框图

1、边沿检测电路:上升沿、下降沿或双边沿

2、软件配置中断或事件寄存器

3、屏蔽中断寄存器或事件寄存器

4、请求挂起寄存器

5、中断请求发送给NVIC中断控制器

6、事件产生一个脉冲响应

复用功能

STM32的引脚默认是一个普通的GPIO,但是引脚可以被复用其他功能。也就是说有些GPIO除了输入输出的功能以外,还可以复用为定时器、计数器等启动功能

Pin定义

 如图所示,以PA0为例,PA0既可以作为普通的I/O口用作输入输出,还可以作为TIM2_CH1_ETR,定时器2通道1的外部触发源

重映射

重映射属于复用功能的另一个功能,可以把具有特殊功能的引脚分配到其他引脚。如果某个功能陪重映射,该功能不遵循默认分配方式

如图,CAN1_RX有三个引脚可以实现:PA11、PB8、PD0。默认是PA11,但是如果CAN_REMAP的取值不同,那么CAN1_RX的引脚也会不同。假设PA11引脚被作为其他寄存器的引脚,此时又要CAN1启动,那么可以改变CAN_REMAP,通过其他引脚启用CAN_RX;此时PD0引脚是无法用作CAN1_RX。

中断嵌套控制器NVIC

所用的是STM32F10X芯片包含16个内核(异常)中断,可屏蔽中断有60个。当有多个中断触发,程序需要给这些中断排优先级,应该优先处理哪个中断。优先级号码越小,优先级越高。

stm32f10x.h内部所列中断

这60个可屏蔽中断就涉及到STM32中断分组。STM32将60个中断分成5个组,组0-组4,;同时每个中断都设置了抢占优先级和响应优先级或子优先级,配置有SCB->AIRCR寄存器的bits10-8定义。SCB->AIRCR是CM3内核定义的。

具体配置关系如下

CM3定义了8位用于设置中断优先级,但是STM32只选用了其中4位,抢占优先级高于响应优先级,同样数值越小优先级越高。

高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的
抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断
抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行
如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行

打断情况只关于抢占优先级,与响应优先级无关

一般情况下,只设置一次中断优先级分组,例如分组2,设置好组后一般不改变分组。随意分组会导致管理混乱,程序出错。

中断优先级控制函数

1、中断优先级控制函数结构体

                NVIC_SetPRriorityGrouping()

2、中断优先级结构体参数

                NVIC_IRQChanel //定义中断类型,在stm32f10x.h找对应中断名称,如USART1_IRQN

                NVIC_IRQChannelPreemptionPriority //定义中断抢断优先级

                NVIC_IRQChannelSubPriority //定义中断响应优先级

                NVIC_IRQChannelCmd //中断使能或失能

                NVIC_Init //初始化NVIC中断控制器

中断优先级设置步骤

1、系统运行后先设置中断优先级分组,调用函数

2、针对每个中断,分配对应的抢占优先级和响应优先级

3、如果需要挂起或解挂,查看中断激活状态,分别调用对应函数即可

外部中断按键控制LED

USER新建EXTI文件夹,内部新建EXTI.c和EXTI.h

keil工程文件USER导入EXTI.c

以SW2,PA0引脚为中断触发,SW2按下,PA0接地,PA1输出低电平,LED亮。

首先先构建GPIO结构体,然后使能时钟信号。确定PA0是输入模式,当PA0接地,即开关按下,PA1输出低电平。但是外部中断还需要外部中断的时钟信号,但是在源文件没有发现RCC_APB2Periph_EXTI这个参数,因为EXTI外部中断时钟信号集成到AFIO中,所以还需要再加上AFIO。

GPIO_InitTypeDef exti_gpio_struct;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

exti_gpio_struct.GPIO_Mode = GPIO_Mode_IPU;

exti_gpio_struct.GPIO_Pin = GPIO_Pin_0;
GPIO_Init(GPIOA, &exti_gpio_struct);

中断需要构建中断结构体用于配置外部中断参数,参数包括中断线;中断模式是中断还是事件;触发方式是上升沿、下降沿还是双边沿;中断线使能还是失能。最后完成配置应用。

EXTI_InitTypeDef exti_exti_struct;

exti_exti_struct.EXTI_Line = EXTI_Line0;

exti_exti_struct.EXTI_Mode = EXTI_Mode_Interrupt;
exti_exti_struct.EXTI_Trigger = EXTI_Trigger_Falling;
exti_exti_struct.EXTI_LineCmd = ENABLE;

EXTI_Init(&exti_exti_struct);

中断还需要构建NVIC中断管理器结构体,参数包括中断通道;抢占优先级和响应优先级分配;中断通道使能还是失能。最后完成配置应用。

NVIC_InitTypeDef exti_nvic_struct;

exti_nvic_struct.NVIC_IRQChannel = EXTI0_IRQn;
exti_nvic_struct.NVIC_IRQChannelPreemptionPriority = 0;
exti_nvic_struct.NVIC_IRQChannelSubPriority = 0;
exti_nvic_struct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&exti_nvic_struct);

到这里还没有完成中断的相关工作。首先就是没有让PA0绑定中断线。

从图里可以看到,一条EXTI外部中断线与七个引脚相连,如果没有将PA0绑定EXTI0,那么程序就不知道EXTI0中断线应该监控哪个引脚的中断信号。因此需要把GPIO引脚PA0与中断线EXTI0绑定。

GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);

还有一步就是NVIC的分组模式,前面提到STM32一共有5组,分别对应不同的抢占优先级和响应优先级。所以还需要设置分组模式。

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

EXTI.c整体代码如下

#include "EXTI.h"
#include "stm32f10x.h"

void Exti_Init(void)
{
        GPIO_InitTypeDef exti_gpio_struct;//GPIO配置结构体
        EXTI_InitTypeDef exti_exti_struct;//外部中断配置结构体
        NVIC_InitTypeDef exti_nvic_struct;//NVIC配置结构体
        

        //使能GPIO和AFIO的APB2总线时钟

        //AFIO时钟必须开启才能配置中断线映射
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

        //配置优先级分组模式:Group2(2位抢占优先级,2位响应优先级)
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

        

        //配置GPIO引脚与中断线映射:PA0映射到外部中断EXTI0
        GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
    

        //配置GPIO引脚模式
        exti_gpio_struct.GPIO_Mode = GPIO_Mode_IPU;//上拉输入(无信号为高电平)
        exti_gpio_struct.GPIO_Pin = GPIO_Pin_0;//操作PA0引脚
        GPIO_Init(GPIOA, &exti_gpio_struct);//应用GPIO配置
    

        //配置外部中断参数
        exti_exti_struct.EXTI_Line = EXTI_Line0;//使用外部中断线0(对应PA0)
        exti_exti_struct.EXTI_Mode = EXTI_Mode_Interrupt;//设置为中断模式
        exti_exti_struct.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发
        exti_exti_struct.EXTI_LineCmd = ENABLE;//使能EXTI线
        EXTI_Init(&exti_exti_struct);//应用外部中断配置

        //配置中断控制器NVIC。Group2模式下,抢占优先级0-3,响应优先级0-3
        exti_nvic_struct.NVIC_IRQChannel = EXTI0_IRQn;//中断通道为EXTI0
        exti_nvic_struct.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级0
        exti_nvic_struct.NVIC_IRQChannelSubPriority = 0;//响应优先级0
        exti_nvic_struct.NVIC_IRQChannelCmd = ENABLE;//使能中断通道
        NVIC_Init(&exti_nvic_struct);//应用NVIC配置
    
}


 

在主程序中实现EXTI0中断函数不是在main函数内部实现,而是具有特定的中断服务函数实现。

打开startup_stm32f10x_hd.s,文件中包含多种中断函数,项目采用的是EXTI0通道,所以找到EXTI0_IRQHandler函数。外部中断触发的判断采用的是EXTI_GetITStatus,外部中断获取标志位判断中断是否触发。另外,触发中断后需要在每个中断函数后面清除触发EXTI_ClearITPendingBit,否则中断函数只会触发一次,不会重复触发。

main.c代码如下

#include"stm32f10x.h"
#include"main.h"
#include"led.h"
#include"bear.h"
#include"button.h"
#include"Relay.h"
#include"Shake.h"
#include"433M.h"
#include"EXTI.h"

int main()
{
    

    Led_Init();
    Exti_Init();
    
    
    GPIO_SetBits(GPIOA, GPIO_Pin_1);
    
    while(1)
    {
        
         
    }
    
    
    
}


void EXTI0_IRQHandler()//EXTI中断服务函数
{

        if(EXTI_GetITStatus(EXTI_Line0)  != RESET)//EXTI0中断标志是否置位
        {
            GPIO_ResetBits(GPIOA, GPIO_Pin_1);//PA1输出低电平,LED亮
            
            EXTI_ClearITPendingBit(EXTI_Line0);//必须清除标志,否则会重复触发中断
            
        }


}
 

外部中断震动感应LED

Shake.c文件添加外部中断即可。注意,STM32引脚默认低电平,当震动传感器感受到震动瞬间,DO引脚会输出高电平,所以触发方式应该是上升沿触发,另外注意引脚与EXTI之间的对应关系。

Shake.c文件程序代码如下

#include "Shake.h"
#include "stm32f10x.h"

void Shake_Init(void)
{
    
    GPIO_InitTypeDef Shake_GPIO_Struct;
    EXTI_InitTypeDef Shake_EXTI_Struct;
    NVIC_InitTypeDef Shake_NVIC_Struct;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
    
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource8);
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    
    
    Shake_GPIO_Struct.GPIO_Mode = GPIO_Mode_IPU;
    Shake_GPIO_Struct.GPIO_Pin = GPIO_Pin_8;
    GPIO_Init(GPIOB, &Shake_GPIO_Struct);
    


    Shake_EXTI_Struct.EXTI_Line = EXTI_Line8;
    Shake_EXTI_Struct.EXTI_Mode = EXTI_Mode_Interrupt;
    Shake_EXTI_Struct.EXTI_Trigger = EXTI_Trigger_Rising;
    Shake_EXTI_Struct.EXTI_LineCmd = ENABLE;
    EXTI_Init(&Shake_EXTI_Struct);
    
    
    Shake_NVIC_Struct.NVIC_IRQChannel = EXTI9_5_IRQn;
    Shake_NVIC_Struct.NVIC_IRQChannelPreemptionPriority = 0;
    Shake_NVIC_Struct.NVIC_IRQChannelSubPriority = 0;
    Shake_NVIC_Struct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&Shake_NVIC_Struct);
    
    
}

为了区分按键占用的PA0,震动传感器D0口接到PB8引脚。因此相关GPIO,EXTI,NVIC配置会有所不同

1、PB8引脚对应的是GPIOB,但是AFIO和GPIOB均在APB2总线,所以可以同步启动

2、GPIO与EXTI绑定需要作对应。

3、GPIO配置与外部中断按键没太大区别。

4、EXTI配置注意EXTI_Linex要对应PB8,和触发方式是上升沿触发

5、NVIC中断通道注意只有EXTI0_IRQn~EXTI4_IRQn,EXTI5_IRQn~EXTI9_IRQn集成到一个参数控制:EXTI9_5_IRQn。具体可以在stm32f10x.h可以找到,如下图。

同样的,min.c也需要修改,主要是上面提到,只有0~4的中断通道的单独控制的,EXTI8有EXTI9_5控制。具体代码如下

#include"stm32f10x.h"
#include"main.h"
#include"led.h"
#include"bear.h"
#include"button.h"
#include"Relay.h"
#include"Shake.h"
#include"433M.h"
#include"EXTI.h"

int main()
{
    
    Led_Init();
    Shake_Init();
    
    GPIO_SetBits(GPIOA, GPIO_Pin_1);

    
    while(1)
    {
        
         
    }
    
    
    
}


void EXTI9_5_IRQHandler()
{
    
    if(EXTI_GetITStatus(EXTI_Line8) != RESET)
    {
            GPIO_ResetBits(GPIOA, GPIO_Pin_1);
        
            EXTI_ClearITPendingBit(EXTI_Line8);
    }
    
    
}


 

至此,还可以加入前面项目用到的蜂鸣器,做一个简单的震动报警器。只需要在main.c加上蜂鸣器函数即可

代码如下

#include"stm32f10x.h"
#include"main.h"
#include"led.h"
#include"bear.h"
#include"button.h"
#include"Relay.h"
#include"Shake.h"
#include"433M.h"
#include"EXTI.h"


void delay(uint16_t time)
{
        uint16_t i = 0;
        
    while(time--)
    {
        i = 12000;
        
        while(i--);
    }
    
}

int main()
{
    
    Led_Init();
    Bear_Init();
    Shake_Init();
    
    GPIO_SetBits(GPIOA, GPIO_Pin_1);
    GPIO_SetBits(GPIOA, GPIO_Pin_3);

    
    while(1)
    {
        
         
    }
    
    
    
}


void EXTI9_5_IRQHandler()
{
    
    if(EXTI_GetITStatus(EXTI_Line8) != RESET)
    {
            GPIO_ResetBits(GPIOA, GPIO_Pin_1);
            GPIO_ResetBits(GPIOA, GPIO_Pin_3);
        
            EXTI_ClearITPendingBit(EXTI_Line8);
    }
    
    
}


 

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐