• 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()其实不是一个函数,而是一个宏定义。如下

    1. /**
    2. * @brief Checks whether the specified EXTI line is asserted or not.
    3. * @param __EXTI_LINE__: specifies the EXTI line to check.
    4. * This parameter can be GPIO_PIN_x where x can be(0..15)
    5. * @retval The new state of __EXTI_LINE__ (SET or RESET).
    6. */
    7. #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个中断线公用一个中断服务函数的尴尬问题。

    使用说明

    1. //EXTI15_10的中断服务函数
    2. void EXTI15_10_IRQHandler(void)
    3. {
    4. //确保是否产生了 EXTI Line 13中断
    5. if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_13) != RESET)
    6. {
    7. }
    8. }

    __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没反应。所以这里是等于!!! 

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

    使用

    1. void KEY1_IRQHandler(void)
    2. {
    3. //确保是否产生了 EXTI Line 13中断
    4. if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_13) != RESET)
    5. {
    6. ...
    7. //清除中断标志位
    8. __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_13);
    9. }
    10. }

    中断分组的函数在哪里

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

    1. //1,点击这个函数,按F12
    2. HAL_Init();
    3. //2,跳转到这个函数之后,往下翻,会看见这一行。注意我配置的中断分组为4
    4. /* Set Interrupt Group Priority */
    5. HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

    中断函数

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

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

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

    代码

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

    宏定义部分

    1. /* Private defines -----------------------------------------------------------*/
    2. #define K2_Pin GPIO_PIN_13
    3. #define K2_GPIO_Port GPIOC
    4. #define K2_EXTI_IRQn EXTI15_10_IRQn
    5. #define K1_Pin GPIO_PIN_0
    6. #define K1_GPIO_Port GPIOA
    7. #define LED_G_Pin GPIO_PIN_0
    8. #define LED_G_GPIO_Port GPIOB
    9. #define LED_B_Pin GPIO_PIN_1
    10. #define LED_B_GPIO_Port GPIOB

    while(1)中代码

    1. while (1)
    2. {
    3. /* USER CODE END WHILE */
    4. if(HAL_GPIO_ReadPin(K1_GPIO_Port,K1_Pin) == SET)
    5. {
    6. //软件消抖,延时20ms,如果按键并联了电容,就不用考虑这个
    7. HAL_Delay(20);
    8. if(HAL_GPIO_ReadPin(K1_GPIO_Port,K1_Pin) == SET)
    9. {
    10. //按下K1,绿灯亮
    11. HAL_GPIO_TogglePin(LED_G_GPIO_Port, LED_G_Pin);
    12. }
    13. //等待松手
    14. while(HAL_GPIO_ReadPin(K1_GPIO_Port,K1_Pin) == SET);
    15. }
    16. /* USER CODE BEGIN 3 */
    17. }

    中断服务函数

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

  • 相关阅读:
    在线 sha1 加密
    基于Java毕业设计在线课堂辅助系统源码+系统+mysql+lw文档+部署软件
    CentOS Docker 安装 & 常用命令
    PHP使用PHPWord生成word文档
    电脑经常弹出“不支持的硬件”解决办法
    客户分析:必须要找出这5种客户?(BP分析)
    ardupilot开发 ---传感器驱动,外设驱动篇
    PowerBI - 匹配并从另一个表中获取值
    Springboot日常总结-idea全局配置maven
    Android环境配置笔记
  • 原文地址:https://blog.csdn.net/qq_63922192/article/details/127929065