按键与中断处理

NVIC中断工作原理

cortex-m3支持256个中断,其中包含了16个内核中断,240个外部中断。stm32中断是cortex-m3中断子集。对于103系列有60个可屏蔽中断。
STM32F103xC强型产品内置嵌套的向量式中断控制器,能够处理多达60个可屏蔽中断通道(不包括16个Cortex™-M3的中断线)和16个优先级。

/*
cortex-m3内核分组方式(8组)结构体表达方式:
*/
typedef struct
{
  __IO uint32_t ISER[8];             中断使能设置寄存器          /*!< 偏移量: 0x000  Interrupt Set Enable Register           */
       uint32_t RESERVED0[24];                                   
  __IO uint32_t ICER[8];              中断清除使能寄存器         /*!<偏移量: 0x080  Interrupt Clear Enable Register        */
       uint32_t RSERVED1[24];                                    
  __IO uint32_t ISPR[8];              中断挂起设置寄存器        /*!< 偏移量: 0x100  Interrupt Set Pending Register          */
       uint32_t RESERVED2[24];                                   
  __IO uint32_t ICPR[8];              中断清除挂起寄存器        /*!<偏移量: 0x180  Interrupt Clear Pending Register        */
       uint32_t RESERVED3[24];                                   
  __IO uint32_t IABR[8];               中断激活状态位寄存器       /*!< 偏移量: 0x200  Interrupt Active bit Register           */
       uint32_t RESERVED4[56];                                   
  __IO uint8_t  IP[240];               中断优先级寄存器       /*!< 偏移量: 0x300  Interrupt Priority Register (8Bit wide) */
       uint32_t RESERVED5[644];        软件触发方式寄存器                          
  __O  uint32_t STIR;                         /*!< 偏移量: 0xE00  Software Trigger Interrupt Register     */
}  NVIC_Type;  

ISER[2]:ISER全称是:Interrupt Set-Enable Registers,这是一个中断使能寄存器组。103系列可屏蔽中断有60个,这里用了2个32位寄存器,总共可以表示64个中断,STM32F103只用了其中的前60位;ISER[0]的bit0-bit31分别对应中断0-31。ISER[1]的bit0-27对应中断32-59;这样,要使能某个中断,必须设置相应的ISER位为1,使该中断被使能(这里仅是使能,还要配合中断分组、屏蔽、IO口映射等设置才算一个完整的中断设置)。
ICER[2]:全称是Interrupt Clear-Enable Registers,是一个清除中断使能寄存器组,和ISER寄存器功能相反。这里专门设置一个ICER寄存器来清除中断位,而不是向ISER写0来擦除,是因为NVIC的这些寄存器都是写1有效的,写0是无效的。
ISPR[2]:全程是Interrupt Set-Pending Registers,是一个中断挂起控制寄存器组。每个位对应的中断和ISER是一样的,通过置1,可将正在进行中的中断挂起,而执行同级或更高级别的中断,写0无效。
ICPR[2]:Interrupt Clear-Pending Registers,解除中断挂起。写1有效,写0无效。
IABR[2]:Interrupt Active Bit Registers,中断激活标志位寄存器组,只读,可以读取当前正在执行的中断是哪一个。在中断执行完成后由硬件自动清零。对应位所代表的中断和ISER相同,如果为1,表示该位所对应的中断正在执行。
IPR[15]:Interrupt Priority Registers,中断优先级控制寄存器组。STM32的中断分组与这个寄存器密切相关。因为STM32的中断多达60多个,所以STM32采用中断分组的办法来确定中断的优先级。IPR寄存器由15个32bit的寄存器组成,每个可屏蔽中断占8bit,这样总共可以表示15x4=60个可屏蔽中断。IPR[0]的[31-24],[23-16],[15-8],[7-0]分别对应中断3-0,总共对应60个外部中断。而每个可屏蔽中断占用的8bit并没有全部使用,只用了高4位。这4位又分为抢占优先级和子优先级。这两个优先级要根据SCB->AIRCR中中断分组的设置来决定。


按键与中断处理_第1张图片
stm32F103外部中断.PNG
typedef struct
{
__IO uint32_t EVCR;
__IO uint32_t MAPR;
__IO uint32_t EXTICR[4];
} AFIO_TypeDef;

STM32 任何一个 IO 口都可以配置成中断输入口,但是 IO 口的数目远大于中断线数(16 个)。于是 STM32 就这样设计,GPIOA~GPIOG 的[15:0]分别对应中断线 15~0。这样每个中断线对应了最多 7 个 IO 口,以线 0为例:它对应了 GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0。中断线每次只能连接到1个IO口上,这样就需要EXTICR来决定对应的中断线配置到哪个GPIO上了。EXTICR 寄存器组,总共有 4 个,因为编译器的寄存器组都是从 0 开始编号的,所以EXTICR[0]~ EXTICR[3],每个EXTICR 只用了其低 16 位。比如如我要设置 GPIOB.1 映射到中断线 1,则只要设置 EXTICR[0]的 bit[7:4]为 0001 即可。默认都是 0000 即映射到 GPIOA。

typedef struct
{
__IO uint32_t IMR;
__IO uint32_t EMR;
__IO uint32_t RTSR;
__IO uint32_t FTSR;
__IO uint32_t SWIER;
__IO uint32_t PR;
} EXTI_TypeDef;

通过这些寄存器的设置,就可以对外部中断进行详细设置了。IMR:中断屏蔽寄存器。这是一个 32 寄存器。但是只有前 19 位有效。当位 x 设置为 1 时,则开启这个线上的中断,否则关闭该线上的中断。
EMR:事件屏蔽寄存器,同 IMR,只是该寄存器是针对事件的屏蔽和开启。
RTSR:上升沿触发选择寄存器。该寄存器同 IMR,也是一个 32 为的寄存器,只有前 19位有效。位 x 对应线 x 上的上升沿触发,如果设置为 1,则是允许上升沿触发中断/事件。否则,不允许。
FTSR:下降沿触发选择寄存器。同 RTSR,不过这个寄存器是设置下降沿的。下降沿和上升沿可以被同时设置,这样就变成了任意电平触发了。
SWIER:软件中断事件寄存器。通过向该寄存器的位 x 写入 1,在未设置 IMR 和 EMR 的时候,将设置 PR 中相应位挂起。如果设置了 IMR 和 EMR 时将产生一次中断。被设置的 SWIER位,将会在 PR 中的对应位清除后清除。
PR:挂起寄存器。当外部中断线上发生了选择的边沿事件,该寄存器的对应位会被置为 1。0,表示对应线上没有发生触发请求。通过向该寄存器的对应位写入 1 可以清除该位。在中断服务函数里面经常会要向该寄存器的对应位写 1 来清除中断请求。

GPIO外部输入中断实验

与按键输入实验相同,通过中断实现。KEY0 控制 DS0,按一次亮,再按一次,就灭。KEY1 控制 DS1,效果同 KEY0。WK_UP 按键则同时控制 DS0 和 DS1,按一次,他们的状态就翻转一次。KEY0为PC5KEY1为PA15WK_UP位PA0。
1 ) 初始化 IO 口为输入。
这一步设置你要作为外部中断输入的 IO 口的状态,可以设置为上拉/下拉输入,也可以设
置为浮空输入,但浮空的时候外部一定要带上拉,或者下拉电阻。否则可能导致中断不停的触
发。在干扰较大的地方,就算使用了上拉/下拉,也建议使用外部上拉/下拉电阻,这样可以一
定程度防止外部干扰带来的影响。
2 ) 开启 IO 口复用时钟,设置 IO 口与中断线的映射关系。
STM32 的 IO 口与中断线的对应关系需要配置外部中断配置寄存器 EXTICR,这样我们要
先开启复用时钟,然后配置 IO 口与中断线的对应关系。才能把外部中断与中断线连接起来。
3 ) 开启与该 IO 口相对的线上中断/ 事件,设置触发条件。
这一步,我们要配置中断产生的条件,STM32 可以配置成上升沿触发,下降沿触发,或者
任意电平变化触发,但是不能配置成高电平触发和低电平触发。这里根据自己的实际情况来配
置,同时要开启中断线上的中断。这里需要注意的是:如果使用外部中断,并设置该中断的 EMR
位的话,会引起软件仿真不能跳到中断,而硬件上是可以的。而不设置 EMR,软件仿真就可以
进入中断服务函数,并且硬件上也是可以的。建议不要配置 EMR 位。
4 ) 配置中断分组(NVIC ),并使能中断。
这一步,我们就是配置中断的分组,以及使能,对 STM32 的中断来说,只有配置了 NVIC
的设置,并开启才能被执行,否则是不会执行到中断服务函数里面去的。关于 NVIC 的详细介
绍。
5 ) 编写中断服务函数。
这是中断设置的最后一步,中断服务函数,是必不可少的,如果在代码里面开启了中断,
但是没编写中断服务函数,就可能引起硬件错误,从而导致程序崩溃!所以在开启了某个中断
后,一定要记得为该中断编写服务函数。在中断服务函数里面编写你要执行的中断后的操作。

寄存器

1)初始化IO口

void KEYInit(void)
{
    RCC->APB2ENR|=1<<2; //使能 PORTA 时钟
    RCC->APB2ENR|=1<<4; //使能 PORTC 时钟
    JTAG_Set(SWD_ENABLE); //关闭 JTAG,开启 SWD
    GPIOA->CRL&=0XFFFFFFF0; //PA0 设置成输入
    GPIOA->CRL|=0X00000008;
    GPIOA->CRH&=0X0FFFFFFF; //PA15 设置成输入
    GPIOA->CRH|=0X80000000;
    GPIOA->ODR|=1<<15; //PA15 上拉,PA0 默认下拉
    GPIOC->CRL&=0XFF0FFFFF; //PC5 设置成输入
    GPIOC->CRL|=0X00800000;
    GPIOC->ODR|=1<<5; //PC5 上拉
}

2)开启IO复用时钟,配置IO口和中断线的映射关系,以及触发条件

void EXTtInit(u8 GPIOx, u8 BITx, u8 TRIM)
{
    U8 EXTADDR;
    u8 EXTOFFSET;
    EXTADDR=BITx/4; //得到中断寄存器组的编号
    EXTOFFSET=(BITx%4)*4;
    RCC->APB2ENR|=0x01; //使能 io 复用时钟
    AFIO->EXTICR[EXTADDR]&=~(0x000F<EXTICR[EXTADDR]|=GPIOx<IMR|=1<FTSR|=1<RTSR|=1<

3)配置中断分组,设置中断优先级,使能中断
设置中断分组

void MY_NVIC_PriorityGroupConfig(u8 NVIC_Group)
{
    u32 temp,temp1;
    temp1=(~NVIC_Group)&0x07;//取后三位
    temp1<<=8;
    temp=SCB->AIRCR; //读取先前的设置
    temp&=0X0000F8FF; //清空先前分组
    temp|=0X05FA0000; //写入钥匙
    temp|=temp1;
    SCB->AIRCR=temp; //设置分组
}

配置中断优先级(包括抢占优先级和子优先级)

void MY_NVIC_Init(u8 NVIC_PreemptionPriority,u8 NVIC_SubPriority,u8 NVIC_Channel,
u8 NVIC_Group)
{
u32 temp;
MY_NVIC_PriorityGroupConfig(NVIC_Group);//设置分组
temp=NVIC_PreemptionPriority<<(4-NVIC_Group);
temp|=NVIC_SubPriority&(0x0f>>NVIC_Group);
temp&=0xf; //取低四位
NVIC->ISER[NVIC_Channel/32]|=(1<IP[NVIC_Channel]|=temp<<4; //设置响应优先级和抢断优先级
}

4)写中断服务函数

void EXTI0_IRQHandler(void)
{
delay_ms(10); //消抖
if(WK_UP==1) //WK_UP 按键
{
LED0=!LED0;
LED1=!LED1;
}
EXTI->PR=1<<0; //清除 LINE0 上的中断标志位
}
//外部中断 9~5 服务程序
void EXTI9_5_IRQHandler(void)
{
delay_ms(10); //消抖
if(KEY0==0) LED0=!LED0; //按键 0
EXTI->PR=1<<5; //清除 LINE5 上的中断标志位
}
//外部中断 15~10 服务程序
void EXTI15_10_IRQHandler(void)
{
delay_ms(10); //消抖
if(KEY1==0) LED1=!LED1; //按键 1
EXTI->PR=1<<15; //清除 LINE15 上的中断标志位
}

库函数

1)配置IO口

GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC,
ENABLE);//使能 PORTA,PORTC 时钟
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
//关闭 jtag,使能 SWD,可以用 SWD 模式调试
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;//PA15
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化 GPIOA15
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//PC5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化 GPIOC5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//PA0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 设置成输入,默认下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化 GPIOA.0

2)开启IO复用时钟,配置IO口和中断线的映射关系,以及触发条件

void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)//GPIO与中断线的映射
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)//中断线上的参数配置
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//外部中断,需要使能 AFIO 时钟

//GPIOC.5 中断线以及中断初始化配置
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource5);
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line=EXTI_Line5;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);//根据 EXTI_InitStruct 中指定的参数初始化外设 EXTI 寄存器

//GPIOA.15 中断线以及中断初始化配置
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource15);
EXTI_InitStructure.EXTI_Line=EXTI_Line15;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);//根据 EXTI_InitStruct 中指定的参数初始化外设 EXTI 寄存器

//GPIOA.0 中断线以及中断初始化配置
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
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);
//根据 EXTI_InitStruct 中指定的参数初始化外设 EXTI 寄存器

3)设置中断分组,设置中断优先级

void NVIC_Init(NVIC_InitTypeDef * NVIC_InitStructure)//中断优先级配置
NVIC_InitTypeDef NVIC_InitStructure;

NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
//使能按键所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //子优先级 1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);//根据 NVIC_InitStruct 中指定的参数初始化外设 NVIC 寄存器

NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
//使能按键所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //子优先级 1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);

NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
//使能按键所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //子优先级 1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);

4)中断服务函数

void EXTI0_IRQHandler(void)
{
delay_ms(10); //消抖
if(WK_UP==1)
{
LED0=!LED0;
LED1=!LED1;
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除 EXTI0 线路挂起位
}
void EXTI9_5_IRQHandler(void)
{
delay_ms(10); //消抖
if(KEY0==0) {
LED0=!LED0;
}
EXTI_ClearITPendingBit(EXTI_Line5); //清除 LINE5 上的中断标志位
}
void EXTI15_10_IRQHandler(void)
{
delay_ms(10); //消抖
if(KEY1==0) {
LED1=!LED1;
}
EXTI_ClearITPendingBit(EXTI_Line15); //清除 LINE15 线路挂起位
}

常用中断服务函数格式为:

void EXTI2_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line2)!=RESET)//判断某个线上的中断是否发生
{
中断逻辑…
EXTI_ClearITPendingBit(EXTI_Line2); //清除 LINE 上的中断标志位
}
}

STM32Cube

配置端口后,软件自动生成配置函数,只需要写回调函数即可。(清除中断标志位在void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin);函数中)

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{ 
    if (GPIO_Pin & WK_UP_Pin){
    HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_8);
    HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_2);
    
        
    }
    
    if (GPIO_Pin & KEY0_Pin){
        
    HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_8);   
    }
    if (GPIO_Pin & KEY1_Pin){
        
    HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_2);   
    }
    
} 

你可能感兴趣的:(按键与中断处理)