美文网首页
按键与中断处理

按键与中断处理

作者: 简小黑 | 来源:发表于2019-06-03 17:08 被阅读0次

    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中中断分组的设置来决定。


    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<<EXTOFFSET);//清除原来设置!!!
        AFIO->EXTICR[EXTADDR]|=GPIOx<<EXTOFFSET;//EXTI.BITx 映射到 GPIOx.BITx
        //自动设置
        EXTI->IMR|=1<<BITx; //开启 line BITx 上的中断
        if(TRIM&0x01)EXTI->FTSR|=1<<BITx; //line BITx 上事件下降沿触发
        if(TRIM&0x02)EXTI->RTSR|=1<<BITx; //line BITx 上事件上升降沿触发
    }
    

    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<<NVIC_Channel%32);
    //使能中断位(要清除的话,相反操作就 OK)
    NVIC->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);   
        }
        
    } 
    

    相关文章

      网友评论

          本文标题:按键与中断处理

          本文链接:https://www.haomeiwen.com/subject/zdgqxctx.html