软件版本:VIVADO2017.4
操作系统:WIN10 64bit
硬件平台:适用米联客 ZYNQ系列开发板
米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!!
7.1 概述
本课节对ZYNQ的中断进行了介绍,并通过PL端按键输入中断信号,PS端进行处理,讲解了外部中断使用。
7.2 ZYNQ 中断介绍
7.2.1 ZYNQ中断框图
上图为ZYNQ中断分布框图。可以看到部分PL到PS部分的中断,经过中断控制分配器(ICD),同时进入CPU1 和CPU0。查询下面表格,可以看到PL到PS部分一共有20个中断可以使用。4个快速中断(PPI),即IRQF2P[19:16];16个共享中断(SPI),即IRQF2P[7:0]、IRQF2P[15:8]。这16个中断可以任意定义,本课涉及使用。
7.2.2 ZYNQ CPU软件中断 (SGI)
ZYNQ共有两个CPU,每个 CPU具备各自的16个软件中断。
7.2.3 ZYNQ CPU 私有端口中断
私有中断不能修改。这里有2个PL到CPU的快速中断nFIQ
7.2.4 ZYNQ PS和PL共享中断
共享中断就是PL的中断可以发送给PS处理。上图中,黄色区域是16个PL的中断,它们可以设置为高电平或者低电平触发。
7.3 搭建BD工程
Step1:新建一个名为为Miz_sys的工程。
Step2:创建一个BD文件,并命名为system,添加并且配置好ZYNQ IP。读者需要根据自己的硬件类型配置好输入时钟频率、内存型号、串口,连接时钟等。新手不清楚这些内容个,请参考“CH01 HelloWold/DDR/网口测试及固化”这一节课。
Step4:单击添加IP按钮,添加两个逻辑门(vector)和一个连接(concat)IP。
Step6:如图所示,完成整体电路。
SW1(端口) 连接Op1(util_vector_logic_0);
SW2(端口) 连接Op1(util_vector_logic_1);
Res(util_vector_logic_0) 连接In0(xconcat_0);
Res(util_vector_logic_1) 连接In1(xconcat_0);
dout(xconcat_0) 连接 IRQ_F2P(ZYNQ7);
Step5:单击窗口上的运行按钮,运行程序。
7.6 实验结果
分别按下SW1和SW2按钮,系统运行结果如下图所示:
7.7 程序分析
分析1
语句:IntcInitFunction(INTC_DEVICE_ID);
含义:中断初始化
具体分析:
输入参数
IntcInitFunction函数输入参数:INTC_DEVICE_ID。选中这个参数跟踪(按F3),找到定义,即“#define INTC_DEVICE_ID XPAR_PS7_SCUGIC_0_DEVICE_ID”。这个参数是系统中断设备ID基地址的宏定义,即中断的基地址。
定义
1、程序开始定义需要用到的指针和变量。
XScuGic_Config *IntcConfig;
int status;
2、查找设备配置的程序,参数为设备ID。查看中断向量是否存在。当执行完下面的程序,系统会对中断做一些初始化,如果初始化成功,会返回一个XST_SUCCESS的标志。当未检测到返回到这个初始化成功的标志时,系统会返回一个XST_FAILURE标志。
IntcConfig = XScuGic_LookupConfig(DeviceId);
status = XScuGic_CfgInitialize(&INTCInst, IntcConfig, IntcConfig->CpuBaseAddress);
if(status != XST_SUCCESS) return XST_FAILURE;
3、接下来是一个中断注册函数Xil_ExceptionRegisterHandler,查看其函数定义。“注册特定异常的处理程序。处理器遇到指定的异常时会调用此处理程序”。
u32 Exception_id:异常源ID,范围:0~XIL_EXCEPTION_ID_LAST(更多相关信息,请参阅xil_exception.h)。这里可以理解为中断类型。
Xil_ExceptionHandler Handler:处理异常的处理程序。
void *Data:被调用时,传递给Handler的数据引用。
从上面可以看出这个函数是把中断句柄和中断参数放到数组中,选中函数按下F3查看。
数组类型为XExc_VectorTableEntry,这个结构体定义如下图所示:
4、连接interrupt和handler,即连接中断到中断处理函数。
查看XScuGic_Connect定义。注释中描述其功能:“使中断源的Int_Id与识别中断时要运行的关联处理程序之间建立连接”。 XScuGic_Connect函数的最后两个函数,把中断的句柄和一个指针变量传递了进来。
// Connect SW1~SW2 interrupt to handler
status = XScuGic_Connect(&INTCInst, SW1_INT_ID, (Xil_ExceptionHandler)SW_intr_Handler, (void *)1);
if(status != XST_SUCCESS) return XST_FAILURE;
status = XScuGic_Connect(&INTCInst, SW2_INT_ID,
(Xil_ExceptionHandler)SW_intr_Handler, (void *)2);
if(status != XST_SUCCESS) return XST_FAILURE;
此时返回查看XScuGic_Connect函数,发现中断句柄是一个指针函数,当程序被执行时,被调用的是这个指针函数,此时跟踪这个指针函数,查看其具体内容。
通过程序开头xilinx给出的这个程序的注释可以知道:这个函数是基本的中断驱动函数。它必须连接到中断源,以便在中断控制器的中断激活时被调用。 它将解决哪些中断是活动的和启用的,并调用适当的中断处理程序。它使用中断类型信息来确定何时响应中断。首先处理最高优先级的中断。此函数假定中断向量表已预先初始化。它不会在调用中断处理程序之前验证表中的条目是否有效。
上面讲到的这个中断向量表其实也就是下图所示的部分。
这部分在刚才已经进行了讲解了,此时我们就可以清楚的知道这就是一个中断向量表了。
回到基本的中断驱动函数的分析,看到下面的一段程序:
通过注释我们知道了这个程序是读取int_ack寄存器以识别最高优先级的中断ID,并确保其有效。 读取Int_Ack将清除GIC中的中断。然后看看读出来的中断ID是否大于最大的中断值。查看下这个最大的中断值。
从上图中圈出的地方可以看到,当使用ZYNQ的时候,最大有95个中断可以供我们使用。当读出来的这个中断值大于95U的话,就直接跳转到异常处理程序部分:
这里的意思也就相当于恢复中断寄存器,相当于出栈。
当读出来的中断值是正常的话,就会查找这个中断的中断向量表,如果这个向量表不是非空的话,就开始处理这个中断,也就是开始执行之前的连接中断的函数。此部分程序如下:
上图中的Tableptr指向的CallBackRef其实就是我们连接中断函数定义的无符号的数字,如下图所示。
为了验证我们的猜想,我们可以把这里的数字改成其他的值进行验证。
5、
回到主程序当中,接着看到这段函数:
这段程序把中断的触发类型设置为了上升沿触发。
这段程序使能了中断。
6、
整段程序下来,那么主要是执行了哪个函数呢?通过上面的分析,我们可以判定其实是下面这个函数:
这个函数的方框部分其实是个指针函数,我们可以跟踪看一下其定义。
一开始,它将传递进来的指针传递给了sw_id,然后会打印哪个按钮初始化,其实也就是哪个中断被触发了。
接下来,我们再对中断的一些寄存器做一些分析。在中断设置里的一些寄存器是比较重要的,我们就来分析一下中断设置里的寄存器。
将鼠标停留在图上圈出的函数上,SDK会跳出关于这个函数的信息,在跳出的窗口中左边是我们圈出的这个函数的定义,右边则是在执行过程中实际运行的程序。我们拷贝出右边这个函数来分析一下:
((Xil_In32((((InstancePtr)->Config->DistBaseAddress)) + ((0x00000C00 + (intId/16)*4)))))
红色部分是一个指针,它调用了config里的一个基地址DisBaseAddress,后半部分我们可以断定这是一个寄存器地址,因为这个函数就是一个读取中断寄存器的函数。此时,我们跟踪一下这个函数。
此时,我们就知道了第一个参数是一个指向要处理的中断的指针,第二个是寄存器偏移。我们就来计算一下这个寄存器偏移。首先我们来看看中断的基地址是多少(也就是红色部分指向的基地址)。