Zynq MIO中断配置实现与中断响应过程
中断结构
在进行中断相关的程序编写之前,首先需要了解zynq的中断框图。
这部分内容建议直接看xilinx官方手册ug585的第7章,里面有非常详细的介绍。xilinx文档可以直接用DocNav查看,会很方便。
从整体框图中可以看到,中断的来源主要分为三个部分,分别是软件中断、私有外设中断和共享外设中断。这里的顺序也是它中断ID编号的顺序,从前往后依次增大,总共有81个中断ID。
- 软件中断ID 0~15
- 私有外设中断ID 27 ~ 31,编号 16 ~ 26的ID是保留的
- 共享外设中断ID 32 ~ 91
这些中断源送进通用中断控制器(GIC)后再由GIC分配给两个CPU。手册中还有很多关于这三类中断的详细介绍。
有一个很好的地方是,共享外设中断虽然说是两个CPU共享的,但是只有其中一个CPU会进行处理,而不需要额外的加互斥锁之类的操作。
比较让我在意的是这里的共享外设中断除了那几个来自PL的中断可以自定义中断的触发类型,而其他的中断都是固定的触发类型。然而我们要用的GPIO中断可能会有不同的应用场景,这要是不能配置触发类型就很奇怪。后来发现这部分是在GPIO相关的寄存器中进行配置的。
6个ICD ICFR寄存器用来配置不同中断的触发类型,其中SGI和PPI中断的触发类型是固定的,所以是不能改的,寄存器只读。SPI的中断有两种配置选项,高电平触发或者是上升沿触发。
几个ICD IPTR寄存器用来设置中断目标,除了PPI的中断目标是固定的,SGI和SPI的中断目标都是可以配置的。
MIO中断实现
基本配置
Vivado的Block Design部分基本不需要什么配置,只要有GPIO就行。硬件上按键引脚上拉,按键按下后变成低电平,设计上采用下降沿触发。实验后发现应该是硬件上自带有消抖的功能,在软件中没有另外进行按键消抖处理也能正常使用。
1 |
这个案例中,中断相关的头文件要包含"xgpiops.h"和"xscugic.h",主要对GPIO和GIC进行配置。宏定义的设备ID用来后面查找设备,这里用到的GPIO的引脚比较少,可以直接对单个引脚进行配置,而不需要对整个GPIO Bank进行配置。
有些GPIO的函数需要用到Bank号,但只用引脚编号(0-117),就可以不需要用到Bank号,直接对单个IO进行配置。
1 | static XGpioPs Gpio; /* The Instance of the GPIO Driver */ |
GPIO和GIC的实例是全局唯一的,所以声明为全局变量。
1 | XGpioPs_Config *ConfigPtr; |
1 | XScuGic_Config *IntcConfig; |
GPIO和GIC的初始化是类似的,都是先根据设备ID查找设备,再调用初始化函数。
1 | XGpioPs_SetDirectionPin(Gpio, KEY_NUMBER, 0);// input |
GPIO口的基本配置,输入输出方向,输出使能,设置输出引脚的初始状态。关于GPIO中断的配置在SetupInterruptSystem函数中实现,在GPIO初始化函数中调用该函数。
1 | Status = SetupInterruptSystem(Intc, Gpio, GPIO_INTERRUPT_ID); |
中断相关配置
除了对GIC进行初始化,接着主要就是回调函数的绑定。
1 | Xil_ExceptionInit(); |
中断处理函数都是由这个“异常”来统一管理的,异常不仅限于IRQ中断,还有系统复位、未定义的中断、FIQ等等。这里的Xil_ExceptionInit函数在函数定义的注释里解释了这个没有任何作用,只是为了兼容。
1 | Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, GicInstancePtr); |
这个函数用来在异常处理向量表汇总注册GIC的中断处理函数。异常总共只有七中,这个异常处理向量表实际上是一个结构体数组,数组中的每个元素是一个结构体,包含异常处理函数的函数指针和这个函数的自变量。
1 | typedef void (*Xil_ExceptionHandler)(void *data); |
这个函数指针的类型已经有定义,和我以前所遇到的中断服务函数不同,这里的中断服务函数可以由自变量。这个自变量一般就是这个GIC的实例的指针。
XScuGic_InterruptHandler这个函数也不是我们自己定义的,而是GIC的驱动自带的,那么它完成了什么工作呢?
它实际上一个管理所有中断处理函数的一个函数,它会根据中断编号,比如GPIO的中断编号是52,根据52它会在HandlerTable中找到相应的中断处理函数进行执行。这里的这个HandlerTable和上面的异常处理向量表类似,也是一个结构体数组,将中断处理函数和自变量一同存储,而这个自变量命名为CallBackRef,一般也就是某个实例的指针。
为什么经常会看到函数把实例的指针作为参数进行传递?这类似面向对象的思想,实例本身是一个结构体,其中有关于这个实例的所有相关的内容。上面的HandlerTable也可以在GIC的实例中找到,而把实例作为一个参数传给中断处理函数,也使得中断处理函数能更加灵活地对外设进行修改。
1 | Status = XScuGic_Connect(GicInstancePtr, GpioIntrId, (Xil_ExceptionHandler)XGpioPs_IntrHandler, (void *)Gpio); |
在HandlerTable中并没有明确相应的中断处理函数,所以我们还要用这个XScuGic_Connect把GPIO的中断处理函数放到HandlerTable中。XGpioPs_IntrHandler是一个GPIO的通用的中断处理函数,它有一个自变量是GPIO的实例,XScuGic_Connect把它和GIC的实例中的HandlerTable进行绑定。
1 | XGpioPs_SetCallbackHandler(Gpio, (void *)Gpio, IntrHandler); |
然而XGpioPs_IntrHandler也还是GPIO驱动自带的中断服务函数,我们还需要定义一个实际的中断处理函数,用XGpioPs_SetCallbackHandler把XGpioPs_IntrHandler和实际的中断处理函数进行绑定。
1 | typedef void (*XGpioPs_Handler) (void *CallBackRef, u32 Bank, u32 Status); |
为了实现更加灵活的功能,GPIO对实际的中断服务函数的定义需要按照XGpioPs_Handler这个函数指针的形式来,其中除了CallBackRef,另外对Bank进行了区分,是的GPIO中断在产生时,可以根据不同的bank定义不同的功能。
1 | static void IntrHandler(void *CallBackRef, u32 Bank, u32 Status) |
以上是我定义的实际的中断服务函数,实现的是LED在按键后切换状态的功能。因为只有一处GPIO中断,所以没用到这里形参中的Status,如果有多个MIO中断来源,就需要根据Status和Bank进行判断具体是哪个IO口发生了中断。
实际的配置过程没有顺序要求,但一定要有这三个步骤;GPIO的中断响应过程基本上就是上面右图所示。
最后还有一些中断类型和使能的配置,这些都比较简单就不细说了。
1 | /* Enable falling edge interrupts for the key pin. */ |
在设计过程中有时候文件很大找不到函数,可以打开Windows菜单下的Open perspective… 选第一个C/C++,就会有一个Outline的窗口,可以很方便地查看有哪些函数。