野火电子论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 6515|回复: 3

[STM32中级篇]第十八课 EXTI - GPIO外部中断

[复制链接]
发表于 2022-7-30 00:36:07 | 显示全部楼层 |阅读模式
中级篇 第十八课《EXTI - GPIO外部中断》课后作业
    中级篇 第十八课题目:
    野火论坛202207291415058552..png



    在应用层面对于中断的理解:
      在中断编程之前,先浅谈一下实际的运用场景。拿按键举例,要为一个单片机编程实现按键检测然后进行相应操作。如果不使用中断,能否实现?是否会遇到问题?
    可以实现。但是会有BUG,最大的问题就是实时性。什么是实时性问题?就是要保证一直在检测按键是否被按下。通常这类程序都是一个循环结构,如果不这么做,
    假设程序运行周期是5T,按键检测所需时间为1T。按键检测功能只有在那个“1T”内是有效的,在剩下的“4T”都无效。
      如果要保证实时性,一种可能的解决方案是“多线程”,引入操作系统的概念。这就出现了例如UCOS、FreeRTOS、Vxworks这类操作系统。
      如果不使用操作系统纯靠编程,也可以实现,但是会比较复杂。可能的方案是利用算法实现时间片轮转,模拟多线程。
      对于一些能力较弱的单片机,这些单片机可能不能搭载实时操作系统,自身结构可能也比较简单。曾经我在Arduino单片机上做过按键检测,Arduino结构相对简单,
    主函数本身是一个循环体。那个项目除了要在循环体里除了实现按键检测,还要做别的,比如时钟累加、温度检测。这样对于实时性的干扰是很强的,按键总是不灵敏。
    当时我没有想到使用中断的方式,由于没有时间模块,定义了三个变量(时、分、秒)来模拟,每一次循环秒+1,判断秒=60的时候,分+1,秒清零。判断分=60的时候,
    时+1,清零。每次循环的延时都是1000ms(使用的是Arduino库函数自带的delay),这样可以实现时间显示,但误差会非常大。我想到的方法是把1秒(1000ms)切成
    200个小份,一份5毫秒。每一个5毫秒都检测一下按键,但这样的解决方案也是治根不治本。而使用中断似乎能解决按键实时检测的问题。


    EXTI中断编程:
      移植点亮led的例程,在User文件夹创建exti文件夹,里面创建exti.c以及它配套的头文件exti.h。
       野火论坛202207291548002862..png



    EXTI中断步骤:
      1.初始化NVIC,用于处理中断
      2.初始化要连接到EXTI的GPIO
      3.初始化EXTI用于产生中断/事件
      4.编写中断服务函数stm32f10x_it.c

      5.main函数


    NVIC要先配置,先写NVIC初始化函数
    NVIC配置步骤:
      1.配置中断优先级分组
      2.配置NVIC寄存器,初始化NVIC_InitTypeDef
      NVIC_InitTypeDef:
        NVIC_IRQChannel:中断源
        NVIC_IRQChannelPreemptionPriority:抢占优先级
        NVIC_IRQChannelSubPriority:子优先级
        NVIC_IRQChannelCmd:使能或者失能
      3.编写中断服务函数


    野火论坛202207291603582922..png

    NVIC结构体原型存放于misc.h

    两个按键分别位于GPIOA P0 和GPIOC P13,分别为两个按键写中断。
    1.两个中断都分在优先级组一

    2.P0使用的是EXTI0线,中断源是EXTI0_IRQn;P13使用的是EXTI13线,这里需要注意:中断源是EXTI15_10_IRQn 而不是EXTI13_IRQn
    野火论坛202207291623573197..png

    从EXTI5开始,命名就发生变化了。

    野火论坛202207291624244463..png

    野火论坛202207291624598161..png

    3.抢占优先级都写1,次优先级P0写1、P13写2。
    4.分别使能,使用Init函数初始化结构体
    野火论坛202207291555364431..png

    因为NVIC函数只用于exti.c,我们不希望被别的程序调用,在函数名字前面加一个static关键字以保护函数。

    然后写EXTI配置函数
    1.先调用刚才写好的NVIC配置函数
    2.初始化所用到的GPIO
     1)使能RCC_APB2时钟
     2)设置管脚
     3)设置输入模式
    用Init函数初始化


    为了增加可移植性,关于硬件GPIO连接的部分都写到了头文件的宏定义
    野火论坛202207291648176013..png



    GPIO初始化过程,按键使用浮空输入模式,输入没有速度。

    野火论坛202207291644373002..png



    3.接下来是EXTI初始化
    仿照GPIO初始化步骤
    先使能时钟,EXTI使用的是AFIO的时钟
    野火论坛202207291834109433..png

    野火论坛202207292250569499..png



    AF代表复用(Alternate Functions),何为复用?打个比方,一间房间有一扇门,里面的人出去要经过这扇门,外边的人进来也要经过这扇门,这扇门就被复用了。
    由于按键GPIO输入 和 外部中断EXTI都使用了同一个GPIO的同一个Pin,可以视为GPIO功能复用,所以选择AFIO时钟。(我是这么理解的,网上没有查到具体的)

    野火论坛202207291659289264..png

   

    和GPIO不同的一点,先要用EXTILineConfig函数选择,告诉程序哪个GPIO的哪个Pin接入中断/事件线,形参/实参如图。

    野火论坛202207292334521526..png



    同样,硬件相关的内容也被写进了头文件的宏定义
    野火论坛202207292332268096..png



    初始化EXTI结构体步骤:
     1)EXTI_Line:用于产生 中断/事件 的线
     2)EXTI_Mode:EXTI模式(中断/事件)
     3)EXTI_Trigger:触发(上升沿/下降沿/上升下降沿)
     4)EXTI_LineCmd:使能或者失能(IMR/EMR)
    用Init函数初始化


    结构体原型在stm32f10x_exti.h可以找到

    野火论坛202207292347197496..png

    模式有两种选择,选择“中断模式”

    野火论坛202207292358436108..png


    触发方式有三种,选择“上升沿触发
    野火论坛202207300000155104..png

   

    一图看懂什么是上升沿什么是下降沿:
    野火论坛202207300003104728..png



    LineCmd只有使能和失能,要使用当然要使能
    野火论坛202207300004551251..png



    EXTI初始化全过程   
    野火论坛202207291651355818..png

    函数写完需要到头文件声明



   

    离配置完EXTI还剩最后一步,配置中断服务函数,在stm32f10x_it.c末尾添加
    这里函数的命名很重要,要和系统配置的一致,不然会出错。
    命名方式在启动文件startup_stm32f10x_hd.s可以找到
    野火论坛202207300013195303..png

    野火论坛202207300015225307..png

   

    开始写中断服务函数
    先判断中断标志位是否为1,为1则触发中断
    野火论坛202207300023461112..png



    野火论坛202207300017098153..png

    定义RESET=0


    if的大括号内填写需要执行的操作,这里填写的是LED灯的反转函数。
    在执行完中断服务函数以后要对标志位清零,不然就陷入死循环了。
    清零函数:

    野火论坛202207300024189840..png
   

    LED取反函数定义:(这部分在led.c内 和之前例程一致)

    野火论坛202207300024488018..png

    野火论坛202207300025231020..png



    中断服务函数整体:

    野火论坛202207300015547753..png



    主函数只需要调用之前写好的GPIO初始化函数和EXTI初始化函数即可
    while 1 为了让其保持运行。

    野火论坛202207300030284152..png

最终代码:
bsp_exti.c:
  1. #include "bsp_exti.h"

  2. static void EXTI_NVIC_Config(void)
  3. {
  4.     NVIC_InitTypeDef  NVIC_InitStruct;
  5.     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  6.     NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;
  7.     NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
  8.     NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
  9.     NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
  10.     NVIC_Init(&NVIC_InitStruct);
  11.    
  12.     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  13.     NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
  14.     NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
  15.     NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
  16.     NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
  17.     NVIC_Init(&NVIC_InitStruct);
  18.    
  19. }

  20. void EXTI_Key_Config(void)
  21. {
  22.     GPIO_InitTypeDef  GPIO_InitStruct;
  23.     EXTI_InitTypeDef  EXTI_InitStruct;
  24.    
  25.     //配置中断优先级
  26.     EXTI_NVIC_Config();
  27.    
  28.     //初始化GPIO
  29.     RCC_APB2PeriphClockCmd(KEY1_INT_GPIO_CLK, ENABLE);
  30.     GPIO_InitStruct.GPIO_Pin = KEY1_INT_GPIO_PIN;
  31.     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  32.     GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStruct);
  33.    
  34.     RCC_APB2PeriphClockCmd(KEY2_INT_GPIO_CLK, ENABLE);
  35.     GPIO_InitStruct.GPIO_Pin = KEY2_INT_GPIO_PIN;
  36.     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  37.     GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStruct);
  38.    
  39.     //初始化EXTI
  40.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
  41.     GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
  42.    
  43.     EXTI_InitStruct.EXTI_Line = EXTI_Line0;
  44.     EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
  45.     EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
  46.     EXTI_InitStruct.EXTI_LineCmd = ENABLE;
  47.     EXTI_Init(&EXTI_InitStruct);
  48.    
  49.     GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource13);
  50.    
  51.     EXTI_InitStruct.EXTI_Line = EXTI_Line13;
  52.     EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
  53.     EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
  54.     EXTI_InitStruct.EXTI_LineCmd = ENABLE;
  55.     EXTI_Init(&EXTI_InitStruct);
  56. }
复制代码


bsp_exti.h:
  1. #ifndef __BSP_EXTI_H
  2. #define __BSP_EXTI_H

  3. #include "stm32f10x.h"

  4. #define KEY1_INT_GPIO_PIN     GPIO_Pin_0
  5. #define KEY1_INT_GPIO_PORT    GPIOA
  6. #define KEY1_INT_GPIO_CLK     RCC_APB2Periph_GPIOA

  7. #define KEY2_INT_GPIO_PIN     GPIO_Pin_13
  8. #define KEY2_INT_GPIO_PORT    GPIOC
  9. #define KEY2_INT_GPIO_CLK     RCC_APB2Periph_GPIOC

  10. #define KEY1_INT_EXTI_Line       EXTI_Line0
  11. #define KEY1_INT_GPIO_PortSrc    GPIO_PortSourceGPIOA
  12. #define KEY1_INT_GPIO_PinSrc     GPIO_PinSource0

  13. #define KEY2_INT_EXTI_Line       EXTI_Line13
  14. #define KEY2_INT_GPIO_PortSrc    GPIO_PortSourceGPIOC
  15. #define KEY2_INT_GPIO_PinSrc     GPIO_PinSource13

  16. void EXTI_Key_Config(void);

  17. #endif /*__BSP_EXTI_H*/
复制代码


stm32f10x_it.c:
  1. /*....此处省略139行...*/

  2. void EXTI0_IRQHandler(void)
  3. {
  4.     if(EXTI_GetITStatus(EXTI_Line0)!=RESET)//检测中断标志位
  5.     {
  6.         LED_TOGGLE(LED_Red);
  7.     }
  8.     EXTI_ClearITPendingBit(EXTI_Line0);//清除中断标志位
  9. }

  10. void EXTI15_10_IRQHandler(void)
  11. {
  12.     if(EXTI_GetITStatus(EXTI_Line13)!=RESET)
  13.     {
  14.         LED_TOGGLE(LED_Green);
  15.     }
  16.     EXTI_ClearITPendingBit(EXTI_Line13);
  17. }

  18. /*如果需要实现别的颜色,直接修改括号里的实参*/
复制代码


bsp_led.c:
  1. #include "bsp_led.h"

  2. void GPIO_LED_Config(uint32_t CLK, uint16_t PIN, GPIO_TypeDef *PORT)
  3. {
  4.     GPIO_InitTypeDef  GPIO_InitStruct;
  5.    
  6.     RCC_APB2PeriphClockCmd(LED_GPIO_CLK, ENABLE);
  7.    
  8.     GPIO_InitStruct.GPIO_Pin = PIN;
  9.     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
  10.     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  11.     GPIO_Init(PORT, &GPIO_InitStruct);
  12.     GPIO_SetBits(PORT,PIN);
  13. }
复制代码


bsp_led.h:
  1. #ifndef __BSP_LED_H
  2. #define __BSP_LED_H

  3. #include "stm32f10x.h"

  4. #define LED_Red          GPIO_Pin_5
  5. #define LED_Green        GPIO_Pin_0
  6. #define LED_Blue         GPIO_Pin_1
  7. #define LED_GPIO_PORT    GPIOB
  8. #define LED_GPIO_CLK     RCC_APB2Periph_GPIOB

  9. #define LED_TOGGLE(color) {LED_GPIO_PORT->ODR ^= color;}

  10. void GPIO_LED_Config(uint32_t CLK, uint16_t PIN, GPIO_TypeDef *PORT);

  11. #endif /*__BSP_LED_H*/
复制代码


main.c:

  1. #include "stm32f10x.h"   // 相当于51单片机中的  #include <reg51.h>
  2. #include "bsp_led.h"
  3. #include "bsp_exti.h"

  4. int main(void)
  5. {
  6.     GPIO_LED_Config(LED_GPIO_CLK, LED_Red|LED_Green|LED_Blue, LED_GPIO_PORT);
  7.     EXTI_Key_Config();
  8.     while(1){}
  9. }
复制代码




回复

使用道具 举报

发表于 2022-8-2 08:43:59 | 显示全部楼层
顶一个~~~~
回复

使用道具 举报

发表于 2022-8-2 08:44:58 | 显示全部楼层
可以
回复

使用道具 举报

发表于 2022-8-11 11:45:30 | 显示全部楼层
小白求问,代码试过两套,一套是跟着b站公开课火哥讲解一行一行打的,另一套用的配套历程,烧录进芯片之后没有其他程序烧录成功的提示音,然后按动案件rgb灯也没有变化,有遇到一样问题的好兄弟吗
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

联系站长|手机版|野火电子官网|野火淘宝店铺|野火电子论坛 ( 粤ICP备14069197号 ) 大学生ARM嵌入式2群

GMT+8, 2024-5-26 02:32 , Processed in 0.041830 second(s), 26 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表