前言
中断对于单片机过来的我们来说,相对也算比较熟悉了,还是严谨一点从头开始说吧。中断是什么?是一种当满足要求的突发事件发生时通知处理器进行处理的信号。中断可以由硬件处理单元和外部设备产生,也可以由软件本身产生。对硬件来说,中断信号是一个由某个处理单元产生的异步信号,用来引起处理器的注意。对软件来说,中断还是一种异步事件,用来通知处理器需要改变代码的执行,当然,轮询所产生的中断的过程是同步的。
当处理器收到中断,它会停下当前正在做的任务,然后跳转到需要处理的地方去。这和轮询的方式是相反的,轮询是由软件同步获取设备的状态。在中断方式中,不需要由处理器不断地轮询设备的 I/O 端口来查看是否需要处理,设备本身会中断处理器。
(1)ARM体系中,在存储地址的低位,固化了一个32字节的硬件中断向量表。
(2)异常中断发生时,程序计数器PC所指的位置不同,异常中断就不同。中断结束后,中断不同,返回地址也不同。但是,对于系统复位中断,不需要返回,因为整个应用系统就是从复位中断中开始的。
数据访问终止:数据访问的地址不存在,或者当前地址不允许访问。
快速中断请求:外部引脚的快速中断请求,比外部中断请求等级高,但是一般外设的中断请求使用外部中断请求。
指令预取终止:预取指令的地址不存在,或者当前地址不允许访问。
未定义的指令:ARM或协处理器认为当前指令未定义。
ARM中断流程:
首先在主程序中发生IRQ中断请求,程序跳到中断向量表找IRQ中断对应的解析程序地址,然后再跳到中断解析程序,进而执行中断程序。
zynq的中断
那具体到zynq的中断呢,先简单概括下吧(起码先看见这些缩写起码不再头疼)
Zynq的中断类型有:
软件中断(Software Generated Interrupt, SGI,中断号0-15)(16–26 reserved)
私有外设中断(Private Peripheral Interrupt, PPI,中断号27-31),
共享外设中断(Shared Peripheral Interrupt, SPI,中断号32-95).
私有外设中断(PPI):每个CPU都有一组PPI,包括全局定时器、私有看门狗定时器、私有定时器和来自PL的FIQ/IRQ.
软件中断(SGI)被路由到一个或者两个CPU上,通过写ICDSGIR寄存器产生SGI.
共享外设中断(SPI)由PS和PL上的各种I/O控制器和存储器控制器产生,这些中断信号被路由的CPU.
通用中断控制器(GIC)是核心资源,用于集中管理从PS和PL产生的中断信号的资源集合。控制器可以使能、关使能、屏蔽中断源和改变中断源的优先级,并且会将中断送到对应的CPU中,CPU通过私有总线访问这些寄存器。
中断控制器(ICC,Interrupt Controller CPU)和中断控制器分配器(ICD, Interrupt Controller Distributor)是GIC寄存器子集。
(外部)中断请求(IRQ)、快速中断请求(FIQ)
接下来详细解释一下,中断(主要是硬件中断)可以进一步被分类为以下几种类型:
• 可屏蔽中断( Maskable Interrupts,IRQ)—可通过在中断屏蔽寄存器中设定位掩码来关闭。触发可屏蔽中断的事件源不总是重要的。程序设计人员需要决定该事件是否应该导致程序跳到所需处理的地方去。使用可屏蔽中断的设备包括定时器、比较器和 ADC。• 不可屏蔽中断( Non-Maskable Interrupts,NMI)—无法通过在中断屏蔽寄存器中设定位掩码来关闭。这些是不可忽视的中断。 NMI 的事件包括上电、外部重启(用实际的按钮)和严重的设备失效。
• 处理器间中断( Inter-Processor Interrupts,IPI)—在多处理器系统中,一个处理器可能需要中断另一个处理器的操作。在这种情况下,就会产生一个 IPI,以便于处理器间通信或同步。Zynq 芯片的 PS 部分是基于使用双核 Cortex-A9 处理器和 GIC pl390 中断控制器的 ARM 架构。中断结构与 CPU 紧密链接,并接受来自 I/O 外设( IOP)和可编程逻辑( PL)的中断。中断控制器架构如下图所示(感谢原子将官方手册一些不太好理解的做了个相对非常清晰的框图和汉化):
从图中可以看到, CPU 接收的中断来源有三种,分别是私有外设中断( private peripheral interrupts, PPI)、软件生成的中断( software generated interrupts, SGI)和共享外设中断( shared peripheral interrupts、 SPI)。每个 CPU 都有一组私有外设中断,使用存储寄存器进行私有访问。 PPI 包括全局定时器、专用看门狗定时器( AWDT)、专用定时器和来自 PL 的 FIQ/IRQ。软件生成的中断通过 SGI 分配(派)器分配给一个或两个 CPU。共享外设中断由 PS 和 PL 中的各种 I/O 和存储控制器生成。它们被路由到任一个或两个 CPU,来自 PS 外设的 SPI 中断也可以路由到 PL。下面我们从下图的系统级中断环境来进一步了解中断
首先我们来看通用中断控制器。通用中断控制器是一个用于集中管理从 PS 和 PL 发送到 CPU 的中断,启用、禁用、屏蔽和优先化中断源的处理中心,将具有最高优先级的中断源分配给各个 CPU 之前集中所有中断源,并在 CPU 接口接受下一个中断时以编程方式将它们发送到选定的 CPU。此外,控制器还支持用于实现安全感知系统的安全扩展。该控制器基于非矢量化的 ARM 通用中断控制器架构版本 1.0( GIC v1)。GIC 寄存器通过 CPU 私有总线访问寄存器,以避免临时阻塞或互连中的瓶颈,从而实现快速读/写响应。GIC 确保针对多个 CPU 的中断一次只能由一个 CPU 占用。所有中断源都由唯一的中断 ID 号标识,对应有它自己的可配置优先级和目标 CPU 列表。接下来我们依次来看软件生成中断、 CPU 私有外设中断和共享外设中断。每个 CPU 都可以使用软件生成的中断中断自身、另一个 CPU 或同时中断两个 CPU。有 16 个软件生成中断。向软件产生的中断寄存器( Software Generated Interrupts Register, ICDSGIR)写入SGI 中断编号并指定目标 CPU(或两个 CPU),就产生了一个 SGI。该写操作通过 CPU 自己的专用(私有)总线进行。每个 CPU 都有自己的一组 SGI 寄存器,用于生成 16 个软件生成的中断中的一个或多个。中断的清除是通过读取中断确认寄存器( Interrupt Acknowledge Register, ICCIAR)或向中断挂起清除寄存器( Interrupt Clear-Pending Register, ICDICPR)对应的位写入1来实现的。所有的 SGI 都是边缘触发的,且其敏感性类型是固定的,不能修改。只读的 ICDICFR0 寄存器指定了所有 16 个 SGI 的灵敏度类型。
翻译下英文部分:每个 CPU 有自己专用的一组 16 个中断源,可以被路由到 16 个公共中断目标,每个目标可以是一个或多个 CPU。
每个 CPU 核连接到了一个有五个外设中断的私有组上,这五个外设中断见下表。 PPI 的敏感类型是固定的,不能改变。需要注意的是:来自 PL 的快速中断( FIQ)信号和中断( IRQ)信号在发送给中断控制器之前,会在传输给 PS 的时候被反转。因此,这些信号因此在 PL 内低电平有效,在 PS-PL 接口处高电平有效
来自各种模块的大约 60 个中断的组可以被路由到 PL 或 CPU 中的一个或两个,这 60 个中断见下表。那些目标为 CPU 的中断的优先级和中断的接收情况是由中断控制器管理的。除 IRQ# 61 至# 68 和# 84 至# 91 外,所有中断灵敏度类型均由请求源固定,无法更改。必须对 GIC 进行编程以适应这种情况。 Boot ROM不编程这些寄存器。因此, SDK 设备驱动程序必须对 GIC 进行编程以适应这些敏感类型。对于电平敏感类型的中断,请求源必须为中断处理程序提供一种机制,以便在确认中断后清除中断。此要求适用于具有高电平敏感类型的任何 IRQF2P [n](来自 PL)。对于上升沿敏感的中断,请求源必须提供足够宽的脉冲以便 GIC 捕获。这通常至少为 2 个 CPU_2x3x 周期。此要求适用于具有上升沿灵敏度类型的任何 IRQF2P [n](来自 PL)。
了解了软件生成中断 SGI、 CPU 私有外设中断 PPI 和共享外设中断 SPI 后,我们来看下中断优先级定序。所有的中断请求,无论是 PPI、 SGI 还是 SPI,都分配了一个唯一的 ID 编号,以用于中断控制器的仲裁。中断分派(配)器保存每个 CPU 的中断挂起列表,并从中选择优先级最高的中断,然后把它发送到 CPU接口。如果具有相同优先级的两个中断同时到达,具有最低中断 ID 的会首先被发送。每个 CPU 都存在着优先级定序逻辑,所以对最高优先级中断的选择是每个 CPU 各自进行的。中断分配器具有中断、处理器和活跃信息的中央列表,并负责触发 CPU 的软件中断。为了给每个处理器提供单独的副本, SGI 和 PPI 分派器寄存器是分组的。硬件确保针对多个 CPU 的中断同一时间只能被一个 CPU 获取。在发送挂起的最高优先级的中断给 CPU 接口后,中断分配器会从该 CPU 收到中断已被确认的消息,这样它就可以改变对应的中断的状态。只有确认中断的 CPU 才能结束该中断。
一.MIO作GPIO中断(填坑GPIO篇)
以上我们大概了解了 ZYNQ 的中断。下面我们来看作为 GPIO 的 MIO 的中断。
左边有 7 个寄存器,说明如下:
INT_MASK:这个寄存器是只读的,显示哪些位当前被屏蔽,哪些位未被屏蔽/启用。
INT_EN:向该寄存器的任何位写入 1,可以启用/解除中断信号的掩码。从该寄存器读取将返回一个不可预测的值。
INT_DIS:向该寄存器的任何位写入 1 都会屏蔽该中断信号。从该寄存器读取会返回不可预测的值。
INT_STAT:该寄存器显示是否发生了中断事件。将 1 写入该寄存器中的某个位可清除该位的中断状态。将 0 写入该寄存器中的某个位将被忽略。
INT_TYPE:该寄存器控制中断是边沿敏感还是电平敏感。
INT_POLARITY:该寄存器控制中断是低电平有效还是高电平有效(或下降沿敏感或上升沿敏感)。
INT _ANY:如果 INT_TYPE 设置为边沿敏感,则该寄存器在上升沿和下降沿都会启用中断事件。如果INT_TYPE 设置为电平敏感,则忽略该寄存器。
从 INT_TYPE、 INT_POLARITY 和 INT _ANY 寄存器我们可以看到中断触发方式可以是上升沿,下降沿,边沿,低电平或高电平。
从图中我们可以看到, INT_TYPE、 INT_POLARITY 和 INT _ANY 控制监视 GPIO 输入信号的中断检测逻辑。如果检测到中断,中断检测逻辑将 GPIO 的 INT_STAT 状态设置为真。如果中断未屏蔽,则中断传输到一个或电路(图中未画出)。该或电路将四个 BANK 中所有 GPIO 的所有中断组合成一个输出( IRQID# 52)到中断控制器。如果中断被禁止(屏蔽),则 INT_STAT 状态将保持直到被清除,但它不会传输到中断控制器,除非稍后写入 INT_EN 以禁用屏蔽。由于所有 GPIO 共享相同的中断,因此软件必须同时考虑INT_MASK 和 INT_STAT 以确定哪个 GPIO 导致中断
通过向 INT_EN 和 INT_DIS 寄存器写入 1 来控制中断屏蔽状态。向 INT_EN 寄存器写入 1 将禁用屏蔽,允许活动中断传输到中断控制器。将 1 写入 INT_DIS 寄存器可启用屏蔽。可以使用 INT_MASK 寄存器读取中断屏蔽的状态。如果 GPIO 中断是边沿触发的,则 INT 状态由检测逻辑锁存。通过向 INT_STAT 寄存器写入 1 来清除INT 锁存器。对于电平触发的中断,必须清零 GPIO 中断输入源,以清除中断信号。或者,软件可以使用INT_DIS 寄存器屏蔽该输入。可以通过读取 INT_STAT 和 INT_MASK 寄存器来推断进入中断控制器的中断信号的状态。如果INT_STAT = 1 且 INT_MASK = 0,则该中断信号有效。
下面上实例,工程中我们用AXIDMA来搬移数据(详细使用日常在AXIDMA篇),由于16个软件中断已经不够用了,所以我们将中断挂在GPIO上(EMIO)来判断是否有输入:
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID//通用中断控制器 ID #define INTC XScuGic #define INTC_HANDLER XScuGic_InterruptHandler static XGpioPs Gpio; /* The Instance of the GPIO Driver */ static INTC Intc; /*通用中断控制器驱动实例 */ */ int SetupInterruptSystem(XScuGic *GicInstancePtr, XGpioPs *Gpio, u16 GpioIntrId) /*官方GPIO例程调用,嗯xilinx 牛逼! */ { int Status; XScuGic_Config *IntcConfig; /* Instance of the interrupt controller */ Xil_ExceptionInit();/* 该函数是一个用于初始化异常处理程序的通用API跨所有支持的arm处理器这个函数不做任何事情但是,它仍然存在,以确保向后兼容性问题 */ /* * 初始化中断控制器 */ IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID); if (NULL == IntcConfig) { return XST_FAILURE; } Status = XScuGic_CfgInitialize(GicInstancePtr, IntcConfig, IntcConfig->CpuBaseAddress); if (Status != XST_SUCCESS) { return XST_FAILURE; } /* * 为特定的异常注册一个处理程序。这个处理器正在当处理器遇到指定的异常时调用。 * Connect the interrupt controller interrupt handler to the hardware * interrupt handling logic in the processor. */ */ Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, GicInstancePtr); /* * Connect the device driver handler that will be called when an * interrupt for the device occurs, the handler defined above performs * the specific interrupt processing for the device. */ Status = XScuGic_Connect(GicInstancePtr, GpioIntrId, (Xil_ExceptionHandler)XGpioPs_IntrHandler, (void *)Gpio); if (Status != XST_SUCCESS) { return Status; } /* 下面就是需要根据这边的实际情况来修改的部分了 Enable falling edge interrupts for all the pins in bank 0. */ XGpioPs_SetIntrType(Gpio, 2, 0x00, 0xFFFFFFFF, 0x00); /* Set the handler for gpio interrupts. */ XGpioPs_SetCallbackHandler(Gpio, (void *)Gpio, IntrHandler); /* Enable the GPIO interrupts of Bank 2. */ XGpioPs_IntrEnable(Gpio, 2, 0xFF); /* Enable the interrupt for the GPIO device. */ XScuGic_Enable(GicInstancePtr, GpioIntrId); /* Enable interrupts in the Processor. */ Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ); return XST_SUCCESS; } static void IntrHandler(void *CallBackRef, u32 Bank, u32 Status) /* * 好家伙也是直接从官方例程搬了来改,读bank2部分,哪个bit来了就处理那一部分 */ { XGpioPs *Gpio = (XGpioPs *)CallBackRef; u32 DataRead = 0;; //DataRead = XGpioPs_ReadPin(Gpio, Input_Pin); DataRead = XGpioPs_Read(Gpio, 2); //直接读bank2,判断哪一位来了中断 if ((DataRead & 0x1) == 0x01) { TxIntrHandler(&AxiDma); } if((DataRead & 0x2) == 0x02) { RxIntrHandler(&AxiDma); } if ((DataRead & 0x4) == 0x04) { TxIntrHandler(&AxiDma1); } if((DataRead & 0x8) == 0x08) { RxIntrHandler(&AxiDma1); } if ((DataRead & 0x10) == 0x010) { TxIntrHandler(&AxiDma2); } if((DataRead & 0x20) == 0x20) { RxIntrHandler(&AxiDma2); } if ((DataRead & 0x40) == 0x40) { TxIntrHandler(&AxiDma3); } if((DataRead & 0x80) == 0x080) { RxIntrHandler(&AxiDma3); } else { } }
二.AXI GPIO作GPIO中断
(填坑GPIO篇,由于这部分还没实际用到,逻辑大哥没给配的时候,只能理论上做个分析了,原子真香!比我这东拼西凑的不论整体设计还是清晰地注释那高了不止一个层次,反复观摩学习)
直接嫖一波代码,写的简洁易懂,是用AXIGPIO做了个按键控制LED的功能
1 #include "stdio.h" 2 #include "xparameters.h" 3 #include "xgpiops.h" 4 #include "xgpio.h" 5 #include "xscugic.h" 6 #include "xil_exception.h" 7 #include "xil_printf.h" 8 #include "sleep.h" 9 10 //宏定义 11 #define SCUGIC_ID XPAR_SCUGIC_0_DEVICE_ID //中断控制器 ID 12 #define GPIOPS_ID XPAR_XGPIOPS_0_DEVICE_ID //PS 端 GPIO 器件 ID 13 #define AXI_GPIO_ID XPAR_AXI_GPIO_0_DEVICE_ID //PL 端 AXI GPIO 器件 ID 14 #define GPIO_INT_ID XPAR_FABRIC_GPIO_0_VEC_ID //PL 端 AXI GPIO 中断 ID 15 16 #define MIO_LED 0 //PS LED 连接到 MIO0 17 #define KEY_CHANNEL 1 //PL 按键使用 AXI GPIO 通道 1 18 #define KEY_MASK XGPIO_IR_CH1_MASK //通道 1 的位定义 19 20 //函数声明 21 void instance_init(); //初始化器件驱动 22 void axi_gpio_handler(void *CallbackRef); //中断服务函数 23 24 //全局变量 25 XScuGic scugic_inst; //中断控制器 驱动实例 26 XScuGic_Config * scugic_cfg_ptr; //中断控制器 配置信息 27 XGpioPs gpiops_inst; //PS 端 GPIO 驱动实例 28 XGpioPs_Config * gpiops_cfg_ptr; //PS 端 GPIO 配置信息 29 XGpio axi_gpio_inst; //PL 端 AXI GPIO 驱动实例 30 31 int led_value = 1; //LED 显示状态 32 33 int main() 34 { 35 printf("AXI GPIO INTERRUPT TEST!\n"); 36 37 //初始化各器件驱动 38 instance_init(); 39 40 //配置 PS GPIO 41 XGpioPs_SetDirectionPin(&gpiops_inst, MIO_LED, 1); //设置 PS GPIO 为输出 42 XGpioPs_SetOutputEnablePin(&gpiops_inst, MIO_LED, 1); //使能 LED 输出 43 XGpioPs_WritePin(&gpiops_inst, MIO_LED, led_value); //点亮 LED 44 45 //配置 PL AXI GPIO 46 XGpio_SetDataDirection(&axi_gpio_inst, KEY_CHANNEL, 1); //设置 AXI GPIO 通道 1 为输入 47 XGpio_InterruptEnable(&axi_gpio_inst, KEY_MASK); //使能通道 1 中断 48 XGpio_InterruptGlobalEnable(&axi_gpio_inst); //使能 AXI GPIO 全局中断 49 50 //设置中断优先级和触发类型(高电平触发) 51 XScuGic_SetPriorityTriggerType(&scugic_inst, GPIO_INT_ID, 0xA0, 0x1); 52 //关联中断 ID 和中断处理函数 53 XScuGic_Connect(&scugic_inst, GPIO_INT_ID, axi_gpio_handler, &axi_gpio_inst); 54 //使能 AXI GPIO 中断 55 XScuGic_Enable(&scugic_inst, GPIO_INT_ID); 56 57 //设置并打开中断异常处理功能 58 Xil_ExceptionInit(); 59 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, 60 (Xil_ExceptionHandler)XScuGic_InterruptHandler, &scugic_inst); 61 Xil_ExceptionEnable(); 62 63 while(1); 64 65 return 0; 66 } 67
在主函数中,首先调用自己编写的 instance_init( )函数对各器件驱动进行初始化。 然后分别配置 PS 端GPIO、 PL 端的 AXI GPIO, 如程序第 40 至 48 行所示。 在配置 PL 端 AXI GPIO 时,我们需要使能其中断功能, 包括 AXI GPIO 通道 1 的中断和全局中断。接下来在程序第 50 至 55 行配置 GIC。每一个中断源都有自己唯一的标识——中断号(ID),具体的数值可以在头文件 xparameters_ps.h 中查看。 其中由 PL 产生的共享外设中断( SPI)共 16 个, 中断 ID 分别为 61 到 68, 以及 84 到 91。 我们在程序第 14 行定义了一个宏 GPIO_INT_ID, 用于标识 AXI GPIO 的中断ID,它的值为 61。配置 GIC 首先需要设置中断 ID 所代表的中断源的优先级和触发类型。中断优先级共分为 32 个等级, 0 代表最高优先级, 0xF8( 10 进制数 248)代表最低优先级, 各优先级之间的步进值为8。 也就是说,支持的优先级分别为 0、 8、 16、 32……、 248。中断触发类型分为高电平敏感类型和上升沿敏感类型。 AXI GPIO在检测到输入接口的信号发生改变时, 会产生一个电平类型的中断请求,高有效,因此将中断源 AXI GPIO的触发类型设置为高电平敏感类型。
然后还需要将中断 ID 与其中断服务函数关联起来。中断服务函数 axi_gpio_handler( )是需要我们自己编写的, 用于响应和处理 AXI GPIO 中断的函数。除此之外, 还要调用函数 XScuGic_Enable(&scugic_inst, GPIO_INT_ID)来使能中断 ID 所对应的中断源。
最后我们需要初始化并设置 ARM 处理器的异常处理功能, 如程序第 57 至 61 行所示。 ARM 处理器支持 7 种异常情况:复位、未定义指令、软件中断、指令预取中止、数据中止、 中断请求( IRQ) 和快速中断请求( FIQ) 。每种异常也都有自己的 ID 标识, 其中 XIL_EXCEPTION_ID_INT 用于标识中断请求( IRQ)异常。 我们通过调用函数 Xil_ExceptionRegisterHandler( XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler) XScuGic_InterruptHandler, &scugic_inst )来给 IRQ 异常注册处理程序,它会将中断控制器 GIC 的中断处理程序与 ARM 处理器中的硬件中断处理逻辑连接起来。另外还要通过 Xil_ExceptionEnable( )函数使能 IRQ 异常。
68 //初始化各器件驱动 69 void instance_init() 70 { 71 //初始化中断控制器驱动 72 scugic_cfg_ptr = XScuGic_LookupConfig(SCUGIC_ID); 73 XScuGic_CfgInitialize(&scugic_inst, scugic_cfg_ptr, scugic_cfg_ptr->CpuBaseAddress); 74 75 //初始化 PS 端 GPIO 驱动 76 gpiops_cfg_ptr = XGpioPs_LookupConfig(GPIOPS_ID); 77 XGpioPs_CfgInitialize(&gpiops_inst, gpiops_cfg_ptr, gpiops_cfg_ptr->BaseAddr); 78 79 //初始化 PL 端 AXI GPIO 驱动 80 XGpio_Initialize(&axi_gpio_inst, AXI_GPIO_ID); 81 } 82 83 //PL 端 AXI GPIO 中断服务(处理)函数 84 void axi_gpio_handler(void *CallbackRef) 85 { 86 int key_value = 1; 87 XGpio *GpioPtr = (XGpio *)CallbackRef; 88 89 print("Interrupt Detected!\n"); 90 XGpio_InterruptDisable(GpioPtr, KEY_MASK); //关闭 AXI GPIO 中断使能 91 key_value = XGpio_DiscreteRead(GpioPtr, KEY_CHANNEL); //读取按键数据 92 if(key_value == 0){ //判断按键按下 93 led_value = ~led_value; 94 XGpioPs_WritePin(&gpiops_inst, MIO_LED, led_value); //改变 LED 显示状态 95 } 96 sleep(1); //延时 1s,按键消抖 97 XGpio_InterruptClear(GpioPtr, KEY_MASK); //清除中断 98 XGpio_InterruptEnable(GpioPtr, KEY_MASK); //使能 AXI GPIO 中断 99 }
其中 instance_init( )函数用于初始化设计中所使用的各个器件的驱动,包括 PS 端的 GIC 和 GPIO, 及PL 端的 AXI GPIO, 如程序第 68 至 81 行所示。其中通用中断控制器( GIC)是 PS 中用于集中管理中断信号的资源,如果我们在程序中使用到了中断,就需要对其进行初始化及配置。而 axi_gpio_handler( void *CallbackRef )是中断服务(处理) 函数,当 CPU 检测到 AXI GPIO 产生的中断后, 需要执行该函数。如程序第 83 至 99 行所示,在中断服务函数中,我们先通过 XGpio_DiscreteRead ( GpioPtr, KEY_CHANNEL )函数读取 AXI GPIO 通道 1 所连接的 PL 端按键的状态;当判断到按键按下时,通过 XGpioPs_WritePin(&gpiops_inst, MIO_LED, led_value)函数修改 PS 端 LED 的显示状态。对于电平敏感类型的中断, 在中断服务函数响应了中断之后,需要将中断源的中断清除。如程序第97行所示,我们通过 XGpio_InterruptClear( GpioPtr, KEY_MASK )函数清除 AXI GPIO 通道 1 的中断状态寄存器。
再次赞美原子简洁易懂的代码和详细的注释!
三.挖坑串口中断
详细部分就在串口篇阐开记述吧,作为补偿同样的软件中断来一个吧,也就是随便一个外设挂在那16个软件中断上
四.相对普遍应用的软件中断
此处拿1553B挂载为例(代表一般外设)(其余1553B部分在1553B篇)
原理在开篇讲的差不多了,现在直接贴代码吧
头文件
#include "xil_io.h" #include "xparameters.h" #include "xstatus.h" #include "xil_printf.h" #include "xgpiops.h" #include "sleep.h" #include "xil_exception.h" #include "xscugic.h" #define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID #define INTC XScuGic #define INTC_HANDLER XScuGic_InterruptHandler #define RT_BASE_ADDR 0x43C00000 #define RT_RAM_BASE 0x43C00000 INTC IntcInstance; INTC IntcInstance1553;
static int rt1553BSetupIntrSystem(INTC *IntcInstancePtr,u16 rt1553bIntrId) { int Status; #ifdef XPAR_INTC_0_DEVICE_ID #else #ifndef TESTAPP_GEN XScuGic_Config *IntcConfig; /* * Initialize the interrupt controller driver so that it is ready to * use. */ IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID); if (NULL == IntcConfig) { return XST_FAILURE; } Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig, IntcConfig->CpuBaseAddress); if (Status != XST_SUCCESS) { return XST_FAILURE; } #endif /* TESTAPP_GEN */ XScuGic_SetPriorityTriggerType(IntcInstancePtr, XPS_FPGA0_INT_ID, 0xA0, 0x3); /* * Connect the interrupt handler that will be called when an * interrupt occurs for the device. */ Status = XScuGic_Connect(IntcInstancePtr, XPS_FPGA0_INT_ID, (Xil_ExceptionHandler)rt1553B_handle, NULL); if (Status != XST_SUCCESS) { return Status; } /* * Enable the interrupt for the Timer device. */ XScuGic_Enable(IntcInstancePtr, XPS_FPGA0_INT_ID); #endif /* XPAR_INTC_0_DEVICE_ID */ #ifndef TESTAPP_GEN /* * Initialize the exception table. */ Xil_ExceptionInit(); /* * Register the interrupt controller handler with the exception table. */ Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)INTC_HANDLER, IntcInstancePtr); /* * Enable exceptions. */ Xil_ExceptionEnable(); #endif /* TESTAPP_GEN */ return XST_SUCCESS; }
相对于常用的GPIO,串口之类为什么要单独拎出来做一个一般中断呢?自然是没有例程参考后令人头疼的ID了,xilinx系列的软件风格在初始化的时候都会对ID做好匹配(其他是不是不知道哈,认知局限),那么这个ID在常用的xparameters.h里就很难找了,在包含的头文件xparameters_ps.h还是可以找到滴,所以也就是说把熟悉的串口中断设置函数里关于串口的部分删掉(因为我们没有啊,对,是的直接删掉
static int UartNs550SetupIntrSystem(INTC *IntcInstancePtr, XUartNs550 *UartInstancePtr,//删掉 u16 UartIntrId))
ID命名为我们现在设备的ID,对应的具体地址也就是
#define XPS_FPGA0_INT_ID 61U
(换了个马甲你应该还是认识我的对吧)
既然这样简单粗暴地话,也就是随便来个挂在软件中断的设备我们都可以通过这样的方式来解决了,官方有实例的那不是就更好了?
暂时只遇见过这么多,之后还有中断问题在进行补充吧,认知局限了想象力!
参考:芯片手册 原子开发手册 CSDN Xilinx社区等网络资源