STM32CubeMX中的外部中断简介

建议提前学习:使用STM32CubeMX实现按下按键,电平反转STM32中断与事件的理解

目录

EXTI

中断

中断的概念

抢占优先级与响应优先级

 中断分组

事件

上升沿,下降沿以及双边沿触发

上升沿,下降沿以及双边沿的概念

上升沿,下降沿以及双边沿触发

EXTI中断/事件线

中断服务函数

NVIC

中断的流程框图

STM32CubeMX配置

电路图分析

引脚配置

K2中断配置 

K1按键下拉输入

LED1和LED2配置为输出

 生成文件

Keil编写程序 

__HAL_GPIO_EXTI_GET_IT()

作用

__HAL_GPIO_EXTI_GET_IT()内部实现

使用说明

__HAL_GPIO_EXTI_CLEAR_IT()

作用

__HAL_GPIO_EXTI_CLEAR_IT()内部实现

使用

中断分组的函数在哪里

中断函数

哪里可以查看中断服务函数名字

STM32CubeMX为我们生成的中断服务函数在哪里

代码

宏定义部分

while(1)中代码

中断服务函数


EXTI

STM32的EXTI有事件与中断两种形式。

推荐一个文档:事件与中断区别

中断:一定会有一个中断函数需要执行。有CPU的参与。

事件:事件可以没有函数,他不需要CPU参与。事件可以触发一些操作,比如触发DMA,触发ADC采样等等。

中断

中断的概念

首先,我们需要了解什么是中断。我们都知道,程序都是按照我们编写的顺序进行的,从上到下依次进行。但是很显然,这样会导致程序运行很死板,无法处理突发事件。于是就有了中断。

CPU你可以理解为一个非常听话的人,他会完美的按照你的指令执行任务,但是又过于死板。举个例子:

(1)假设你有一个小孩。你跟小孩说让他做完作业再出房间看电视。

(2)按照正常情况,他是先做完作业,然后出房间,最后看电视。但是你这个时候有紧急事情要处理,要出去有点事要处理,但是现在你在厨房做菜。如果你直接就折磨走了,显然会造成事故。怎么办呢?

(3)于是你就让小孩停止做作业,出来做饭。但是我说了,小孩认为做饭不是他的事情,不听你的。于是你拿出皮带,强行要求小孩做饭。(有点小残忍呀,嘿嘿)

这个打断小孩做作业,让他做饭,就叫做中断

(4)然后小孩做完饭做了一半,你回来了。就让小孩再去做作业?你想吃饭时间再让他去做作业,很显然太残忍。于是你让他先看电视去休息。

这个打断小孩做饭(第一层中断),他还没做完饭(第一层中断还没有完成)就让他去看电视(第二层中断),叫做中断嵌套

抢占优先级与响应优先级

一个中断响应有抢占优先级与响应优先级。有什么区别呢?

抢占优先级:(概念)抢占优先级就和他的名字一样。很霸道,如果我的抢占优先级比你高,那么我就打断你现在的行为,让你做其他事情。不需要等待正在执行的中断执行完

(举例)依旧拿上面这个小孩作为例子,打断小孩做饭(第一层中断),他还没做完饭就让他去看电视(第二层中断),这个叫做中断嵌套,同时也体现了,第二层中断的抢占优先级高于第一层中断。

响应优先级:(概念)响应优先级就是等待响应,他不会突然打断现在执行的中断。如果有一个中断正在执行,这个时候先后来了两个中断,这三个中断的抢占优先级是一样的。那么就先等待第一个正在执行的中断执行完,然后判断后来的两个中断响应优先级,响应优先级高的任务先执行。

(举例)假设你现在下班了,同时有两个选择,第一个是打一小时游戏,第二个是陪女友逛街。显然打游戏的优先级高于陪女友逛街。那么你就会先打一小时游戏,等一个小时之后再去陪女友逛街。这种先执行一个任务,再执行另外一个任务,这个就体现了响应优先级。

 如果来了两个中断,首先我们判断它的抢占优先级(也可以叫做主优先级),如果抢占优先级一致。那么才开始判断响应优先级(也可以叫做从优先级)。

需要注意的一点是,抢占优先级0比抢占优先级1要大,响应优先级0比响应优先级1大。

 中断分组

stm32的中断是存在分组的,有5种分组方式。如果我们选择了分组2,那么抢占优先级只有0-3,不存在抢占优先级4。

事件

推荐博客:STM32中断与事件的理解

依旧是上面这个例子

现在小孩(CPU)在看电视,但是突然来了一个上门快递。你(硬件)去开门签收快递。

这个过程小孩没有参与(CPU),全过程交给了你(硬件)。这个叫做事件

 我暂时还没用过事件,我所能想到的是通过事件触发ADC,然后利用DMA将数据存入SRAM。

此章节只讲中断!!!

 

上升沿,下降沿以及双边沿触发

上升沿,下降沿以及双边沿的概念

上升沿:低电平到高电平的这个过程

下降沿:高电平到低电平的这个过程

双边沿:上升沿+下降沿

上升沿,下降沿以及双边沿触发

这个触发表示,发生上升沿或者下降沿才会触发中断。具体是怎么触发,这个需要我们自己配置。

EXTI中断/事件线

EXTI 有 20 个中断/事件线,每个 GPIO 都可以被设置为输入线,占用 EXTI0 至 EXTI15,还有另外4根用于特定的外设事件。

我们需要注意一个点,就是如果我们PA0设置为外部中断引脚,那么PB0就不能为中断引脚。因为所有PIN0都公用同一根中断线

 

中断服务函数

外部中断虽然有16根中断,但是外部中断服务函数却只有7个。EXTI0—EXTI4五个中断线分别各占一个中断服务函数。但是EXTI5—EXTI9这5个中断线公用一个中断服务函数,EXTI10—EXTI15这6个中断线公用一个中断服务函数。

NVIC

所有中断最后都是由NVIC才处理,这里涉及到内核相关知识了,属于我的知识盲区。感兴趣的可以去看CM3内核的NVIC部分内容。

总结:记住所有中断由NVIC处理即可,他与内核有关

中断的流程框图

不知道与或非门概念的,看:二值逻辑变量与基本逻辑运算

使用中断之前,我建议先了解一下STM32F103的中断机制,以上升沿触发为例,如下

看不懂没关系,接着往下看,你记住那几个函数什么时候使用就行。但是有基础的建议看完,之后接触其他型号的单片机上手更快

(1)边缘检测电路检测输入线,如果检测到上升沿,上升沿触发选择寄存器将会置1。这里需要注意,我们这里使用的是外部中断,非软件中断,所以软件中断事件寄存器为0。(软件中断就是定时器中断,ADC中断等等)但是这是或门,所以有1出1。2处输出为1。

(2)因为我们是中断,而非事件,所以事件屏蔽寄存器位0。与门是有0出0,所以与门2输出为0。

(3)因为我们使用的是中断,所以中断屏蔽寄存器要设置为1。(这个在GPIO初始化的时候就已经设置好了)

(4)因为断屏蔽寄存器已经为1了。现在我们2处的或门输出为1,那么与门1输出1给NVIC,这个时候将会触发中断。

STM32CubeMX配置

实验要体现中断,我打算利用,按下按键K1,绿灯电平反转,然后while等待。这个时候将K2配置为中断,按下K2,蓝灯和绿灯电平都反转。(注意,如果K2不是中断,K1没有松手,无论你怎么按K2都没有现象改变呀。因为会堵死在while循环里面,一直等待你K1松手)

电路图分析

首先我们看电路图都知道按下按键为高电平。那么我们可以设置K2为上升沿触发。K1为下拉输入。 

引脚配置

K2中断配置 

(1)第一步,配置GPIO模式

配置中断的时候,GPIO mode里面有6个选项

(1)前面三个表示中断,后面三个是事件。 

(2)第一个是上升沿触发中断,第二个是下升沿触发中断,第三个是双边升沿触发中断

(3)第四个是上升沿触发事件,第五个是下升沿触发事件,第六个是双边升沿触发事件

然后配置为下拉。注意

(1)在标准库里面,外部中断要配置成输入。这里HAL库虽然没说GPIO是输出还是输入,但是我们需要知道,外部中断是要配置成输入

(2)

如果我们是上升沿触发,输入要配置为下拉或者无上下拉。

如果我们是下升沿触发,输入要配置为上拉或者无上下拉。

如果我们是双边升沿触发,上下拉配置随便。

(2)第二步,配置NVIC(这个专门负责中断的)

∆ 这里我们需要配置中断分组(这个随便你自己怎么配置,只要注意抢占优先级和响应优先级符合中断分组就行)

∆ 建议把按照抢占优先级和响应排序打开,这样我们就能直观的知道中断顺序。(这里需要注意一个点,我们只开启了PC13的中断,但是怎么会有折磨多中断呢?原因是因为其他中断是系统中断,不需要理会)

∆ 使能中断需要勾选,如果你没有勾选,中断也就不会产生。

∆ 5和6分别是配置抢占优先级和响应优先级的。

这里有一个东西需要注意!!!我是写写程序验证实验的时候才发现的!!!

Time base: System tick timer的抢占优先级要高于(不能等于)外部中断的优先级。否则会出现在外部中断中无法调用延时函数的情况。(HAL_Delay()这个函数是滴答定时器,使用的是滴答定时器中断实现的。只需了解)

K1按键下拉输入

LED1和LED2配置为输出

因为LED是外接高电平,所以引脚初始化为高电平输出。否则你刚开机,灯就已经亮了。

GPIO配置

 生成文件

如果还不会生成文件的,看:STM32CubeMX新建工程并点亮一个LED;的文件生成部分

Keil编写程序 

我们先介绍它的函数使用,再进行程序编写吧。此处我们只会讲解需要用到的函数,其他的不怎么常用的,等各位入门了自己可以研究。

这里会涉及到中断的流程框图的内容,如果没看懂的人,直接看函数的使用说明即可。

__HAL_GPIO_EXTI_GET_IT()

作用

(1)获取中断线的是否发起中断请求。这个是什么意思呢?

(2)上面我说了,EXTI0—EXTI4五个中断线分别各占一个中断服务函数。但是EXTI5—EXTI9这5个中断线公用一个中断服务函数。那么就会出现一个问题,现在我PA5和PA7两个引脚都有中断,当其中一个引脚发生中断,他们会同时进入相同的中断服务函数EXTI9_5_IRQHandler。

(3)那么这个时候__HAL_GPIO_EXTI_GET_IT()这个函数的作用就有了,我们可以在EXTI9_5_IRQHandler这个中断函数里面先进行一个if判断,是哪一个中断线发生中断请求。

__HAL_GPIO_EXTI_GET_IT()内部实现

 __HAL_GPIO_EXTI_GET_IT()其实不是一个函数,而是一个宏定义。如下

/**
  * @brief  Checks whether the specified EXTI line is asserted or not.
  * @param  __EXTI_LINE__: specifies the EXTI line to check.
  *          This parameter can be GPIO_PIN_x where x can be(0..15)
  * @retval The new state of __EXTI_LINE__ (SET or RESET).
  */
#define __HAL_GPIO_EXTI_GET_IT(__EXTI_LINE__) (EXTI->PR & (__EXTI_LINE__))

(1)我们看这个宏定义,发现其实它是对PR这一个寄存器进行操作,那么我们可以尝试翻阅STM32F103数据手册。发现PR这一个寄存器可以查看中断是否被挂起。让我们让PR寄存器&(有0 出0)对应的PIN脚(比如GPIO_PIN_13就是0x2000,正好对应PR13),那么就可以获取这一个中断线是否有中断请求。(这里看不懂,直接看后面,这一部分是给有基础的人看的。我最后面会告诉你们什么时候需要使用,记住即可。)

(2)这个PR就是挂起寄存器,如果外部来了符合我们设置的脉冲,那么这个寄存器将会被挂起,也就是输出为1。(结合上面那个流程图)

这个PR有32bit,但是只有前20bit被使用,这一一对应了20根中断/事件线。如果是中断/事件线13发生中断响应,那么PR13将会被挂起(也就是1)

(3)我们可以通过读取PR,来确认到底是拿一根中断/事件线发生了中断响应。这样就很好的处理了EXTI5—EXTI9这5个中断线公用一个中断服务函数,EXTI10—EXTI15这6个中断线公用一个中断服务函数的尴尬问题。

使用说明

//EXTI15_10的中断服务函数
void EXTI15_10_IRQHandler(void) 
{
    //确保是否产生了 EXTI Line 13中断
    if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_13) != RESET)
     {

     }
}

__HAL_GPIO_EXTI_CLEAR_IT()

作用

(1)外部中断的PR寄存器发生了触发信号之后,会被置1。使用完之后PR寄存器并不会自己清0。

(2)这样将会导致一个问题,PR(挂起寄存器)一直为1,然后中断屏蔽寄存器在GPIO配置的时候就已经置1了。就会导致,会持续向NVIC中断控制器发起请求,中断会持续进行,进行完这次中断之后,再重新进行一次这个中断。

(3)因为我们只希望来一次触发信号,就执行一次中断。而不是来一次触发信号,就一直执行中断。所以我们就要使用__HAL_GPIO_EXTI_CLEAR_IT()来清除PR寄存器。

__HAL_GPIO_EXTI_CLEAR_IT()内部实现

 我们看下面__HAL_GPIO_EXTI_CLEAR_IT()的内部实现,发现很有意思的一点,就是__HAL_GPIO_EXTI_CLEAR_IT()不是一个函数!

我们发现是向PR寄存器相应位写入1,才是清除标志位,写0没反应。所以这里是等于!!! 

/**
  * @brief  Clears the EXTI's line pending bits.
  * @param  __EXTI_LINE__: specifies the EXTI lines to clear.
  *          This parameter can be any combination of GPIO_PIN_x where x can be (0..15)
  * @retval None
  */
#define __HAL_GPIO_EXTI_CLEAR_IT(__EXTI_LINE__)      (EXTI->PR = (__EXTI_LINE__))

使用

void KEY1_IRQHandler(void)
{
    //确保是否产生了 EXTI Line 13中断
    if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_13) != RESET)
     {
        ...
        //清除中断标志位
        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_13);
     }
}

中断分组的函数在哪里

很多人都是从学习标准库转到HAL库的,所以我这里说一下STM32CubeMX的中断分组在哪里。纯新手可以了解一下。

//1,点击这个函数,按F12
HAL_Init();

//2,跳转到这个函数之后,往下翻,会看见这一行。注意我配置的中断分组为4

  /* Set Interrupt Group Priority */
  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

中断函数

哪里可以查看中断服务函数名字

STM32的中断服务函数都是由指定名称的。比如外部中断线EXTI10到15引起中中断,会进入EXTI15_10_IRQHandler这个中断服务函数。那么我们应该如何寻找我们的对应中断服务函数呢?

STM32CubeMX为我们生成的中断服务函数在哪里

代码

现在各位应该已经了解了STM32的外部中断的。我就直接上代码

宏定义部分

/* Private defines -----------------------------------------------------------*/
#define K2_Pin GPIO_PIN_13
#define K2_GPIO_Port GPIOC
#define K2_EXTI_IRQn EXTI15_10_IRQn
#define K1_Pin GPIO_PIN_0
#define K1_GPIO_Port GPIOA
#define LED_G_Pin GPIO_PIN_0
#define LED_G_GPIO_Port GPIOB
#define LED_B_Pin GPIO_PIN_1
#define LED_B_GPIO_Port GPIOB

while(1)中代码

  while (1)
  {
    /* USER CODE END WHILE */
			if(HAL_GPIO_ReadPin(K1_GPIO_Port,K1_Pin) == SET)
			{
				//软件消抖,延时20ms,如果按键并联了电容,就不用考虑这个
				HAL_Delay(20);
				if(HAL_GPIO_ReadPin(K1_GPIO_Port,K1_Pin) == SET)
				{
					//按下K1,绿灯亮
					HAL_GPIO_TogglePin(LED_G_GPIO_Port, LED_G_Pin);
				}
				//等待松手
				while(HAL_GPIO_ReadPin(K1_GPIO_Port,K1_Pin) == SET);
			}
    /* USER CODE BEGIN 3 */
  }

中断服务函数

void EXTI15_10_IRQHandler(void)
{
	//确保是否产生了 EXTI Line 13中断
	if (__HAL_GPIO_EXTI_GET_IT(K2_Pin) != RESET)
	 {
            //再次强调!!!Time base: System tick timer的抢占优先级要高于(不能等于)外部中断的优先级
			//软件消抖,延时20ms,如果按键并联了电容,就不用考虑这个
			HAL_Delay(20);
		 //判断是不是K2被按下
		 if(HAL_GPIO_ReadPin(K2_GPIO_Port,K2_Pin) == SET)
		 {
		 		//按下K2,绿灯灭,蓝灯亮,
				HAL_GPIO_TogglePin(LED_G_GPIO_Port, LED_G_Pin);
				HAL_GPIO_TogglePin(LED_B_GPIO_Port, LED_B_Pin);
		 }
			//清除中断标志位
			__HAL_GPIO_EXTI_CLEAR_IT(K2_Pin);
	 }
}

物联沃分享整理
物联沃-IOTWORD物联网 » STM32CubeMX中的外部中断简介

发表评论