嵌入式-EXTI外部中断
本文最后更新于223天前,其中的信息可能已经有所发展或是发生改变。

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

中断触发条件

如对外部中断来说,是引脚发生了电平跳变;

对于定时器来说,定时的时间到了;

对于串口通信来说,可以是接收到了数据。

这些都是比较紧急的事件,如果不及时处理,那么下一个跳变信号就会跟着过来、新的数据会去覆盖旧的数据,所以需要及时处理。

使用终端的优点

极大的提高了程序运行效率,如果没有中断系统,为了防止外部中断被忽略或者串口数据被覆盖,那么主程序就需要不断地查询是否有这些事情发生,就不能执行其他事情了。比如,如果没有定时中断,那么主函数只能使用Delay函数来实行定时功能,但有了中断系统以后主程序就可以放心执行其他事情,有中断的时候再去处理

中断
中断

中断触发后,主程序立即暂停,程序由硬件电路自动跳转到中断程序中,当中断程序执行完成后,程序在返回到被暂停的地方继续执行。这个被暂停的地方,我们称之为断点,为了程序能在中断返回后继续原来的工作,在中断执行前,会对程序的现场进行保护,中断执行后会再还原现场。使用C语言编程,保护现场和还原现场的流程不需要我们来做。

  1. 中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源。
  2. 中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回。
中断嵌套
中断嵌套

当中断执行时,又有新的优先级更高的中断到来,那就会再次打断,然后执行新的中断。新的中断结束,再去执行原来的中断,原来的中断结束再去执行主程序。

STM32中断

  • 68个可屏蔽中断通道(中断源),包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设。F1系列最多的中断数量,对于一个具体的型号,可能没有那么多中断,具体以对应型号的数据手册为准。
  • 使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级(使用了4位中断优先级),可对优先级进行分组,进一步设置抢占优先级和响应优先级。NVIC就是STM32中用来管理中断、分配优先级的,NVIC的优先级总共有16个等级,加上可以设置抢占优先级和响应优先级,是非常灵活的
STM32中断向量表
STM32中断向量表

上图中,灰色部分是内核的中断。第一个,复位中断,当产生复位事件时,程序就会自动执行复位中断函数,也就是我们复位后程序开始执行的位置。NMI不可屏蔽中断、硬件失效、存储管理、总线错误、错误应用等。一般这些中断比较高深,也一般用不到,了解即可。

其他部分就是STM32外设中断了,比如第一个WWDG,窗口看门狗,这个是用来检测程序运行状态中断,那比如你程序卡死了,没有及时喂狗,窗口看门狗就会申请中断,让你的程序调到窗口看门狗的中断程序里,那么在中断程序里就可以进行一些错误检查,看看出现什么问题了。

然后是PVD电源电压监测,如果你供电电压不足,PVD电路就会申请中断,你在供电里就知道,现在供电不足,是不是电池没电了,要赶紧保存下重要数据。

FLASH,外设电路检测到有什么异常或者事件,要提示下CPU的时候,它就可以申请中断,让程序调到对应中断函数里运行一次。

EXTI0到EXTI4,EXTI9_5和EXTI15_10,就是我们本节要学习的外部中断对应的中断资源。

表右边还有个对应的中断地址。这个地址用处:我们程序的中断函数,它的地址是由编译器来分配的,是不固定的,但是我们中断跳转由于硬件限制,只能跳到固定的地址执行程序,所以为了能让硬件跳转到一个不固定的中断函数里,就需要在内存中定义一个地址的列表,这个列表地址是固定的,中断发生后,就跳到这个固定的位置,然后在这个固定位置,由编译器,再加上一条跳转到中断函数的代码,这样中断跳转就可以跳转到任意位置了。这个中断地址的列表就叫做,中断向量表,相当于中断跳转的跳板。不过,用C语言编程的话,不需要管这个中断向量表,编译器都帮我们做好了。

NVIC(嵌套向量中断控制器)

嵌套向量中断控制器,控制着整个芯片中断相关的功能,是Cortex-M3内核里面的一个外设。但是各个芯片厂商在设计芯片的时候会对 Cortex-M3内核里面的 NVIC进行裁剪,把不需要的部分去掉,即 STM32F103系列的 NVIC 是阉割过得。NVIC的全称是Nested vectoredinterrupt controller,即嵌套向量中断控制器。 对于M3和M4内核的MCU,每个中断的优先级都是用寄存器中的8位来设置的。8位的话就可以设置2^8 =256级中断,实际中用不了这么多,所以芯片厂商根据自己生产的芯片做出了调整。比如ST的STM32F1xx和F4xx只使用了这个8位中的高四位[7:4],低四位取零,这样2^4=16,只能表示16级中断嵌套。对于这个NVIC,有个重要的知识点就是优先级分组,抢占优先级和子优先级(响应优先级),下面就以STM32为例进行介绍,STM32F1xx和F4xx都是只使用了这个8位寄存器的高四位[7:4]。

具有高抢占式优先级的中断可以在具有低抢占式优先级的中断服务程序执行过程中被响应,即中断嵌套,或者说高抢占式优先级的中断可以抢占低抢占式优先级的中断的执行。

在抢占式优先级相同的情况下,有几个子优先级(响应优先级)不同的中断同时到来,那么高子优先级(响应优先级)的中断优先被响应。

在抢占式优先级相同的情况下,如果有低子优先级(响应优先级)中断正在执行,高子优先级(响应优先级)的中断要等待已被响应的低子优先级(响应优先级)中断执行结束后才能得到响应,即子优先级(响应优先级)不支持中断嵌套。Reset、NMI、Hard Fault 优先级为负数,高于普通中断优先级,且优先级不可配置。

系统中断(比如:PendSV,SVC,SysTick)不是一定比外部中断(比如SPI,USART)要高,它们是在同一个NVIC下面设置的。

NVIC优先级分组

•NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级,优先级的数是值越小,优先级越高,0就是最高优先级

•抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队

中断号就是,中断向量表里左边第二列的数字,当抢占优先级和响应优先级均相同时,就按照这里的这个数字来排队,数字小的优先响应

分组方式抢占优先级响应优先级 (子优先级)
分组00位,取值为04位,取值为0~15
分组11位,取值为0~13位,取值为0~7
分组22位,取值为0~32位,取值为0~3
分组33位,取值为0~71位,取值为0~1
分组44位,取值为0~150位,取值为0

分组方式在程序中是由我们自己来选择的,选好分组方式后,我们在配置优先级的时候,就要注意抢占优先级和响应优先级的取值范围,但是不要超出这个表里规定的取值范围

NVIC优先级分组
NVIC优先级分组

NVIC基本结构

NVIC基本结构
NVIC基本结构

EXTI外部中断

  • EXTI(Extern Interrupt)外部中断。
  • EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序。即引脚电平变化,申请中断
  • 支持的触发方式:上升沿(低电平变化到高电平的瞬间触发中断)/下降沿(高电平变化到低电平的瞬间触发中断)/双边沿(上升沿和下降沿同时都可以触发中断)/软件触发(引脚无变化,程序里执行一句代码就能触发中断)
  • 支持的GPIO口:任意的GPIO口都可以作为外部中断的引脚,但相同的Pin不能同时触发中断,例如,PA0、PB0不能同时使用,端口的GPIO_Pin一样的
  • 通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒。总共20个中断线路,16个GPIO_Pin是外部中断的主要功能。后面的四个是过来“蹭网”的,因为外部中断有个功能,是从低功耗模式的停止模式下唤醒STM32,那对于PVD电源电压监测,当电源从电压过低恢复时,就需要PVD借助一下外部中断退出停止模式,对于RTC闹钟来说,有时候为了省电,RTC定了一个闹钟之后,STM32会进入停止模式,等到闹钟响的时候再唤醒,这也需要借助外部中断,其他的也是类似功能
  • 触发响应方式:中断响应(申请中断,让CPU执行中断函数)/事件响应(STM32外部中断增加的一种额外功能,选择触发一个事件,那么外部中断的信号就不会通向CPU,而是通向其他外设,用来触发其它外设的操作,比如触发ADC转换、触发DMA等)

EXTI基本结构

EXTI基本结构
EXTI基本结构

如果每个引脚都占用一个通道,那么EXTI的16个通道显然不够用了,所以AFIO中断引脚选择器(数据选择器)可以再这前3个GPIO外设的16个引脚里选择其中一个连接到后面EXTI的通道里。所以说相同的Pin不能同时触发中断,因为对于PA0、PB0、PC0…通过AFIO选择之后,只有一个能连接到EXTI的通道0上。

通过AFIO选择后的16个通道,连接到EXTI边沿检测及控制电路上,同时另外四个外设也并列连接进来,总共20个通道。然后经过EXTI电路后,分为了两种输出:

上面20个通道连接到了NVIC,用来触发中断。外部中断的9~5,和15~10,给分到一个通道里,也就是说外部中断的9~5会触发同一个中断函数,15~10也会触发同一个中断函数。在编程时,我们在这两个中断函数里,需要在根据标志位来区分到底是哪个中断进来的。

另外20个输出线路连接到了其他外设,这就是用来触发其他外设操作的,也就事件响应

EXTI框图

下图来自《STM32参考手册》,从整个架构图可以知道,外部中断的功能可以配置六个寄存器;

  • 中断屏蔽寄存器(EXTI_IMR)
  • 事件屏蔽寄存器(EXTI_EMR)
  • 上升沿触发选择寄存器(EXTI_RTSR)
  • 下降沿触发选择寄存器(EXTI_FTSR)
  • 软件中断事件寄存器(EXTI_SWIER)
  • 挂起寄存器(EXTI_PR)
外部中断/事件控制器框图
外部中断/事件控制器框图

右边输入线就是20根输入线,输入线首先进入边沿检测电路,在上面的上升沿寄存器和下降沿寄存器可以选择是上升沿触发、下降沿触法或者都触发,接着触发信号进入或门电路输入端(只要有一个是高电平1,输出就是高电平1,只有全部输入低电平0,输出才为0),触发信号通过或门后,分两路:

上一路是触发中断的,触发中断首先会置一个挂起寄存器,这相当于是一个中断标志位,可以读取这个寄存器判断哪个通道触发的中断,如果中断挂起寄存器置1,它就会继续向左走,与中断屏蔽寄存器一起过与门(在输入端,只要有一个是低电平0,输出就是0,只有全部输入高电平1,输出才为高电平1),这里的与门是开关的作用,因为1&X=X、0&X=X,即,中断屏蔽器为1,另一个输入就是直接输出,也就是允许中断;中断屏蔽器为0,另一个输入无论输入什么,都输出为0,相当于屏蔽这个中断,

下一路是触发事件的,同样是事件屏蔽寄存器加与门相当于开关控制,然后再通过脉冲发射器到其他外设,这个脉冲发射器就是给一个电平脉冲,用来触发其他外设的动作

上面的这些就是外设接口和APB总线,我们可以通过总线访问这些寄存器。

  • EXTI支持配置20个中断和事件屏蔽位;
  • GPIO端口以下图的方式连接到16个外部中断/事件线上;EXTI_Line0EXTI_Line15
  • EXTI_Line16 连接到PVD输出 ;
  • EXTI_Line17连接到RTC闹钟事件;
  • EXTI_Line18连接到USB唤醒事件;
  • EXTI_Line19连接到以太网唤醒事件(只适用于互联型产品);

AFIO复用IO口

•AFIO主要用于引脚复用功能的选择和重定义(数据选择器的功能)

•在STM32中,AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择

image-20230918202119124
外部中断通用I/O映像

PA0、PB0、PC0…PG0通过数据选择器选择一个连接EXTI0上

PA1、PB1、PC1…PG0通过数据选择器选择一个连接EXTI1上

中断服务函数的映射关系

GPIOIRQnIRQHandler
GPIO_Pin0EXTI0_IRQnEXTI0_IRQHandler
GPIO_Pin1EXTI1_IRQnEXTI1_IRQHandler
GPIO_Pin2EXTI2_IRQnEXTI2_IRQHandler
GPIO_Pin3EXTI3_IRQnEXTI3_IRQHandler
GPIO_Pin4EXTI4_IRQnEXTI4_IRQHandler
GPIO_Pin5 — GPIO_Pin9EXTI9_5_IRQnEXTI9_5_IRQHandler
GPIO_Pin10 — GPIO_Pin15EXTI15_10_IRQnEXTI15_10_IRQHandler
中断服务函数的映射关系

外部中断的配置

宏定义,抽象一下接口,方便后面修改;

 #define Z_GPIO_PIN    GPIO_Pin_14
 #define Z_GPIO_PORT   GPIOB
 #define Z_PortSource  GPIO_PortSourceGPIOB
 #define Z_PinSource   GPIO_PinSource5
 #define Z_Line        EXTI_Line14
 #define Z_IRQ         EXTI10_15_IRQ

GPIO的配置;这里GPIO的输入模式可以配置为浮空输入(GPIO_Mode_IN_FLOATING),上拉输入(GPIO_Mode_IPU)或者下拉输入(GPIO_Mode_IPD),具体如下图所示;

GPIO的配置代码如下;

 GPIO_InitTypeDef GPIO_InitStructure;
 GPIO_StructInit(&GPIO_InitStructure);
 GPIO_InitStructure.GPIO_Pin = Z_GPIO_PIN;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_Init(Z_GPIO_PORT, &GPIO_InitStructure);
 ​

不要忘记外设总线时钟的配置;

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC |
  RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE | RCC_APB2Periph_GPIOF |
    RCC_APB2Periph_GPIOG, ENABLE);
 ​

EXTI的配置,EXTI_Trigger这里支持三种模式;

  • EXTI_Trigger_Rising 上升沿触发;
  • EXTI_Trigger_Falling 下降沿触发;
  • EXTI_Trigger_Rising_Falling 上升沿和下降沿都可以触发;
 GPIO_EXTILineConfig(Z_PortSource, Z_PinSource);
 ​
 EXTI_InitStructure.EXTI_Line = Z_Line;
 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿
 EXTI_InitStructure.EXTI_LineCmd = ENABLE;
 EXTI_Init(&EXTI_InitStructure);
 ​

NVIC的配置

  NVIC_InitStructure.NVIC_IRQChannel = Z_IRQ;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
 ​

中断服务函数

 void EXTI10_15_IRQHandler(void)
 {
      //中断服务函数
 }
 ​

以上就完成了检测下降沿信号的GPIOB14的外部中断;

点击数:8

    暂无评论

    发送评论 编辑评论

    
    				
    |´・ω・)ノ
    ヾ(≧∇≦*)ゝ
    (☆ω☆)
    (╯‵□′)╯︵┴─┴
     ̄﹃ ̄
    (/ω\)
    ∠( ᐛ 」∠)_
    (๑•̀ㅁ•́ฅ)
    →_→
    ୧(๑•̀⌄•́๑)૭
    ٩(ˊᗜˋ*)و
    (ノ°ο°)ノ
    (´இ皿இ`)
    ⌇●﹏●⌇
    (ฅ´ω`ฅ)
    (╯°A°)╯︵○○○
    φ( ̄∇ ̄o)
    ヾ(´・ ・`。)ノ"
    ( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
    (ó﹏ò。)
    Σ(っ °Д °;)っ
    ( ,,´・ω・)ノ"(´っω・`。)
    ╮(╯▽╰)╭
    o(*////▽////*)q
    >﹏<
    ( ๑´•ω•) "(ㆆᴗㆆ)
    😂
    😀
    😅
    😊
    🙂
    🙃
    😌
    😍
    😘
    😜
    😝
    😏
    😒
    🙄
    😳
    😡
    😔
    😫
    😱
    😭
    💩
    👻
    🙌
    🖕
    👍
    👫
    👬
    👭
    🌚
    🌝
    🙈
    💊
    😶
    🙏
    🍦
    🍉
    😣
    Source: github.com/k4yt3x/flowerhd
    颜文字
    Emoji
    小恐龙
    花!
    上一篇
    下一篇