Bootstrap

ARM中断 - ARM内核(五)

ARM内核中断是ARM异常的一种,用来实现外部数据和外部硬件之间进行通信。内核每次执行完一条指令都会查询其硬件中断情况,如果此时硬件硬件中断信号被拉起来,就会触发中断异常,跳转到异常向量表处理。作为SOC复杂片上系统管理众多外部硬件设备,不可能在内核中开发众多的硬件信号给各个外设。一次在SOC设计时会在ARM内核外增加一道多层辅助的中断控制网络,这种中断网络针对不同SOC设计或者不同公司是的不同系列产品其中断IP是不一致的。前面提到内核转到中断向量表进行进行代码执行,结合复杂的外部硬件控制器网络(类似GIC),在这部分代码处理中会读取中断控制网络器中的数据进行复杂的中断网络处理。对于内核的中断异常,很难使用独立的代码出发硬件类型中断让内核处于中断异常,都是需要结合外部中断网络来完成的。但其实中断异常内核的异常基本处理流程和其他类型的异常没有本质差别,核心差别在于内容处理这里需要获取SOC上面的中断控制器来配合完成整个中断体系的管理。基于如上两点,我们这里不专门针对ARM内核的中断异常进行实验,而是主要针对外部中断控制器来完成整体的工作。本篇文章实际上不属于ARM内核部分内容,更应该是SOC的内容。

本篇主要是针对SOC硬件中断控制器内容进行讲解,讲解中断硬件配置和使用,对于中断软件的处理只是给定了简单的串口打印。关于复杂的软件处理中断流程,看Linux的中断体系架构文章。

1. 中断体系

在SOC中包含了ARM内核,ARM内核其他的异常模式功能相应默认是打开的,但是ARM内核是否响应外部中断控制器通给其的硬件中断信号是需要对ARM的CPSR状态寄存器中断使能控制为配置的。正常情况下,对齐配置1为使能外部中断响应,这个时候整个外部的中断网络中的中断信息才可能被ARM内核响应。

1.1 中断硬件架构

中断是一种特殊的异常模式,用来实现外部器件打断CPU内核的运行。中断基本的硬件框架如下:

  • 中断源。中断源多种多样,比如GPIO、定时器、UART、DMA等等。它们都有自己的寄存器,可以进行相关设置:使能中断、中断状态、中断类型等等。

  • 中断控制器。各种中断源发出的中断信号,汇聚到中断控制器进行统一管理。可以在中断控制器中设置各个中断的优先级。中断控制器会向CPU发出中断信号,CPU可以读取中断控制器的寄存器,判断当前处理的是哪个中断。中断控制器有多种实现,比如:

    • STM32F103中被称为NVIC:Nested vectored interrupt controller(嵌套向量中断控制器)

    • ARM9中一般是芯片厂家自己实现的,没有统一标准

    • Cortex A7中使用GIC(Generic Interrupt Controller)

  • CPU。CPU每执行完一条指令,都会按照设计的扫描优先级依次判断一下是否有中断发生了。CPU也有自己的寄存器,可以设置它来使能/禁止中断,这是中断处理的总开关。

    • 对于低端的芯片可能直接是打开状态,或者某一个内部寄存器

    • 对于高端的ARM芯片通过设置程序状态寄存器中IRQ使能标识来打开

1.2 实例 - TM32F103

对于GPIO中断,STM32F103又引入了External interrupt/event controller (EXTI)。用来设置GPIO的中断类型并进行GPIO的管理。对于串口或者网口相关的额中断直接链接在NVIC中断控制器上面进行管理。EXTI可以给NVIC提供16个中断信号:EXTI0~EXTI15。那么某个EXTIx,它来自哪些GPIO呢?这需要设置GPIO控制器完成这种映射关系的对应。

1.3 实例 - imx6ull

对于imx6ull这种A7类型开发板外部所有中断都是统一通过General Interrupt Control来控制管理,如果中断已经触发,就将CPU与GIC对应的中断硬件拉高触发相关的中断。当然CPU处理中断过程中也可以将中断控制器的屏蔽寄存器打开或者直接去使能中断触发,这个时候GIC就没有办法像CPU汇报自己中断处理以后的结果。

 需要另外注意的一个点是对于高端的芯片GIC可以针对多核CPU进行管理,也就是说GIC可以配置某种类型的中断只发给某个CPU核,同时CPU核之间也可以通过GIC完成互相通信。另外加上CPU内部的一些定时器触发自身中断。也即是说高端CPU核一般有三张类型的中断。

2. 中断优先级与嵌套

2.1 中断优先级

中断优先级的管理都是在中断控制器(GIC或NVIC)中管理完成完成。在说到中断之前,我先来定义一下优先级,明白了什么是优先级,后面的阐述就容易明白了。实际上很多人都是混淆了优先级的含义,所以才觉得糊里糊涂。中断的优先级有两个:查询优先级和执行优先级。

  •  什么是查询优级呢?我们从datasheet或书上看到的默认(IP寄存器不做设置,上电复位后为00H)的优先级:外部中断0 > 定时/计数器0 > 外部中断1 > 定时/计数器1 > 串行中断。或 int0,timer0,int1,timer1,serial port 或 INT0、T0、INT1、T1、UART。或 PX0>PT0>PX1>PT1>PS>......

其实都是查询优级。首先查询优先级是不可以更改和设置的,这个是硬件设计中断控制器这种中断控制状态机的时候固定下来的。这是一个中断优先权排队的问题。是指多个中断源同时产生中断信号时,中断仲裁器选择对哪个中断源优先处理的顺序。而这与是否发生中断服务程序的嵌套毫不相干。当CPU查询各个中断标志位的时候,会依照上述5个查询优先级顺序依次查询,当数个中断同时请求的时候,会优先查询到高优查询先级的中断标志位,但并不代表高查询优先级的中断可以打断已经并且正在执行的低查询优先级的中断服务。

例如:当计数器0中断和外部中断1(按查询优先级,计数器0中断>外部中断1)同时到达时,会进入计时器0的中断服务函数;但是在外部中断1的中断服务函数正在服务的情况下,这时候任何中断都是打断不了它的,包括逻辑优先级比它高的外部中断0计数器0中断。

  • 中断的执行优先级就是你对IP寄存器的设置了。在2个优先级的情况下,某位为1,则相应的中断源为高优先级;为0,则为低优先级。对于ARM系统存在多个优先级,数字越小优先级越高,也即是说0号中断优先级最高。

关于中断的优先级有三条原则:

1、CPU同时接收到几个中断时,首先响应优先级最高的中断请求;但如果优先级序列最高的存在多个中断,这个时候按照查询优先级(中断向量表的前后顺序)来执行中断处理程序;

2、正在进行的中断过程不能被新的同级或低行优优先级的中断请求所中断(实际硬件细节是中断控制器进行判定,它会记录当前自己发送给CPU的中断是什么以及优先级,如果新来的优先级比这个优先级低,中断控制器就直接拦下来,不会拉中断消息传输引脚告知CPU);

3、正在进行的低行优优先级中断服务,能被高行优优先级中断请求中断;

若:同一执行优先级中的中断申请不止一个时,则有一个中断优先权排队问题。同一执行优先级的中断优先权排队,由中断系统硬件确定的自然优先级形成,优先权自高到低的顺序即:

外部中断0>定时/计数0>外部中断1>定时/计数1>串行接口

例如:设置IP = 0x10,即设置串口中断为最高优先级,则串口中断可以打断任何其他的中断服务函数实现嵌套,且只有串口中断能打断其他中断的服务函数。若串口中断没有触发,则其他几个中断之间还是保持逻辑优先级,相互之间无法嵌套。

2.2. 中断嵌套

中断嵌套是中断发生的一种情况,这种情况条件是中断优先级高的中断在中断优先级低的中断处理过程中发生,这个时候中断控制器会通过硬件接口传递信息给CPU新的中断发生。中断嵌套过程中断控制器工作如前所讲是比较简单的,但是中断嵌套现场保存合计恢复过程确实复杂的,这个复杂过程可以CPU自己硬件设计做(M3),也可以软件完成(A7)。现场保存过程实际是将CPU内部相关的寄存器保存到一个不会被改写的存储器位置,至于这种存储器类型根据不同的处理器内容可能不一样(SDRAM或者ERAM)。实际较大的系统当中中断嵌套发生的概率是非常大的。中断现场保存于恢复相比于函数栈的调用过程信息保存两者极为类似,差别是函数调用过程由于函数形式确定,函数保存和恢复的现场都是确定的。但是对于中断发生时机不确定,保存的数据自然也是尽可能多,进而保证数据不被丢失。中断过程相比于函数会多保存过个普通寄存器An,同时还会保存程序状态寄存器。

3. 代码实例

 DMA基于物理地址的数据搬运工作

驱动开发中DMA涉及概念见文章总结

1. ARM寄存器

ARM寄存器通用寄存器、。程序状态寄存器cpsr描述当前寄存器的内容。

1. 中断基本概念

1.1. 实例

假设我们生活中一位母亲在照顾睡觉的小孩,并自己在看书,那么这种情况下面如何得知小孩是否醒来?

这个时候母亲可以通过不断的去查看孩子确认是否醒来,还可以是在孩子醒来的哭声打断看书的母亲。第一种方式简单,但是非常累,一般在小孩子很小的时候会这个样子。第二种方式复杂但是轻松,当孩子稍微大一些的时候这种方式比较好。对于这两种情况处理流程可以是:

1. 第一种轮训使用一个while循环,完成读书事情,然后接着查看孩子情况,如果没有醒返回继续while看书,如果醒来照顾孩子穿衣服,结束以后继续自己看书。

2. 第二种通过中断方式,在看书过程中,听外面发出来的各种声音。这个时候外界可能有门铃声、小孩哭声或者猫叫声。这个时候母亲在读书,如果发生了中断,母亲则放置书签合上书(保存现场),然后针对不同类型的中断产生不同的动作(中断处理)。对于第三种猫叫声可以不用做任何处理,其他几种类型可以产生不同的动作,门铃声打开门拿快递,孩子哭声则去照顾孩子。处理完事宜以后则继续回来看刚才书签确定的书籍位置(恢复现场)。

1.2. CPU中断

类比与母亲通过声音照顾孩子的场景,那么CPU是如何处理中断的?对于硬件类型的异常,包含中断和其他类型的异常,其中中断属于异常的一种。

ARM处理中断过程如下:

1. 初始化中断系统。a) 设置中断源,让外部器件能够产生中断。b) 设置中断控制器优先级和屏蔽情况。c) 设置CPU总开关,使能中断。

2. 程序正常执行。这个时候CPU处于中断检测当中,没运行完一条指令就确认是否有中断和异常产生。

3. 硬件中断被触发。这个时候一个打开了中断的按键被按下,触发的中断被传入中断控制器进而传导到CPU。

4. CPU感知到中断跳转固定中断向量表位置执行(这个是硬件约定,汇编语言固定在某个位置写定代码)。这个时候中断会跳转到中断处理分支开始执行中断处理程序。

5. 中断处理程序首先保存现场,然后分辨中断源处理中断,最后回复现场。

2. 处理器模式

CPU的模式可以简单的理解为当前CPU的工作状态,比如:当前操作系统正在执行用户程序,那么当前CPU工作在用户模式,这时网卡上有数据到达,产生中断信号,CPU自动切换到一般中断模式下处理网卡数据(普通应用程序没有权限直接访问硬件),处理完网卡数据,返回到用户模式下继续执行用户程序。

特权模式,除用户模式外,其它模式均为特权模式(Privileged Modes)。ARM  内部寄存器  和一些  片内外设  在硬件设计上只允许(或者可选为只允许)特权模式下访问。此外,特权模式可以自由的切换处理器模式,而用户模式不能直接软件切换到别的模式。

异常模式,特权模式中除系统(system)模式之外的其他5种模式又统称为异常模式。它们除了可以通过在特权下的程序切换进入外,也可以由特定的异常进入。比如硬件产生中断信号进入中断异常模式,读取没有权限数据进入中止异常模式,执行未定义指令时进入未定义指令中止异常模式。其中管理模式也称为超级用户模式,是为操作系统提供软中断的特有模式,正是由于有了软中断,用户程序才可以通过系统调用切换到管理模式。

2.1. 7种工作模式介绍

(1)用户模式

用户模式是用户程序的工作模式,它运行在操作系统的用户态,它没有权限去操作其它硬件资源,只能执行处理自己的数据,也不能切换到其它模式下,要想访问硬件资源或切换到其它模式只能通过软中断或产生异常。

(2)系统模式

系统模式是特权模式,不受用户模式的限制。用户模式和系统模式共用一套寄存器,操作系统在该模式下可以方便的访问用户模式的寄存器,而且操作系统的一些特权任务可以使用这个模式访问一些受控的资源。

 说明:用户模式与系统模式两者使用相同的寄存器,都没有SPSR(Saved Program Statement Register,已保存程序状态寄存器),但系统模式比用户模式有更高的权限,可以访问所有系统资源。

(3)一般中断模式

一般中断模式也叫普通中断模式,用于处理一般的中断请求,通常在硬件产生中断信号之后自动进入该模式,该模式为特权模式,可以自由访问系统硬件资源。

(4)快速中断模式

快速中断模式是相对一般中断模式而言的,它是用来处理对时间要求比较紧急的中断请求,主要用于高速数据传输及通道处理中。一般在单片机分秒必争的时候会使用这种中断模式。

(5)管理模式(Supervisor,SVC) :

管理模式是CPU上电后默认模式,因此在该模式下主要用来做系统的初始化,软中断处理也在该模式下。当用户模式下的用户程序请求使用硬件资源时,通过软件中断进入该模式。

说明:系统复位或开机、软中断时进入到SVC模式下。

(6)终止模式

中止模式用于支持虚拟内存或存储器保护,当用户程序访问非法地址,没有权限读取的内存地址时,会进入该模式,linux下编程时经常出现的segment fault通常都是在该模式下抛出返回的。

(7)未定义模式

未定义模式用于支持硬件协处理器的软件仿真,CPU在指令的译码阶段不能识别该指令操作时,会进入未定义模式。

2.3. 硬件资源

在 不同的工作模式下面,芯片内部有不同类型的寄存器资源可以利用。其中在特权模式r13(SP)用作栈指针,r14(LR)保存异常发生时对应的地址,方便处理完数据以后返回结果。下面SPARM 26(和以后)的 32 位模式下可获得的寄存器有:

User26   SVC26    IRQ26    FIQ26      User     SVC      IRQ      ABT      UND      FIQ

R0 ----- R0 ----- R0 ----- R0 --   -- R0 ----- R0 ----- R0 ----- R0 ----- R0 ----- R1
R1 ----- R1 ----- R1 ----- R1 --   -- R1 ----- R1 ----- R1 ----- R1 ----- R1 ----- R2
R2 ----- R2 ----- R2 ----- R2 --   -- R2 ----- R2 ----- R2 ----- R2 ----- R2 ----- R2
R3 ----- R3 ----- R3 ----- R3 --   -- R3 ----- R3 ----- R3 ----- R3 ----- R3 ----- R3
R4 ----- R4 ----- R4 ----- R4 --   -- R4 ----- R4 ----- R4 ----- R4 ----- R4 ----- R4
R5 ----- R5 ----- R5 ----- R5 --   -- R5 ----- R5 ----- R5 ----- R5 ----- R5 ----- R5
R6 ----- R6 ----- R6 ----- R6 --   -- R6 ----- R6 ----- R6 ----- R6 ----- R6 ----- R6
R7 ----- R7 ----- R7 ----- R7 --   -- R7 ----- R7 ----- R7 ----- R7 ----- R7 ----- R7
R8 ----- R8 ----- R8       R8_fiq     R8 ----- R8 ----- R8 ----- R8 ----- R8       R8_fiq
R9 ----- R9 ----- R9       R9_fiq     R9 ----- R9 ----- R9 ----- R9 ----- R9       R9_fiq
R10 ---- R10 ---- R10      R10_fiq    R10 ---- R10 ---- R10 ---- R10 ---- R10      R10_fiq
R11 ---- R11 ---- R11      R11_fiq    R11 ---- R11 ---- R11 ---- R11 ---- R11      R11_fiq
R12 ---- R12 ---- R12      R12_fiq    R12 ---- R12 ---- R12 ---- R12 ---- R12      R12_fiq
R13      R13_svc  R13_irq  R13_fiq    R13      R13_svc  R13_irq  R13_abt  R13_und  R13_fiq
R14      R14_svc  R14_irq  R14_fiq    R14      R14_svc  R14_irq  R14_abt  R14_und  R14_fiq
--------- R15 (PC / PSR) ---------    --------------------- R15 (PC) ---------------------
                                      ----------------------- CPSR -----------------------
                                               SPSR_svc SPSR_irq SPSR_abt SPSR_und SPSR_fiq

2.4. CPSR/SPSR寄存器

当前程序状态寄存器,mode给地当前模式状态,并且可以修改让CPU进入对应程序运行模式。对应的编码格式可以查看芯片硬件手册。条件编码标志位可以用来表示对应的汇编代码运行状态,

SPSR寄存器用来保存异常产生时,当前正在运行的模式下CPSR寄存器值,用户模式没有这个寄存器。

中断屏蔽与状态:控制寄存器中还存在一个状态为,和两个中断屏蔽位,这两个中断屏蔽位确定了当前内核是否相应外部中断信号。

状态与指令集:其中J(Jazelle)和T(Thumb)位反映了处理器的状态。当T位和J位都是0时,处理器处于ARM状态,执行ARM指令;当T置位,处理器处于Thumb状态。

条件标志:在后面ARM会汇编指令中会大量使用到这里的标志位,对应的条件指令可以通过英文确定其具体的含义。在ARM的条件执行指令中(EQ)这种类型的条件处理时,其真假结果确定了当前这条慧琳是否被执行。也就是说在执行前,处理器比较条件属性和cpsr的条件标志位,如果匹配指令就会被执行,否则指令就会被忽略。当然比方说teq这种指令其比较结果会决定下一条指令是否执行。

2.5. r15(PC)

用来表示当前指针所在的位置,即在内存中当前程序所在的地址位置。

中断产生时硬件会做哪些动作?

1. LR寄存器保存当前中断的下一条指令地址,PC+4、PC+8

2. 搬移CPSR寄存器值到SPSR寄存器

3. 修改CPSR寄存器模式,进入异常模式

4. PC值修改跳转向量表入口执行

异常程序返回执行原来程序模式

1. PC=LR减去某个值

2. SPSR赋值会CPSR寄存器

3. 最后清理中断

除了如上的寄存器资源外,CPU通过地址线访问外部的内存空间memory,这部分内存空间包含了指令也包含了数据。结合在一起,CPU通过PC指针在命令空间移动,通过命令解析将数据从数据内存空间获取,然后通过内部的寄存器进行计算以后将数据输出到外部数据内存空间当中。全局编制的情况下,我们可以认为所有的I/O设备等都是内存数据空间。

首先,ARM开发板在刚上电或复位后都会首先进入SVC即管理模式,此时、程序计数器R15-PC值会被赋为0x0000 0000;bootloader就是在此模式下,位于0x0000 0000的NOR FLASH或SRAM中装载的,因此、开机或重启后bootloader会被首先执行。
    接着,bootloader引导Linux内核,此时、Linux内核一样运行在ARM的SVC即管理模式下;当内核启动完毕、准备进入用户态init进程时,内核将ARM的当前程序状态CPSR寄存器M[4:0]设置为10000、进而用户态程序只能运行在ARM的用户模式。
    由于ARM用户模式下对资源的访问受限,因此、可以达到保护Linux操作系统内核的目的。
    需要强调的是:Linux内核态是从ARM的SVC即管理模式下启动的,但在某些情况下、如:硬件中断、程序异常(被动)等情况下进入ARM的其他特权模式,这时仍然可以进入内核态(因为就是可以操作内核了);同样,Linux用户态是从ARM用户模式启动的,但当进入ARM系统模式时、仍然可以操作Linux用户态程序(进入用户态,如init进程的启动过程)。
    即:Linux内核从ARM的SVC模式下启动,但内核态不仅仅指ARM的SVC模式(还包括可以访问内核空间的所有ARM模式);Linux用户程序从ARM的用户模式启动,但用户态不仅仅指ARM的用户模式。

2.2. 工作状态

arm处理器包含了32为和16位两种工作状态指令集模式。其中,ARM状态32位,ARM状态执行字对齐的32位ARM指令;Thumb状态,16位,执行半字对齐的16位指令。汇编通过用Bx Rn指令来进行两种状态的切换:其中Bx是跳转指令,而Rn是寄存器(1个字,32位),如果Rn的位0为1,则进入Thumb状态;如果Rn的位为0,这进入ARM状态。(原 因:ARM指令的后两位始终为0,没有用,而Thumb指令的后一位始终为0,没有用,因此采用位0来表示ARM指令与Thumb指令的切换标志位。)

工作状态只是命令的实行方式,ARM和Thumb两种状态之间的切换不影响处理器的工作模式和寄存器的内容。ARM处理器在处理异常时,不管处理器处于什么状态,则都将切换到ARM状态。ARM的M系列主要用Thumb指令,ARM9和A系列主要用ARM指令。S3C2440.S启动代码中根本就没用Thumb指令。

也就是对于同样一条mov r0, r1,在ARM指令集下面是4个字节表示,在Thumb指令集下面这条指令是用2个指令。在单片机下面,硬件flash空间寸土寸金,这个时候thumb会进一步减少代码指令的空间。当然thumb指令集没有没有办法单独存在,一般情况下是结合部分ARM指令集实现功能。

ARM芯片在初始化启动是芯片会处于SVC系统管理模式下,如果存在uboot情况下会引导内核切换到用户模式。如果用户模式程序通过软件中断的方式进入到系统模式,ARM在探测到这种中断发生会自动从user模式切换到svc模式。因此,在user模式不能通过代码手动切换到其他任何模式,而其他特权模式可以任意切换同时可以切换到用户模式。对于其他类型的异常会对应的将ARM芯片切换到对应的异常工作模式,切换过程是芯片遇到某种类型的异常时自动产生。

不同用户模式下面寄存器资源的访问形式和数量都有差别,同时不同用户模式下面硬件的访问权限也是不一样的。尤其为了防止用户直接进行硬件行为的修改,user模式对硬件的操作往往没有办法直接操作,同时硬件也会支持不同的用户模式访问行为。

用户空间的时候,处于user的工作模式,访问内存地址的时候,MMU会检查页表的权限;同时,内核空间对应ARM的特权模式,访问内存地址的时候,MMU也会检查页表的权限。这个时候页表的访问权限就起作用了。
具体可以看下面这个表:AP是页表的访问权限项,S和R是CP15的C1系统控制寄存器的两个特性位,后面是分别在特权模式和用户模式下的访问权限。
AP S R Privileged permissions User permissions
0 0 0 0 No access No access
0 0 1 0 Read-only No access
0 0 0 1 Read-only Read-only
0 0 1 1 Unpredictable Unpredictable
0 1 x x Read/write No access
1 0 x x Read/write Read-only
1 1 x x Read/write Read/write

CPU工作模式、寄存器资源、外部内存资源确定下来以后,后面给出ARM处理器常用的汇编指令进行说明,实际工作中遇到具体的问题,直接查看命令手册即可。

3. 异常向量表

异常向量表基本实验

高低电平触发,(边沿触发)上升沿触发和下降沿触发 中断区别

外部中断可以分为电平触发和边缘触发两种,那么这两种中断有什么区别。中断基本概念是:

  1. CPU在处理某一事件A时,发生了另一事件B请求CPU迅速去处理(中断发生);
  2. CPU暂时中断当前的工作,转去处理事件B(中断响应和中断服务);

待CPU将事件B处理完毕后,再回到原来事件A被中断的地方继续处理事件A(中断返回),这一过程称为中断。通俗点说:你正在家里做家务,突然有人来找你,打断了你的进程,在你们交谈完之后,你就又回去继续进行你的家务活,这其中被打断的过程,就叫做中断,而在中断结束之后,你则会继续进行本来应该做的事情

1. 高低电平触发:

1.1 低电平触发:

低电平触发中断顾名思义,就是检测到引脚为低电平就触发,从而进入中断函数中处理这个中断,并且在低电平保持的时间内持续触发。假设是低电平触发,只要引脚为低电平时间内中断一直有效,那么就会一直进入中断,直到电平变化为高电平。

1.2 高电平触发:

则是检测为高电平就触发,其余与低电平触发相同

注意事项:

1.电平触发中断,如果在电平没有恢复之前就退出中断程序,那么会在退出后又再次进入中断。只要不退出是不会重复触发的。也就是重复触发只有在退出中断后才会再次触发,不用担心这次还没进行完,中断已经重新触发的情况

2.低电平触发是即时的,当外部中断信号撤消时,中断申请信号随之消失。如果在外部中断信号申请期间,CPU来不及响应此中断,那么有可能这次中断申请就漏掉了。也就是说假设低/高电平的时间很短。CPU没来得及相应,那么这次的电平中断申请就可能不会检测到

3.如果想要电平触发中断也只进行一次,通常的做法是在中断进入后就关闭中断响应,等后面需要的时候再打开。

2. 边沿触发

这里要先了解下:从低电平到高电平,  叫做上升;从高电平到低电平, 叫做下降

2.1 上升沿触发

数字电平从低电平(数字“0”)变为高电平(数字“1”)的那一瞬间叫作上升沿。 上升沿触发是当信号有上升沿时的开关动作,当电位由低变高而触发输出变化的就叫上升沿触发。也就是当测到的信号电位是从低到高也就是上升时就触发,叫做上升沿触发。

2.2 下降沿触发

数字电路中,数字电平从高电平(数字“1”)变为低电平(数字“0”)的那一瞬间叫作下降沿。 [1]  下降沿触发是当信号有下降沿时的开关动作,当电位由高变低而触发输出变化的就叫下降沿触发。也就是当测到的信号电位是从高到低也就是下降时就触发,叫做下降沿触发。

那么我们可以很好的理解两种触发:上升沿触发 就是当电压从低变高时触发中断;下降沿触发 就是当电压从高变低时触发中断。

当然,上升沿与下降沿检测的是电平变化的一瞬间,就会产生中断,这个时间是us级别的,但是如果中断引脚检测到一直保持低/高电平,则无法产生下次中断,也就是中断只会触发一次,只有在下次电平发生变化时才会重新触发中断。

还需要注意的是中断在上升或者下降沿的时候进行中断检查,检查过程是通用的数据信号采集检测原理。硬件设置的检测周期可能是有所变化的,如果在检测时间周期内(比方说3个时间周期,us级别)发现待检测信号的梯度变化不足够大,这种低端相应的硬件可能无法触发硬件边沿中断信号。

注意事项:

1 边沿触发就是单片机在上一次机器周期内,检测到中断引脚口为高电平,这一次机器周期内检测到为低电平,则会申请产生中断,所以为us级别

2 下降沿触发是锁存中断信号的,由D触发器记忆,意即:即使当时CPU来不及响应中断,外部中断信号撤消后,由于D触发器的记忆作用,消失的中断信号仍然有效,直到中断被响应并进入中断ISR,记忆的中断信号才会由硬件清除。 这也是为什么边沿触发只能触发一次的原因。

3.对于单片机的中断引脚,如果你另一端接的是VCC 则需要设置成上升沿或者高电平触发 如果你接的是GND 就可以设置成下降沿或者低电平触发

区别:我们可以理解,电平触发在你一直按着按键的时候会一直进入中断,边沿触发则是只会触发一次,再次按下才会重新触发,这就给我们不同的应用功能提供了选择,使得我们可以在不同个工作下选择适合的模式,边沿触发适用于对对时间要求高的,比如中断中有计数之类的(GATE门控位置1时),而电平触发则适合报警装置。

3. 操作系统实现

初始化中断向量表时,有一个重要的操作就是set_irq_handler。这个函数参数一般为handle_edge_irq或者handle_level_irq。对于边缘中断,使用handle_edge_irq作参数;对于电平中断,使用handle_level_irq作为中断。那这两个函数有什么区别呢?

3.1 handle_edge_irq

kernel源代码中的注释为:

c代码

/**
* handle_edge_irq - edge type IRQ handler
* @irq: the interrupt number
* @desc: the interrupt description structure for this irq
*
* Interrupt occures on the falling and/or rising edge of a hardware
* signal. The occurence is latched into the irq controller hardware
* and must be acked in order to be reenabled. After the ack another
* interrupt can happen on the same source even before the first one
* is handled by the assosiacted event handler. If this happens it
* might be necessary to disable (mask) the interrupt depending on the
* controller hardware. This requires to reenable the interrupt inside
* of the loop which handles the interrupts which have arrived while
* the handler was running. If all pending interrupts are handled, the
* loop is left.
*/

下面翻译为中文:

中断发生在硬件信号的上升沿/下降沿。中断事件保存在中断控制器中并且必须被立刻响应以重新打开中断。响应之后,即使第一个中断还没有被相关的中断函数处理完,另一个中断也可能在同一个中断源发生。如果这种情况发生的话就有必要关闭中断。这样,在中断处理函数运行时发生的中断就会被标记为阻塞,在中断处理循环处理完这些阻塞的中断之后,就有必要在循环内部重新打开中断。如果全部的阻塞中断处理完毕,就可以离开中断处理循环。

响应中断条件是:中断使能中断标志同时成立时。一般来讲,响应中断后,有硬件清标志软件清标志两种.(如果硬件不能清标志,说明书会说明)。单片机要靠查询中断标志来判断是否要进入中断,如果你不清除中断标志,本次中断退出,单片机又会检测到中断标志,因此重复进入中断。当然,这样情况中断后面响应过来的硬件中断系统也是没有办法做出处理的,会被挡在外边。

3.2 handle_level_irq

c代码

/**
* handle_level_irq - Level type irq handler
* @irq: the interrupt number
* @desc: the interrupt description structure for this irq
*
* Level type interrupts are active as long as the hardware line has
* the active level. This may require to mask the interrupt and unmask
* it after the associated handler has acknowledged the device, so the
* interrupt line is back to inactive.
*/

一旦硬件线路达到了激活电平,电平类型的中断就会被触发。这可能需要屏蔽中断,并且在相关的中断处理程序响应了设备之后重新打开中断,这样中断线才能恢复反激活状态。

对比上述两个函数可见,对于电平中断,一进入中断处理程序就会立刻屏蔽中断,直到退出中断的时候才会unmask中断;而对于边缘中断,除非在处理当前中断时有一个新的中断被触发否则不会屏蔽中断。这两个处理函数的特点刚好和这两种中断的特点一致:对于电平中断,只要中断脚在激活电平电平,就会不断地触发中断;而边缘中断只在上升沿或者下降沿被触发。

下面是linux 2.6.32版本kernel的代码。

//觉得很奇怪,既然电平中断一开始就mask了中断,
//退出的时候才unmask,怎么会与同类型的其他中断冲突呢?

c代码


void
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
struct irqaction *action;
irqreturn_t action_ret; 

spin_lock(&desc->lock);
//一开始以为对于电平中断ack不会被调用,但是发现
//mask_ack_irq会间接调用ack
mask_ack_irq(desc, irq); 

if (unlikely(desc->status & IRQ_INPROGRESS))
goto out_unlock;
desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
kstat_incr_irqs_this_cpu(irq, desc); 

/*
* If its disabled or no action available
* keep it masked and get out of here
*/
action = desc->action;
if (unlikely(!action || (desc->status & IRQ_DISABLED)))
goto out_unlock; 

desc->status |= IRQ_INPROGRESS;
spin_unlock(&desc->lock); 

action_ret = handle_IRQ_event(irq, action);
if (!noirqdebug)
note_interrupt(irq, desc, action_ret); 

spin_lock(&desc->lock);
desc->status &= ~IRQ_INPROGRESS; 

if (unlikely(desc->status & IRQ_ONESHOT))
desc->status |= IRQ_MASKED;
else if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask)
desc->chip->unmask(irq);
out_unlock:
spin_unlock(&desc->lock);
}
EXPORT_SYMBOL_GPL(handle_level_irq); 

void
handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
spin_lock(&desc->lock); 

desc->status &= ~(IRQ_REPLAY | IRQ_WAITING); 

/*
* If we're currently running this IRQ, or its disabled,
* we shouldn't process the IRQ. Mark it pending, handle
* the necessary masking and go out
*/
//正如注释中所说,在处理一个中断时下一个同中断源的中断会被标记为阻塞,
//然后屏蔽这个中断源的中断。注意:只有在这个时候才会屏蔽中断,否则
//linux不会做无谓的屏蔽。
if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||
!desc->action)) {
desc->status |= (IRQ_PENDING | IRQ_MASKED);
mask_ack_irq(desc, irq);
goto out_unlock;
}
kstat_incr_irqs_this_cpu(irq, desc); 

/* Start handling the irq */
if (desc->chip->ack)
desc->chip->ack(irq); 

/* Mark the IRQ currently inprogress.*/
desc->status |= IRQ_INPROGRESS; 

do {
struct irqaction *action = desc->action;
irqreturn_t action_ret; 

if (unlikely(!action)) {
desc->chip->mask(irq);
goto out_unlock;

/*
* When another irq arrived while we were handling
* one, we could have masked the irq.
* Renable it, if it was not disabled in meantime.
*/
//因为这时中断处理函数没有运行,所以可以unmask中断
if (unlikely((desc->status &
(IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
(IRQ_PENDING | IRQ_MASKED))) {
desc->chip->unmask(irq);
desc->status &= ~IRQ_MASKED;

desc->status &= ~IRQ_PENDING;
spin_unlock(&desc->lock);
action_ret = handle_IRQ_event(irq, action);
if (!noirqdebug)
note_interrupt(irq, desc, action_ret);
spin_lock(&desc->lock); 

} while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING); 

desc->status &= ~IRQ_INPROGRESS;
out_unlock:
spin_unlock(&desc->lock);
}

;