首页 > 技术文章 > RoboMaster电控培训(三)中断

JiangLiHong 2021-08-18 22:02 原文

RM电控入门(三)中断

  今天我们来学习一下中断,中断是嵌入式开发中必不可少的一个环节,也是提高程序运行效率的法宝,实际上中断是实现多线程设计的必要条件,希望大家通过本篇文章,能更好地理解中断的内涵和本质。

 

为什么要用中断

  通常我们可能会先问:什么是中断?但是今天我想让大家想一想为什么我们需要用到中断。上一篇文章(GPIO)中我说到,大多数功能语句都是放在while(1)的死循环中不断执行,只有少部分只需要执行一次的初始化代码放在while循环外。那么这种对信号处理的方式我们称之为轮询方式,也就是不断地访问一个信号的端口,看看有没有信号进入,有再进行处理 。举个买火车票例子,在售票窗口人都会排队买票,那售票员挨个处理的这种方式就可以近似看作是轮询,那假如是春运时间,排队的队伍是相当长,很不幸你的车马上就要发车了但是如果还是排队的话你肯定是上不了车了。所以轮询方式在处理事项不多(排队的人少)且每个事项花费时长不长(每个人很快就能处理完)的情况下是不会出现问题的,一旦事项多且长时,轮询方式处理信号会出现响应慢甚至不响应的问题,这可能会导致程序崩溃。也就是说在任务多且有急缓之分时,迫切需要一种新的信号处理方式。在这样的情况下中断就应运而生了。

 

什么是中断

  中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。换句话说中断就是中途被打断转而处理其他事情,处理完后在返回处理原来的事情。本质上看中断其实就是CPU芯片内外部硬件电路产生的电信号。引起中断的事件称为中断源,中断源向CPU提出进行处理的请求称为中断请求。

  中断分为同步中断和异步中断。

  • 同步中断是当指令执行时由 CPU 控制单元产生,之所以称为同步,是因为只有在一条指令执行完毕后 CPU 才会发出中断,而不是发生在代码指令执行期间,比如系统调用、CPU本身故障、程序故障和请求系统服务的指令引起的中断CPU内核出错。同步中断也被叫做系统异常(简称异常)、内中断。

  • 异步中断,通常是外部硬件设备事件所引起的中断,异步中断是指由其他硬件设备依照 CPU 时钟信号随机产生,即意味着中断能够在指令之间发生,例如键盘中断,异步中断也叫外中断。

 

  在这里我们暂时不讨论异常和中断的差别,无特殊说明统一称为中断。

NVIC、中断优先级与中断向量表

QQ截图20210816162446

NVIC

  上图为中断结构框图,可以看到所有的中断信号最终都输入到NVIC中,那NVIC是什么?NVIC是如何处理这些中断信号的?

当一个外设根据其硬件状况产生了中断信号时,会将中断信号提交到NVIC中。NVIC全称是Nested Vectored Interrupt Controller,即嵌套向量中断控制器。NVIC接收到中断信号后会根据中断信号定义的编号信息,置位ISER寄存器中的相应中断Pending位,如果该中断是被使能并且当前阶段没有更高优先级的中断正在执行,那么NVIC会查询中断向量表的对应中断入口服务函数的地址找到并进入对应服务函数。

  说到这里有大家可能会有点懵,所以我们先来捋一捋相关知识。

  1. NVIC相关寄存器

    • ISER寄存器组: 32位寄存器,每位控制一个中断,写1使能相应中断,写0无效

    • ICER寄存器组: 32位寄存器,每位控制一个中断,写1除能相应中断,写0无效、

    • ISPR寄存器组:32位寄存器,每位控制一个中断,将某位置1会将相应的中断挂起,写0无效

    • ICPR寄存器组:32位寄存器,每位控制一个中断,将某位置1会将相应中断解除挂起状态,写0无效

    • IABR寄存器组:32位寄存器,只读寄存器组,如果读到某位为1,表示该位对应中断正在执行服务函数,中断服务函数执行完后 由硬件自动清零

    • IP寄存器组:8位寄存器,每个寄存器高四位用来配置抢占优先级和响应优先级的等级(优先级的分组由SCB->AIRCR寄存器的[10:8]位控制)

     

    以上就是NVIC的一些主要寄存器,可以看出NVIC的寄存器与我们平时见到的寄存器有些不同:

    • NVIC寄存器绝大多数是寄存器组,因为F427内核可屏蔽中断就有80多个,至少需要3个寄存器才能做到控制完全部的中断,但是厂商深谋远虑不仅给每个寄存器预留了很多位,每个寄存器也配置了8个(虽然目前我们只用到了3个)。

    • 与大多数的控制寄存器的写1使能写0失能不同,中断的使能和失能都是由不同的寄存器写1控制,对这些寄存器写0是无效的。

     

  2. 中断的一些状态

    • Pending (中断悬(挂)起)

      当中断输入脚的中断请求被NVIC确认有效后,NVIC会将该中断悬起,中断悬起标志位被置1,这个中断悬起标志可以类似于生活中去小餐馆吃饭点单完的小票单,意思就是说你的点单(中断请求)后厨(CPU)已经了解了,之后会给你做(响应中断),但是这个这个做饭的顺序就不同与现实生活中的先来后到了:如果CPU从Handler模式退回到线程模式,通俗一点说就是刚处理完一个中断服务函数。他会瞄一眼现在有哪些中断请求,根据不同中断的优先级来确定下一个响应的中断。当一个中断被响应,CPU进入该中断的中断服务函数时,中断悬起标志会可能被硬件自动清除也可能需要用户自己手动清除。如果用户自己忘了清除中断悬起标志位,CPU可不会记得自己上一次处理过这个中断,他会认为有有一个新的中断请求所以会再次进入该中断的服务函数,从而导致程序一直卡在这个中断服务函数里面!!!所以有些中断悬起标志不会被硬件自动清除的中断,需要我们在编写中断服务函数时手动清除中断悬起标志,否则后果很严重。置于为什么会存在需要用户自己清除悬起标志这种看上去只会增加程序BUG的设计?因为中断向量表(之后会谈到)在随着内核更新后变得不够装了,所以有许多同类型的中断共用一个中断服务函数,那中断悬起标志需要在进入中断服务函数之后也保持置高状态让用户能分辨出具体是哪个中断请求,自然也需要用户自己清除中断悬起标志位了。需要注意的是,只要中断请求被确认有效,中断悬起标志被置1后,中断请求撤销也不能改变中断悬起标志位,CPU一样会进入该中断的中断服务函数。还有就是,NVIC只会记录是否有中断请求,而不会记录有几次中断请求,假如在CPU进入中断服务函数之前有多次中断请求,中断悬起标志早在第一次就会被置高,中断服务函数也只会执行一次。

    • 中断活跃

      当某中断的服务函数开始执行时,就称此中断进入了活跃状态,也叫做中断有效,并且其中断悬起位会被硬件自动清零。当中断服务函数结束退出后,中断的活跃状态也停止并返回,只有在返回后才能对该中断的新请求予以响应。

      这么说可能脑子有点糊,我们来看时序图来捋一捋。

image-20210817144753525

                                                 当NVIC确认中断请求有效后,会将对应中断的中断悬起标志位置高,当进入对应中断服务函数时,中断悬起标志位需要被清除(硬/软)

image-20210817145048834

                                                                        如果在CPU进入中断服务函数之前,中断悬起标志被软件清零,则CPU不会进入中断服务函数(Handler模式)

image-20210817145245819

                                                                     当CPU进入中断服务函数,相应的中断活跃寄存器位(IABR寄存器)置1,退出中断服务函数时,中断活跃寄存器被置0

20181203061942619

                                                                                                       只有当中断服务函数执行完,中断从活跃状态返回后,才能响应新的中断请求

 

优先级分组

  上文讲了单个中断的响应流程,接下来讲的是当有多个中断请求时,NVIC如何仲裁这些中断哪个先响应,这就涉及到中断的优先级分组的知识了,还记得上文中NVIC的寄存器一块里我们有一个IP寄存器吗,我们来讲一下单片机是如何操控这个寄存器来实现中断的优先级分组的。

  对于M3和M4内核的MCU,每个中断的优先级都是用IP寄存器中的8位来设置的。8位的话就可以设置2^8 =256级中断,实际中用不了这么多,所以芯片厂商根据自己生产的芯片做出了调整。比如ST的STM32F1xx和F4xx只使用了这个8位中的高四位[7:4],低四位取零,这样2^4=16,只能表示16级中断嵌套。用于表达优先级的这 4bit,又被分组成抢占优先级响应优先级。4bit分别表示两个优先级等级,一共可以分成5组

组别抢占优先级位响应优先级位抢占优先级级数响应优先级级数
0 [7:4] none 16 0
1 [7:5] [4] 8 2
2 [7:6] [5:4] 4 4
3 [7] [6:4] 2 8
4 none [7:4] 0 16

  表中可以看出抢占优先级和响应优先级在分高四位([7:4]),如果分组策略是组0的话,表达的优先级就全部用于区别抢占优先级,抢占优先级就有16级,中断的优先级选择可以从0~15。如果分组策略是组2的话,4位中各有2位表示抢占优先级和响应优先级,那一个中断分别有4级抢占优先级和4级响应优先级可供选择。注意:优先级的数值越小代表器优先级等级越高

 

  中断的仲裁原则:

  • 抢占优先级高(数值小)的中断可以打断正在执行的抢占优先级低(数值大)的中断。

  • 多个中断同时响应,抢占优先级高(数值小)的中断优先响应

  • 如果多个中断抢占优先级相同,则不存在打断现象,只是在同时响应中优先执行响应优先级高(数值小)的中断

  • 如果抢占优先级和响应优先级都一样的中断,就比较他们的硬件中断编号,硬件编号小的优先响应

 

  抢占优先级的存在导致了有中断嵌套的现象。如果大家有C语言的基础,了解过循环嵌套的说法后 ,对中断嵌套也能理解地非常快。

  CPU是在每次执行完一条语句后查看NVIC中所有的中断悬起标志位,如果CPU看到了比现在正在执行的中断服务函数优先级更高的中断悬起标志,那么CPU会转而执行更高优先级的中断,实现了中断的中断,只要你想,就有中断的中断的中断,中断的中断的中断的中断 …

  既然是嵌套,那内层中断执行完后必然要回到上一级中断执行完,如下图。这其实涉及到一个完整的中断响应流程:中断悬起标志置高—>保存现场—>处理中断—>恢复现场的过程。下图中的ISR指的是中断服务处理,Interrupt Service Routines。

image-20210818105313558

 

中断向量表

  以上是中断的响应流程,但是F427在内核水平上的异常响应系统中系统异常有 10 个,外部中断有 87 个。 这些中断中包含所有的外设中断,外部中断,系统异常。每个中断都有自己的中断服务函数,那NVIC将一个中断信号和其对应的中断服务函数联系起来,用的就是中断向量表。这里我们节选部分。

image-20210818090952732

image-20210818091143979

image-20210818090923669

 

  上面我断断续续截了一些图,如果想看详细的这97个中断的向量表可以在STM32F4中文/英文参考手册中NVIC一章中看到。

  从上图我们能观察出一些特点,先说明一下表格中灰色的部分代表是系统异常

  • 系统异常中有一些中断优先级是比其他所有优先级都要高的,他们的优先级等级为负数,这是可配置优先级所不能配置出的。像硬件复位优先级最高,可以打断任何中断,还有NMI中断 :Non Maskable Interrupt 即不可屏蔽中断,这是时钟安全系统(CSS)的中断,当外部高速时钟故障时,CSS会紧急调用内部高速时钟来应急,这些我们会在下一节 时钟中具体谈到。

  • 并不是系统异常的优先级一定比外部中断的优先级高,有一些系统异常的优先级类型也是可设置的,自然就有可能比外部中断等级低

  • 有一些中断共用同一个中断向量例如EXTI0,所以需要在中断服务函数内部判断具体是哪个中断。

 

  至于中断向量这个名字是怎么来的?中断向量到底有什么用?为什么要有中断向量表的存在?我们现在来解释一下:

  经过一番查找资料后,网络上大部分对中断向量的观点是:中断向量更偏向于指针的作用,中断向量是中断服务函数的指针,向量的含义可能只是代表有指向方向。那内核厂商把所有可能的中断都初步编写了中断服务函数,自然也就把这些中断向量列成了一张中断向量表供NVIC查阅,有了中断向量表后CPU看到中断悬起标志就能根据这个中断向量表查找到对应中断向量,自动寻址到中断服务函数,这样处理中断的效率就非常高。关于中断向量表有个我觉得网上有个非常恰当的比喻,中断向量表就是一个地址黄页,上面记录着如果火警去哪找消防队,如果匪警上哪去找警察局,如果病危上哪去找医院。这里面的“火警”、“匪警”、“病危”就是各种中断,上哪去找负责的单位,就是中断向量表里面的地址。专业一点说中断向量就是记录着每个不同中断发生后处理器跳转到中断服务程序的起始地址。

  那NVIC的主要功能我们已经全部讲完了,现在来小结一下NVIC的主要功能。

  • 使能与失能某些中断

  • 配置中断的抢占优先级和响应优先级

  • 接收中断请求后置位相应中断悬挂位

  • 提供只读寄存器IABR,检测中断的活跃状态,监测是否进入中断服务程序

 

EXTI

  上一小节我们已经详细介绍了 NVIC,对 STM32F4xx 中断管理系统有个全局的了解, 那么接下来我们讲讲外部中断,我们重新看一下上面的中断程序框图,可以看到并不是所有的中断都是直接移交给NVIC控制的,外部设备的中断处理经过了一个特殊的控制器来管控,这就是我们的EXTI,External interrupt/event controller,即外部中断/事件控制器。

QQ截图20210816162446

  外部中断/事件控制器 (EXTI) 管理了控制器的 23 个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。 EXTI 可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。可以这么说,一共144引脚的芯片,可配置中断的140多个GPIO,都是由EXTI管理的,注意是GPIO,一旦引脚为外设专用脚或是复用为其他外设的引脚比如串口接收引脚,CAN接收引脚等等。它们就属于片上外设的引脚,它们的中断相关配置由其模块的寄存器配置,有专门的中断请求和中断源由NVIC管控。

  23个中断/事件线,具体是哪些我们要看STM32F4XX中文参考手册

image-20210818144245962

  可以看到EXTI0~EXTI15这16根线由GPIO 映射而成,所有的0引脚从PA0到PH0都与EXTI0线相连,也就是说,在PA0到PH0这8个IO口同时只有一个能配置成外部中断模式,不然会冲突,其他线也同理,那具体选择那个引脚作为外部中断线由SYSCFG_EXTICR寄存器的相应位设置,所以我们配置EXTI模式时,也要开启SYSCFG的时钟。另外七根 EXTI 线连接方式如下,这些特定外设的中断在具体用到时再做说明。

image-20210818145226933

EXTI功能框图

image-20210818145616580

  和任何外设一样,想要掌握使用方法首先我们要理解他的功能框图,可以看到功能框图中有红绿两条主线,一个是产生中断请求到NVIC中断控制器中,一个是通过脉冲发生器产生一个脉冲信号这里叫做产生事件,这也是EXTI名字的由来:外部中断/事件控制器 。图中还有很多23上划有斜杠,这个表示在控制器内部类似的信号线路有 23 个,这与 EXTI 总共有 23 个中断/事件线是吻合的,线路与线路之间也是独立不干涉的。 还有一点就是EXTI是在APB2总线上的,可以看到有PCLK2指向其外设接口,至于APB2总线的知识我们也会在下一章时钟一节讲解。

QQ截图20210818193452

中断线

  最右边的输入线代表的都是我们的GPIO口或是一些外设的事件,一个GPIO配置成EXTI模式后,那这个引脚的输入电平就会接到输入线上,那输入电平首先就会经过边沿检测电路。这个边沿检测电路就是受上升沿触发选择寄存器和下降沿触发选择寄存器配置的一个电路,这两个寄存器可以控制器需要检测哪些类型的电平跳变过程即配置有效边沿跳变,可以是只有上升沿触发、 只有下降沿触发或者上升沿和下降沿都触发。 而输入线输入的电平一般都是存在电平变化的信号。如果边沿检测电路检测到有效边沿跳变就会输出1到后续的电路,否则输出0。之后边沿检测电路输出的信号会流入编号3电路中。

 

  编号3是一个或门电路,或门电路是一个经典的逻辑门电路,这个门电路要求有两个输入,只要有一个输入为1,那或门电路就输出1,只有当两个输入门都是输入0时,或门电路才输出0。在此处中或门电路的一个输入信号来自边沿检测电路,另一个信号来自软件中断事件寄存器,该寄存器允许我们通过程序来启动中断/事件线,这在某些地方非常有用。我们知道或门的作用就是有1就为 1,所以这两个输入随便一个有有效信号 1 就可以输出 1 给编号 4 和编号 6 电路 。

  之后是编号4电路,现在我们有了3号的输出信号,不论是软件提供的中断信号还是硬件的电平跳变信号都会转化成中断请求信号输入到4号电路中,4号电路是另一个经典逻辑门电路——与门电路,与门电路也是两个输入,并且只有当所有输入都为1时,才会输出1,任何一个输入为0都会导致输出也为0。那来自3号的输出信号为1表示有中断请求,另一路输入来自中断屏蔽寄存器,这个寄存器的作用和他的名字一样,这个寄存器的输出可以导致3号的输出信号被屏蔽,这其实也是配合了与门电路,如果中断屏蔽寄存器输出1,则是不屏蔽输出信号,4号的输出取决于3号电路的输出,如果中断屏蔽器选择输出低电平,那么不论3号是否输出高电平与门电路都不会输出中断信号。那4号与门电路最终输出的中断请求信号会把挂起请求寄存器的对应位置1,这样中断请求就能被NVIC接收到了。

 

事件线

  事件线的流程与中断线非常相似,在3号或门电路之前的路径可以说完全一致,或门电路判断出中断/事件信号后也是和一个屏蔽寄存器进入一个与门。唯一的不同就是事件线的末端是接入一个脉冲发生器,这个脉冲发生器在收到有事件请求后会产生一个脉冲信号,这个脉冲信号可以给其他外设电路使用,比如定时器 TIM、模拟数字转换器 ADC 等等,这样的脉冲信号一般用来触发 TIM 或者 ADC开始转换

  可能会不太好理解为什么需要产生事件,如果我们需要一个事件信号来触发TIM或者启动AD转换,为什么不用中断信号来产生一个中断,然后在中断服务函数里面编写这些驱动程序?更何况从外部激励信号来看,中断和事件的产生源都是一样的。

  之所以分成2个部分,由于中断是需要CPU参与的,需要软件的中断服务函数才能完成中断后产生的结果。但是事件,是靠脉冲发生器产生一个脉冲,进而由硬件自动完成这个事件产生的结果,当然相应的联动部件需要先设置好,比如引起DMA操作,AD转换等。用事件通道,在有硬件基础的条件下可以顺带驱动一个功能而不需要CPU的参与了。可以这样简单的认为,事件机制提供了一个完全有硬件自动完成的触发到产生结果的通道,不要软件的参与,降低了CPU的负荷,节省了中断资源,提高了响应速度(硬件总快于软件),是利用硬件来提升CPU芯片处理事件能力的一个有效方法。

至此,与中断相关的知识就全部梳理完了,接下来是实际操作,我们会用硬件输入中断(按键)和软件中断两种中断来实现点灯。

 

实战练习

  在前面两篇文章中我重点介绍了STM32CubeMX和Keil5的功能使用,以后的文章中一些配置步骤就会比较简略,只会留下关键步骤截图

image-20210818193810464

image-20210818200140009

  这里虽然是软件中断,引脚配置只要选择是中断模式就都行,因为软件中断的信号与输入线的电平变化无关。这里设置一下可以用作或门的实验。然后配置一下LED

image-20210818205215642

image-20210818201206165

image-20210818201301969

image-20210818201321946

image-20210818201407760

  生成代码,打开工程文件,进入keil,可以在main.h中看到我们的User label

image-20210818205256187

  在Startup.S文件中我们可以看到官方用汇编写的中断向量表,CPU就是根据中断向量表来寻址中断服务函数。

image-20210818201618411

  在stm32f4xx_it.c中就存放者各种中断服务函数,其中没配置的中断往往都是空函数,而用CubeMX配置过的中断的中断服务函数内部有相应的HAL库函数。而且可以看出EXTI的中断服务函数都是同一个HAL_GPIO_EXTI_IRQHandler,也就是说我们需要在内部查看中断悬起标志才能判断出具体是哪个中断。如果我们不是HAL库函数而是使用标准库函数,那么我们的中断服务函数就要在相应的IRQHandler里面编写了,这里面的IRQ是Interrupt Request的缩写。

image-20210818202021398

  我们再查看HAL_GPIO_EXTI_IRQHandler的函数定义。

image-20210818202441986

  可以看到HAL_GPIO_EXTI_IRQHandler函数首先利用了__HAL_GPIO_EXTI_GET_IT函数来确定中断请求是否是确定的(asserted),__HAL_GPIO_EXTI_GET_IT是一个宏定义函数,他的函数功能如下图所示。

image-20210818202625913

  判断完中断是确定的后,HAL_GPIO_EXTI_IRQHandler调用了另一个宏定义函数__HAL_GPIO_EXTI_CLEAR_IT。简而言之,这个函数就是程序清除中断悬起标志位,将中断悬起标志位置0,不然CPU会一直卡在中断服务函数里。

image-20210818202823507

  清除完中断悬起标志后,HAL_GPIO_EXTI_IRQHandler调用了HAL_GPIO_EXTI_Callback函数,其实这才是HAL库里的真正的中断服务函数,又被翻译为中断回调函数。如果是标准库,那检验中断有效性,清除中断标志位,中断服务函数内容会一股脑塞在IRQHandler函数内,但是HAL库将一些系统的操作检验中断有效性,清除中断标志位放在HAL_GPIO_EXTI_IRQHandler中,而真正需要用户编写逻辑的中断服务函数,放在了Callback函数内。其函数定义如下图。

image-20210818203341937

  可以看到这个函数与我们平时所见的函数定义不太相同,这个函数好像没有什么内容,并且在函数头前还有_weak关键字,这个weak关键字的作用就是弱定义。有_weak属性的函数是允许再次定义并且不会报错的,如果你定义了一个重名函数,那么原weak函数定义就不再生效,你调用的该函数如果用go_to_definition查看会跳转到你的函数定义中。所以我们能够在别处定义一个同名的Callback函数。来实现用户自己的中断服务功能。

  最后我们再利用function窗口看看软件中断的相关函数。可以看到__HAL_GPIO_EXTI_GENERATE_SWIT函数的作用就是对一条EXTI线产生软件中断。

image-20210818212341991

  小结一下EXTI的相关HAL库函数:

  • HAL_GPIO_EXTI_IRQHandler函数,中断服务函数

  • __HAL_GPIO_EXTI_GET_IT函数,返回中断是否有效

  • __HAL_GPIO_EXTI_CLEAR_IT函数,清除中断悬起标志

  • HAL_GPIO_EXTI_Callback函数,中断回调函数,编写中断服务逻辑

  • __HAL_GPIO_EXTI_GENERATE_SWIT函数,产生软件中断。

 

我们编写的程序逻辑如下图:

image-20210818213858231

  可以看到我们在while(1)内每3s产生一次软件中断信号。

  然后我们在main.c的下方的任意一处可以编写函数的地方重新定义中断回调函数。注意要找User Code Begin 和User Code End 之间填写

image-20210818214105995

  可以看到我们首先用if判断到底是哪个引脚产生的中断对应不同的处理。这里我们对软件中断的回调服务亮红灯,硬件的按键中断回调服务是亮绿灯。

  既然都是一个引脚产生的外部中断,软件中断与输入线的外部中断是或门关系,我们在这个程序中同样可以测试,可以将while(1)中的有关软件中断的两行代码注释掉,下载好程序后,我们用一根杜邦线一边连GND,另一边插入PF1脚,观察现象,同样可以观察到红灯翻转。

 

总结

  本章我们学习了NVIC和EXTI这两大中断控制器,需重点掌握以下几点:

  • 轮询式和中断式的信号处理

  • 中断优先级的分组

  • 中断响应流程

  • 中断嵌套

  • EXTI功能框图

  • EXTI相关函数

  • 三种信号触发方式

  • 产生中断与产生事件的区别

  • __weak关键字

 

  思考以下问题:

  1. 为什么实验中是将引脚接GND而不是接VCC?

  2. 在本例实验中会存在中断嵌套的现象吗?

  3. 在上一篇文章中我们谈到了按键需要按键消抖动,那本次按键是以外部中断的形式输入,如何实现按键消抖动,可以在中断回调函数中使用HAL_Delay()函数吗?

 

  下一节我们将学习STM32时钟。

推荐阅读