【STM32教程】第四章 STM32的外部中断EXTI

案例代码及相关资料下载链接:

链接:https://pan.baidu.com/s/1hsIibEmsB91xFclJd-YTYA?pwd=jauj

提取码:jauj

1 中断系统

1.1 中断的概念

中断系统的定义:中断是指在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行。

通俗来讲中断就是主程序在正常运行时,出现了中断触发条件,而中断触发的条件有很多:对于外部中断来讲可能是引脚的电平发生变化;对于定时器来讲可能是定时的时间到了;对于串口通信来讲可能是接收到了数据,当以上事件发生时,程序的执行就会从主程序中跳到中断程序中来执行中断程序,直到中断程序执行完成之后才会重新执行主程序。而使用中断系统最大的作用就是能够极大的提升程序的运行效率。

中断优先级:中断优先级建立在有多个中断源的时候,当有多个中断源出现时,CPU就要考虑率先执行哪个中断,中断就像做一件事的轻重比例,当有很多事情堆到眼前时候,通常先做情况最紧急的,再做稍次一点的,这样就会给中断确立执行的先后顺序,而多个中断的执行顺序的先后就被叫做中断优先级。

中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回,而中断嵌套的执行通常是由中断优先级来确定的。中断嵌套可以理解将一堆要做的事慢慢一个一个抛给你,而你要根据每个事情的紧急程度来决定在已经有的事情里先做哪一个。

中断执行的流程如下图所示:

在我们学习的STM32 中有68个可屏蔽中断通道(中断源),包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设(68个中断源是F1系列中最多的,而对于某个具体型号可能并没有这么多,所以中断源的数量以单片机的数据手册为准),这些中断源都使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级。(具体中断源如下表所示灰色部分为内核中断,不是灰色部分的为STM32的外设中断,具体图标手册中有)

NVIC结构:

基本结构图如下图所示:

NVIC的全称是Nested vectoredinterrupt controller,即嵌套向量中断控制器,在STM32中NVIC用来统一分配中断优先级和管理中断的,他是单片机内核外设,是用来帮助CPU处理中断分配和配置中断优先级的地方(为了让CPU高速运行),因为NVIC有多个输入口,所以能将单片机的外设中断都来接到NVIC上,而NVIC只有一个输出口这样就能通过对中断优先级的设置,来输出中断优先级高的,然后交给CPU让CPU优先执行中断优先级高的程序,这就是NVIC的基本作用。

NVIC优先级分组:

为了处理不同形式的优先级,NVIC会有优先级进行分组,而NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级,其属性编号越小,表明它的优先级别越高。抢占优先级,是指打断其他中断的属性,即因为具有这个属性会出现嵌套中断(在执行中断服务函数A 的过程中被中断B 打断,执行完中断服务函数B 再继续执行中断服务函数A),抢占属性由NVIC 的参数配置。而响应属性则应用在抢占属性相同的情况下,当两个中断向量的抢占优先级相同时,如果两个中断同时到达, 则先处理响应优先级高的中断,抢占优先级和响应优先级均相同的按中断号排队,数字小的先响应。(中断号是指上图中优先级的序号)

优先级的分组方式如下图所示:

注意在配置好NVIC的分组方式之后,配置优先级时,取值不能超过对应NVIC分组的取值范围。

1.2 外部中断的简介与配置

外部中断简介:

EXTI(Extern Interrupt)外部中断,EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序。

其触发方式有上升沿,下降沿,双边沿,软件触发四种。EXTI支持所有的GPIO口都可以触发中断,但是相同的Pin_不可以同时触发中断,例如PA1与PB1不能同时触发中断。EXTI有20个通道数,说明EXTI可以有20个中断源,其中包括16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤

醒;而EXTI响应方式有中断响应(申请中断让CPU执行中断函数),事件响应(中断响应时,外部中断信号不会传向CPU而是给到单片机外设,让单片机外设响应一个事件)。

外部中断的基本结构:

结构图如下图所示:

最左边是GPIO口的外设,由于每个GPIO都有16个通道当每个GPIO的引脚都占用一个那么EXTI的通道就会不够用,为了让相同的Pin_不同时触发中断,所以就有AFIO用来进行中断引脚的选择,相当于一个数据选择器,他可以从前面N个GPIO的16个引脚中选择其中一个接到后面的EXTI通道里面,所以就会有上面说的相同的Pin_不可以同时触发中断,因为对于相同的Pin_在经过AFIO之后只会有一个Pin_接到后面的EXTI通道上,例如:PA0,PB0,PC0,PD0,只有一个Pin_0可以接到EXTI的通道0上。而以上GPIO引脚通过EXTI上之后,就会分为两种输出,一种接到NVIC上,一种接到单片机外设上(也是对应的事件响应),这里EXTI的输出本来为20个但是由于ST公司想要节省引脚口,所以EXTI4之后的中断被五个为一组分在同一个通道里,所以EXTI5~9会触发同一个中断函数,EXTI10~15触发同一个中断函数,但是基于Hal库开发的STM32将这些中断函数写在了一起,所以所有中断通道都可以写在同一个中断函数里面,但是标准库必须根据不同的中断通道来判断到底能不能写在一起。

AFIO口的结构图如下:

此图原理就是将输入的不同GPIO相同的Pin_通过数据选择器来进行选择,最后只选择一个Pin_来接到EXTI通道上,具体如何选择就是通过配置AFIO_EXTRCRX寄存器的EXTIx位来进行选择,每个位代表不同的GPIO,这样就能以确定的位来确定是哪个GPIO的引脚会接到EXTIX的通道上面。

AFIO主要用于引脚复用功能的选择和重定义(数据选择器),在STM32中,AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择。

EXTI结构图

电平经过AFIO之后进入EXTI,首先要对该中断源的触发方式进行选择(上升沿触发/下降沿触发/双边沿触发)进入或门的输入,这里用或门进行输入的原因是因为触发方式也可以选择是软件触发,然后就会分成两路,接到NVIC中断控制器的是触发中断的,而接到脉冲发生器的是用来触发事件的。

先看触发中断这一路的,这里接的请求挂起寄存器相当于中断标志位,通过读取这个寄存器判断是哪个通道发出中断请求,如果给请求挂起寄存器置1,那么就会和中断屏蔽寄存器进入一个与门,然后进入NVIC中断控制器,这里与门和这个中断屏蔽寄存器相当于一个开关的作用,因为与门是如果输入有一个为0,那么输出一定为0,所以会通过控制中断屏蔽寄存器来控制是否响应中断。

事件触发这一路与中断触发大同小异,这里事件屏蔽寄存器和与门也充当开关的作用,通过与门之后接一个脉冲发生器,这个脉冲发生器的作用就是给一个电平脉冲是用来触发其他外设的。

上图中画斜杠写20的表示这里可以接20根线,连接20个通道,而APB总线和外设接口,单片机对于寄存器的访问就是借助APB总线来进行。

2实验演示

2.1 旋转编码器的简介

旋转编码器:用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向,通常有以下几种类型:机械触点式/霍尔传感器式/光栅式。

实物图如下:

左边第一个不能判断不能判断旋转方向,只能测位置和速度所以不经常使用,而剩下的就是我们本开发板自带的旋转编码器,由第三个拆解图可得他内部是通过金属触点来进行通断的,所以为机械式编码器,有左右两部分开关触点,其中左右两部分内侧较细的触点是连接在引脚B上,而剩下外侧的触点分别连在引脚A和C上,而中间的圆点为一个普通的按键,然后来看这个编码盘也是一系列像光栅一样的东西,但也是金属触点,在旋转时依次接通和断开两边的触点,而金属盘的位置也是经过设计可以让两侧触点的通断有九十度的相位差,

具体输出波形如下方两图所示(上图为正转时,下图为反转时)以下这样的正交波形就可以用来区别方向。

硬件电路图如下

2.2 对射式红外传感器计次

接线图如下:

实物图如下:

实验内容

本实验是对对射式红外传感的传感次数进行计次,通过用遮光片遮挡来对红外传感器传输进行计次,然后通过OLED屏幕来显示传感次数。并且对于中断触发方式的不同,计次方式也不同。

首先,配置STMCube MX

将GPIO口用来实现外部中断的配置为中断模式即PB14,由于OLED也要继续使用所以对OLED的引脚继续配置为输出模式即可:

在NVIC中勾选EXTI line[15:10] interrupts

对NVIC的中断分组方式选择方式2(哪一个随自己选择)

在GPIO这里对中断触发方式进行选择:

其他配置不变,生成工程即可。

代码部分:

HAL库关于外部中断的函数只有一个:

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);

具体模板用法如下:

HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) 
{

   if(GPIO_Pin==GPIO_PIN_1)//判断是哪个中断源

   {

   //执行中断函数

   __HAL_GPIO_EXTI_CLEAR_IT ( GPIO_Pin);

//中断结束标志必须有不然会卡在中断中不能进入主程序

   }

//Hal库中所有的中断函数都在这一个函数中进行

   if(GPIO_Pin==GPIO_PIN_3)

   {

   }
}

CountSensor部分代码

#include "main.h"
#include "gpio.h"

uint16_t Consensor_count;

uint16_t  CountSensor_Get(void)
{
   return Consensor_count;
}

/**
  * 函数功能: 外部中断回调函数
  * 输入参数: GPIO_Pin:中断引脚
  * 返 回 值: 无
  * 说    明: 无
  */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  if(GPIO_Pin==CountSensor_Pin)
  {
    if(HAL_GPIO_ReadPin(CountSensor_GPIO_Port,CountSensor_Pin)== 1  )//下降沿有效计次即可
    {
      Consensor_count++;
    }
    __HAL_GPIO_EXTI_CLEAR_IT ( CountSensor_Pin);
  }
}

主函数部分代码

OLED_Init ();
OLED_ShowString (1,1,"Count:");
while (1)
{
  OLED_ShowNum (1,7,CountSensor_Get (),5);//如果有新的下降沿出现直接覆盖上一次的数字
}

2.3 旋转编码计次

接线图如下图所示:

实验内容:

对旋转编码器旋转进行计次,正转加加爱,反转减减,计得次数通过OLED来不断刷新。

STM32CubeMX配置:

对旋转编码器连接的引脚PB0与PB1配置为外部中断模式,给OLED连接的引脚PB8与PB9配置为输出模式,在NVIC选择分组方式,然后在下面勾选

EXTI line0 interrupt EXTI line1 interrupt 。

然后在GPIO里配置IO口的触发中断方式与输出模式,根据自身习惯来对IO口进行宏定义。

配置完成后,生成工程。

代码部分:

Encoder部分代码:

#include "main.h"
#include "gpio.h"

uint16_t Encoder_Count;

uint16_t  Encoder_Get(void)
{
   uint16_t Temp;
   Temp = Encoder_Count ;
   Encoder_Count = 0;
   return Temp ;
}

/**
  * 函数功能: 外部中断回调函数
  * 输入参数: GPIO_Pin:中断引脚
  * 返 回 值: 无
  * 说    明: 无
  */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  if(GPIO_Pin==Count_Increase_Pin)//读取中断源
  {
   if(HAL_GPIO_ReadPin(Count_Increase_GPIO_Port  , Count_Increase_Pin)==0 )
   {
   if(HAL_GPIO_ReadPin(Count_Decrease_GPIO_Port  , Count_Decrease_Pin)== 0 )//旋转编码器向左转时
      Encoder_Count--;
    }
    __HAL_GPIO_EXTI_CLEAR_IT ( Count_Increase_Pin);//消除中断标志,由中断进入主函数
  }
  else if(GPIO_Pin==Count_Decrease_Pin)
  {
     if(HAL_GPIO_ReadPin(Count_Decrease_GPIO_Port  , Count_Decrease_Pin)== 0)
    {
     if(HAL_GPIO_ReadPin(Count_Increase_GPIO_Port  , Count_Increase_Pin)== 0 )//旋转编码器向右转时
     Encoder_Count++;
    }
    __HAL_GPIO_EXTI_CLEAR_IT ( Count_Decrease_Pin);//消除中断标志,由中断进入主函数
}

主函数部分代码:

in16_t Num;
OLED_Init();
OLED_ShowString (1,1,"Count:");

while (1)
{
   /* USER CODE END WHILE */
   /* USER CODE BEGIN 3 */
   Num+=Encoder_Get();//时刻刷新计次
   OLED_ShowSignedNum(1,7,Num,5);
 }
}
相关推荐
LateBloomer77725 分钟前
FreeRTOS——信号量
笔记·stm32·学习·freertos
wenchm1 小时前
细说STM32单片机DMA中断收发RTC实时时间并改善其鲁棒性的另一种方法
stm32·单片机·嵌入式硬件
编码追梦人2 小时前
如何实现单片机的安全启动和安全固件更新
单片机
电子工程师UP学堂2 小时前
电子应用设计方案-16:智能闹钟系统方案设计
单片机·嵌入式硬件
飞凌嵌入式3 小时前
飞凌嵌入式T113-i开发板RISC-V核的实时应用方案
人工智能·嵌入式硬件·嵌入式·risc-v·飞凌嵌入式
blessing。。4 小时前
I2C学习
linux·单片机·嵌入式硬件·嵌入式
嵌新程5 小时前
day03(单片机高级)RTOS
stm32·单片机·嵌入式硬件·freertos·rtos·u575
Lin2012305 小时前
STM32 Keil5 attribute 关键字的用法
stm32·单片机·嵌入式硬件
电工小王(全国可飞)6 小时前
STM32 RAM在Memory Map中被分为3个区域
stm32·单片机·嵌入式硬件
maxiumII6 小时前
Diving into the STM32 HAL-----DAC笔记
笔记·stm32·嵌入式硬件