stm32---2.按键触发外部中断

需求:

  • 我们要实现通过按下按键来触发外部中断,然后点亮LED灯。

中断简单来说,CPU在处理一件事的过程中,突然来了其他事件来需要CPU处理,然后CPU放下眼前的事,去处理突然来的事。这些事件就是中断。

CPU还有一个辅助人员:NVIC,可以帮助CPU进行筛选,根据优先级进行排序,选出一事件,然后让CPU处理。

一、以寄存器的方式实现

(1)时钟配置

  • 在我使用的开发板上,PF8和PF10控制Key1和Key3,且这两个按键控制逻辑刚好相反。分别按下时,输出的电平信号相反,在后续的对应步骤中,在详细说明。
  • 也就是意味着,我们现在需要打开PF------GPIOF的时钟。
    除了GPIOF的时钟,还有AFIO,什么是AFIO?为什么需要打开它的时钟?
    • 我们需要从中断来源的分类开始说起:
      • 内部其他控件
      • 片上外设
      • 外部中断

我们使用按键触发的是外部中断,不同于另外两种中断,另外两种中断,是可以直接与NVIC建立联系,而外部中断不可以。

这就好比CPU是皇帝,NVIC是宰相,内部其他控件是内廷近臣,片上外设是朝廷六部,它们有什么事,可以直接向宰相奏折。

而外部中断好比地方/外国使臣,它们肯定不能直接与宰相奏折呗!肯定需要进行一个机构进行统一管理(AFIO) ,最终在由另外一个机构与NVIC建立联系(EXIT)

外部中断都是复用GPIO口,我使用的stm32包含GPIOA~GPIOG,一共有112根引脚,倘若这些引脚尝试的外部中断都与NVIC建立联系,那硬件电路设计不得非常复杂,而且占用空间吗?

所以通过AFIO将所有GPIO端口,相同的序号的引脚映射成一根,总数112根的引脚线被压缩成16根:EXIT0~EXIT15

  • 在实际使用的使用,只需要关注使用的哪个GPIO,哪个引脚就可以了。
c 复制代码
RCC->APB2ENR |=RCC_APB2ENR_IOPFEN;
RCC->APB2ENR |=RCC_APB2ENR_AFIOEN;

(2)配置GPIO工作模式

  • 由于此时stm32需要获取来自外部中断的信息,所以需要配置为输入模式,以及上/下拉的具体模式
c 复制代码
//输入模式--00
GPIOF->CRH&=~GPIO_CRH_MODE10;
GPIOF->CRH&=~GPIO_CRH_MODE8;

//上下拉输入模式--10
GPIOF->CRH|=GPIO_CRH_CNF10_1;
GPIOF->CRH&=~GPIO_CRH_CNF10_0;
GPIOF->CRH|=GPIO_CRH_CNF8_1;
GPIOF->CRH&=~GPIO_CRH_CNF8_0;
  • 当我们完成了工作模式的配置以后,还需要给这个引脚设置一个默认的输入值,这个输入值如何设置,得参考按键的原理图:
    • PF8连接着的K1,当我们按下的时候,这个线路就会导通,接地,因此就会输入低电平给引脚。所以我们需要设置PF8默认输出高电平,这样当按下的时候,就会由高变低 ,出现一个下降沿,可以作为促发中断的条件。
    • 同理PF10连接着的K3,当我们按下的时候,这个线路就会导通,接地,因此就会输入高电平给引脚。所以我们需要设置PF10默认输出低电平,这样当按下的时候,就会由低变高 ,出现一个上升沿,可以作为促发中断的条件。

中断触发的条件有三种:

  • 上升沿触发
  • 下降沿触发
  • 上升沿和下降沿都触发
c 复制代码
//以上是包含,上下拉两种模式,我们要选择其中一种
GPIOF->IDR|=GPIO_IDR_IDR8;//默认上拉
GPIOF->IDR&=~GPIO_IDR_IDR10;//即默认是下拉

(3)配置AFIO 配置引脚复用选择

我们已经知道,AFIO将GPIOA~GPIOF的112根引脚映射成了最终的16根引脚,从EXIT0 ~ EXIT15。而我们使用的PF10和PF8,对应的就是EXIT8和EXIT10。

实际的寄存器中是将这16根引脚的配置,划分到了四个寄存器中:

我们要使用的10和8号引脚就属于EXTICR这个寄存器中。

在完成了查找引脚的工作以后,我们还需要说明这个引脚是属于哪一个GPIO端口的,我们可以看到每一个EXTIx中都有四位来表示是哪一个GPIO端口的,自然是PF:0101;

c 复制代码
 //可以先清零对应位
AFIO->EXTICR[2]&=~AFIO_EXTICR3_EXTI10;
AFIO->EXTICR[2]&=~AFIO_EXTICR3_EXTI8;
//配置对应位
AFIO->EXTICR[2]|=AFIO_EXTICR3_EXTI10_PF;
AFIO->EXTICR[2]|=AFIO_EXTICR3_EXTI8_PF;

(4)EXIT

现在我们可以来配置这个与NVIC建立联系的寄存器了。

如何建立联系?

自然很好理解,换一个问法就是如何触发中断?

有三种触发中断的方式,

  • 对于PF10我们采用的是上升沿的触发方式
  • 对于PF8我们采用的是下降沿的触发方式

除了这个以外,还有需要对事件屏蔽寄存器进行配置,我们需要让NVIC看到这个中断事件。因此,

将对应位给它置1.

c 复制代码
//4.配置EXTI
//首先上升沿触发有效
EXTI->RTSR|=EXTI_RTSR_TR10;

EXTI->RTSR|=EXTI_FTSR_TR8;

//还配置屏蔽寄存器---是不是应该打开 0是屏蔽 1是打开
EXTI->IMR|=EXTI_IMR_MR10;
EXTI->IMR|=EXTI_IMR_MR8;

(5)NVIC的配置

最后就需要我们的大宰相NVIC,来进行最终的配置,前面我们已经提到了,它是帮助CPU筛选要处理的中断程序的:

  • 主要是设置优先级类型
  • 设置优先级
  • 最后打开中断使能,等待最终中断的产生
c 复制代码
//5.配置NVIC
//5.1配置优先级的类型,有五种
NVIC_SetPriorityGrouping(3);  //全部是抢占优先级
//配置 哪个中断的优先级
NVIC_SetPriority(EXTI15_10_IRQn,3);
NVIC_SetPriority(EXTI9_5_IRQn,3);
//中断使能
NVIC_EnableIRQ(EXTI15_10_IRQn);
NVIC_EnableIRQ(EXTI9_5_IRQn);

(6)完善中断处理程序

  • 通常当产生多个中断事件,这些事件都会交付给CPU处理,没被CPU处理的事件,就会处于挂起状态。

  • 当我们进入中断程序要处理该事件时,第一件事就是取消该事件的挂起状态。

  • 然后在判断是否真正的产生对应信号,比如我们设置的是上升沿触发,那我们就还需要读取对应引脚是否处于高电平。

  • 最后就可以实现一些功能了

注意:

  • 中断程序,尽量要保证简短,不能太过冗余
  • 尽量不要写延时函数,循环语句之类的。
  • 我们通常会定义一个全局的标志位,在中断中置1或0。然后在主函数中,根据这个标志位值,执行一些相应的操作

中断函数名都是固定的,我们可以去中断向量表 中去寻找:

c 复制代码
//中断处理程序  中断向量表中寻找
void  EXTI15_10_IRQHandler(void)
{
    //进入中断程序以后,需要将挂起位清零
    //写1,清零
    EXTI->PR|=EXTI_PR_PR10;
   
    if((GPIOF->IDR&GPIO_IDR_IDR10)!=0)
    {
    		//翻转LED
        Int_LED_Toggle(LED1);
    }
}

void  EXTI9_5_IRQHandler(void)
{
    //进入中断程序以后,需要将挂起位清零
    //写1,清零
    EXTI->PR|=EXTI_PR_PR8;

    if((GPIOF->IDR&GPIO_IDR_IDR8)!=1)
    {
        Int_LED_Toggle(LED2);
    }
}    

二、以HAL的方式实现

1.前置知识说明

这部分内容,我将带着大家来梳理一下通过HAL库的方式实现按键触发外部中断控制LED灯的亮灭。

首先,我们来看原理图,看一下我们需要的外设信息:

  • 我们要使用KEY1和KEY3,来控制两个LED灯的亮灭,这是为什么呢?
  • 因为这两个按键的控制逻辑恰好相反,我们可以来感受不同逻辑下,实现相同的功能。
  • 首先是KEY1,可以看到它外接GND,这就意味着,当我们按下按键的时候,整个线路导通,PF8就会输入一个低电平给单片机。
  • 而KEY2的逻辑,就相反,它外接VCC,所以导通的时候,就会输入给PF10高电平。
  • 那如何通过按键来触发外部中断呢?
  • 在此之前,我们已经介绍了触发外部中断的三种情况:
    • 上升沿触发
    • 下降沿触发
    • 上升沿和下降沿都会触发

因此,我们可以给PF8和PF10设置一个初始值;

  • KEY1按下之后,就是一个低电平,所以我们让PF8默认输入高电平,按下之后,电平由高变低,就形成了一个下降沿。
  • 同理PF10就默认输出低电平,按下按键之后就变成高电平,电平由低变高,就形成了一个上升沿。

2.Cubemx的配置

现在我们可以打开CubeMX来进行一个图像化的配置:

  1. 首先选择我们要使用的引脚,进行模式的配置
  • 外部中断是复用引脚,所以在给引脚进行配置的时候,有配置的选项:EXITx

2.引脚具体模式的配置以及打开NVIC的使能

  • 如图所呈现的就是PF8配置情况,下降沿触发,默认输入高电平。
  • 打开这个外部中断使能

3.优先级类型选择和优先级的配置

  • 我们只对抢占优先级进行了一个配置。设置为3

常规配置:

  • 晶振类型选择
  • dubug选择单总线one-wire模式
  • 时钟树,高速外部时钟8Mhz,经过9倍频为72Mhz,然后还需要给APB1进行分频。
  • 工程管理的配置:
    • 命名
    • 选择IDE
    • 保留必要文件和生成外设的.c和.h文件
      (完整的图示说明可以见我写的第一篇)

3.在VScode中实现


  • 这里面包含了各种中断,找到我们要使用的两个中断
  • 我们可以看到这两个中断调用的都是同一个函数,只是传递的参数不一样。

我们鼠标移动到指定位置,按住Ctrl 点击,然后就可以跳转


  • 在这里,代码已经帮助我们完成了产生中断进入中断程序是,挂起状态清除的工作。
  • 然后可以看到调用了一个回调函数。

  • 这是一个弱实现的一个函数,因此我们就可以对它进行一个重写,写我们目标的中断程序。

中断程序要保证简短,尽量不要写循环,延时之类的,我们可以给我们的按键定义两个静态的状态全局变量,当按下按键的时候,触发外部中断,在中断程序中,将其置1.然后再主函数的死循环中,检测这个状态变量的变化,执行相应的操作。

c 复制代码
static uint8_t KEY1_State=0;
static uint8_t KEY2_State=0;
c 复制代码
while (1)
  {
    /* USER CODE END WHILE */
    if(HAL_GPIO_ReadPin(GPIOF,GPIO_PIN_10)==1&&KEY2_State==1)
    {
      HAL_Delay(20);
      HAL_GPIO_TogglePin(GPIOA,LED2_Pin);
      KEY2_State=0;

    }

     if(HAL_GPIO_ReadPin(GPIOF,GPIO_PIN_8)==0&&KEY1_State==1)
    {
      HAL_Delay(20);
      HAL_GPIO_TogglePin(GPIOA,LED1_Pin);
      KEY1_State=0;

    }
c 复制代码
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  if(GPIO_Pin==KEY1_Pin)
  {
    KEY1_State=1;
  }
  if(GPIO_Pin==KEY2_Pin)
  {
    KEY2_State=1;
  }

}
相关推荐
rit84324994 小时前
STM32F4 USB Host 功能实现
stm32·单片机·嵌入式硬件
金戈鐡馬4 小时前
定时器+中断优化单总线通信
stm32·单片机·嵌入式硬件
cici158745 小时前
STM32 + VS1003/VS1053 MP3播放器SD卡读取程序
stm32·单片机·嵌入式硬件
xiangw@GZ5 小时前
DDR3 颗粒信号定义解析
单片机·嵌入式硬件
小+不通文墨5 小时前
在树莓派中部署emqx
经验分享·笔记·单片机·学习
Deitymoon5 小时前
STM32——oled显示字符串和数字
stm32·单片机·嵌入式硬件
深圳市晨芯阳科技有限公司6 小时前
带延时功能的电压检测系列晨芯阳HC809
单片机·嵌入式硬件·电源芯片·深圳市晨芯阳科技有限公司
xiangw@GZ6 小时前
DDR2 / DDR3 / DDR4 颗粒信号差异对照表
单片机·嵌入式硬件
科芯创展6 小时前
1A,60VIN,1MHz,XZ4116,降压恒流LED驱动芯片 输入电压:5V-60V
stm32·单片机·嵌入式硬件