【STM32F103】中断系统&外部中断

本文详细解析了STM32F103的中断系统,包括NVIC控制器的作用、Cortex-M3的中断特性(如16级优先级和抢占/响应优先级)、GPIO与外部中断的映射、以及中断优先级的配置方法。还提供了示例代码和参考资源。
摘要由CSDN通过智能技术生成

STM32F103的中断系统

说到中断就离不开嵌套向量中断控制器(NVIC),NVIC控制整个芯片的中断相关问题,NVIC属于是内核里面的一个外设,因此与其说是STM32F103的中断系统,倒不如说是Cortex-M3(STM32F103的内核)的中断系统。

Cortex-M3内核支持256个中断,其中16个内部中断,240个外部中断。以及256级中断优先级的设置。

而STM32可支持85个中断,其中16个内部中断,68个外部中断。以及16级中断优先级的设置。

下图出自《STM32F10xxx参考手册(中文)》,STM32支持68个中断通道(中断通道!=中断)。

中断优先级

STM32或者说Cortex-M3中的优先级分为两种,分别是抢占优先级和响应优先级。

如果一个中断事件触发了,那么会打断主函数,进入中断函数。但此时另一个中断事件也触发了,如果后来的中断事件的抢占式优先级更高,那么第二个中断事件会打断第一个中断,进入第二个中断函数里,这也被称为中断嵌套。这就是抢占优先级的作用。

响应优先级相对鸡肋一些,如果两个中断事件的抢占优先级不一样,那么响应优先级派不上用场。

在抢占优先级一样的情况下,如果两个中断事件来的时间不一样,那么响应优先级也是派不上用场的。

响应优先级的作用是,当两个抢占优先级一样的中断事件同时触发的时候,响应优先级高的事件优先触发。

当两种优先级相同的两个事件同时触发的时候,触发的顺序按照中断表(参考手册的P132)中的排位顺序触发。

在STM32中用来配置两种优先级的寄存器用到4位,也就是说两个优先级的等级全靠4个bit。

所以我们有五种选择来分配两种优先级的数值。

分别是:

4位用于指定抢占优先级,0位用于指定响应优先级。

3位用于指定抢占优先级,1位用于指定响应优先级。

2位用于指定抢占优先级,2位用于指定响应优先级。

1位用于指定抢占优先级,3位用于指定响应优先级。

0位用于指定抢占优先级,4位用于指定响应优先级。

可以配置的优先级等级为2^(位数),所以可以知道可分配的优先级最多为16级,但是另外一种优先级就无法配置了。

一般我们是抢占优先级和响应优先级各分2位,或者是抢占优先级多一位。

分配的优先级数值越小,优先级越高。

外部中断/事件控制器

以下两图取自《STM32F10xxx参考手册(中文)》P135和P137

主要看一看第二个图,可以看的出来一共有16个可映射的外部中断线EXTIn(n=0~15)。

但是每个通道的左侧对应的确实GPIOXn(X=A~G,n=0~15),因此可以知道GPIOA的0号引脚和GPIOB的0号引脚共用一个中断线EXTI0,以此类推。

同一个时刻,只能有一个端口的n号引脚映射到外部中断线中,并且引脚号需要和外部中断线的号一样。也就是说我们用了GPIOA的0号引脚作为外部中断,那么就不能用GPIOB的0号引脚作为外部中断了。

另外,映射到外部中断线的引脚需要配置为输入模式,一般不配置为浮空输入,多为下拉输入或上拉输入。

如果我们使用GPIO口来映射到中断线的话,我们需要使用AFIO,打开对应的时钟以及进行对应的设置。

但如果配置的中断不是GPIO口的外部中断的话则不用。

固件库函数

 配置AFIO映射

参数一选择要映射到中断线的GPIO端口,可选参数为GPIO_PortSourceGPIOX(X=A~G)

参数二选择要映射到中断线的GPIO引脚号,可选参数为GPIO_PinSourceX(X=0~15)

虽然这个函数名的开头是GPIO,但是其实是属于AFIO寄存器的。

分配优先级位数

参数选择分组,可选的选项有NVIC_PriorityGroupX(X=0~4)。

X为为抢占优先级分配的位数,而响应优先级分配的位数就为4-X了。一般五五开,各占两位。

不论整个程序用了多少个中断,这个函数仅需调用一次,因为我们指定优先级的位数就只有4位,指定一次即全局使用了。

初始化中断通道的优先级

这个初始化函数的参数只有一个结构体变量。

NVIC_IRQChannel:指定中断通道。在stm32f10x.h文件中可以找到。

 找到对应的STM型号,我这里是STM32F103C8T6属于MD的,因此我找到对应的位置。

此外在所有#ifdef之上还有所有型号通用的中断通道。

NVIC_IRQChannelPreemptionPriority:指定抢占优先级。根据分配的位数来确定填入参数的范围(0~(2^n)-1)n为分配的位数。数值越小,优先级越高

NVIC_IRQChannelSubPriority:指定响应优先级。

NVIC_IRQChannelCmd:使能,ENABLE或是DISABLE

配置外部中断

这个函数的参数同样只有一个结构体变量。

EXTI_Line:指定中断线,参数为EXTI_LineX(X为0~15),使用的GPIO哪个引脚就选哪条中断线。

EXTI_Mode:指定模式,可选中断模式或者事件模式,一般都是中断模式(我没用过事件模式),EXTI_Mode_Interrupt

EXTI_Trigger:选择触发的方式,可选上升沿EXTI_Trigger_Rising,下降沿EXTI_Trigger_Falling,上升沿和下降沿都行EXTI_Trigger_Rising_Falling

EXTI_LineCmd:ENABLE或者DISABLE

中断函数

可以在startup_stm32f10x_md.s文件里找到对应中断线的中断函数。

中断函数要求都是无参无返回值的。

中断一旦发生,那么中断标志位都会被置位,需要我们软件手动清除,如果不在中断函数里清除中断标志位的话,那么一旦触发了一次中断,由于中断标志位始终处于置位状态,那么CPU会不断地进入中断函数。

获取中断标志位

参数为中断线,我们可以通过这个函数知道中断是否是由某条中断线触发的。参数为EXTI_LineX(X=0~19)

清除中断标志位

参数同上,通过这个函数清除中断标志位。

其他中断

稍微小总结一下,外部中断需要配置AFIO+NVIC+EXTI。不过内部中断仅需要NVIC加上对应的中断启动函数即可。

通常我们都可以在对应的头文件中找到中断启动函数。

ADC的中断配置函数: 

USART的中断配置函数:

I2C的中断配置函数:

可以总结出来,不同的内部中断配置函数都有一个共同点,那就是函数名以ITConfig结尾,具体的配置方法就参考具体的文件里的注释了。 

接线

 两个按钮分别接在了GPIOA的6号引脚和7号引脚上,6号引脚的按钮的另一端接在高电平,7号引脚的按钮的另一端接在低电平。

6号按钮按下,则会产生上升沿电平进入触发中断,因此GPIOA的6号引脚应该配置为下拉输入,即默认是低电平。

7号按钮按下,则会产生下降沿电平进入触发中断,因此GPIOA的7号引脚应该配置为上拉输入,即默认是高电平。

现象

代码

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"

uint16_t count1=0;
uint16_t count2=0;

int main(void){
    OLED_Init();
    //无需打开EXTI和NVIC的时钟.
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);         //打开AFIO引脚映射寄存器的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);        //打开GPIOA的时钟
    
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource6);  //将GPIO口的引脚映射到中断线上
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource7);  //虽然函数名为GPIO开头,实际上属于AFIO
    
    //将GPIOA的6号引脚配置为下拉输入模式,默认读取的电平为低电平(逻辑0)
    GPIO_InitTypeDef gitd;
    gitd.GPIO_Mode=GPIO_Mode_IPD;
    gitd.GPIO_Pin=GPIO_Pin_6;
    gitd.GPIO_Speed=GPIO_Speed_2MHz;
    GPIO_Init(GPIOA,&gitd);
    //将GPIOA的7号引脚配置为上拉输入模式,默认读取的电平为高电平(逻辑1)
    gitd.GPIO_Mode=GPIO_Mode_IPU;
    gitd.GPIO_Pin=GPIO_Pin_7;
    GPIO_Init(GPIOA,&gitd);
    //配置NVIC优先级位数分组,抢占优先级和响应优先级各两位,数值范围为0~3.
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    //配置中断通道的优先级
    NVIC_InitTypeDef nitd;
    nitd.NVIC_IRQChannel=EXTI9_5_IRQn;
    nitd.NVIC_IRQChannelCmd=ENABLE;
    nitd.NVIC_IRQChannelPreemptionPriority=1;   //抢占优先级
    nitd.NVIC_IRQChannelSubPriority=1;          //响应优先级
    NVIC_Init(&nitd);
    //配置外部中断
    EXTI_InitTypeDef eitd;
    eitd.EXTI_Line=EXTI_Line6;              //GPIOA的6号引脚对应6号中断线
    eitd.EXTI_LineCmd=ENABLE;
    eitd.EXTI_Mode=EXTI_Mode_Interrupt;     //选择中断模式
    eitd.EXTI_Trigger=EXTI_Trigger_Rising;  //上升沿触发,因为GPIO口配置为了下拉输入
    EXTI_Init(&eitd);
    
    eitd.EXTI_Line=EXTI_Line7;              //GPIOA的7号引脚对应7号中断线
    eitd.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿触发,因为GPIO口配置为了上拉输入
    EXTI_Init(&eitd);
    
    OLED_ShowString(1,1,"count1: ");
    OLED_ShowString(2,1,"count2: ");
    while(1){
        OLED_ShowNum(1,9,count1,4);
        OLED_ShowNum(2,9,count2,4);
        
    }
}
//在startup_stm32f10x_md.s文件里找到对应中断线的中断函数
void EXTI9_5_IRQHandler(void){
    if(EXTI_GetFlagStatus(EXTI_Line6)){     //因为两条中断线用的都是同一个函数,因此通过获取中断标志位来判断是谁触发的中断
        ++count1;
        Delay_ms(10);                       //消除机械按键的抖动
        while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==1);
        Delay_ms(10);
        EXTI_ClearITPendingBit(EXTI_Line6); //需要手动清除中断标志位,否则会一直重复触发.
    }else if(EXTI_GetFlagStatus(EXTI_Line7)){
        ++count2;
        Delay_ms(10);
        while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7)==0);
        Delay_ms(10);
        EXTI_ClearITPendingBit(EXTI_Line7);
    }
}

参考

b站江科大自化协

《STM32F10xxx参考手册(中文)》

《ARM Cortex-M3 嵌入式原理及应用 基于STM32F103微控制器 》

  • 25
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值