PL中断
双核Zynq中共有20个PL到PS的中断。IRQF[15:0]是16个共享外设中断(SPI),可配选择上升沿触发或高电平触发,中断号为61-68和84-91。
另外还有4个私有外设中断(PPI)IRQF2P[19:16],每个CPU都有一个来自PL的FIQ(快速中断)和IRQ,其中断敏感类型固定。
本文体会Zynq中PL到PS的SPI的使用方法。若设置为电平敏感,PL必须提供在中断被确认后将其清除掉的机制。若设置为上升沿敏感,PL必须提供足够宽的脉冲,让GIC能够捕获。
硬件平台搭建
本例中将连接到PL部分的两个按键作为中断请求源,从PL部分路由到PS的中断接口。设置Zynq7处理系统,Interrupts中启用PL到PS的中断,选择IRQ_F2P[15:0],启用SPI。下面4个是PPI。
配置DDR和MIO电平,启用UART,设计程序根据按键中断串口打印相应信息。配置完成后白板中的Zynq7 IP会出现IQR_F2P管脚,但位宽只有[0:0]。我们把中断源以总线的形式连接到这个管脚后,运行Validate Design有效性检查,会自动更新位宽。
将多路信号拼接为总线可以用Concat IP核实现。这个IP可以设置端口数目和每个端口上的位宽,设为Auto可以完成自动检测。In0在输出总线的低位,In[Number of Ports - 1]在输出总线的高位。
硬件连接如下图所示。由于使用了PL部分的管脚,要添加XDC约束文件,将sw0和sw1绑定到按键管脚。生成bit流文件后,将硬件平台导出到SDK中。
SDK程序设计
新建工程,依次添加源文件并编写代码。init.h文件代码如下:
#include <stdio.h>
#include "xscugic.h"
#include "xil_exception.h"
//---------------------------------------------------------
// 参数定义
//---------------------------------------------------------
#define SW1_INT_ID 61 //按键PL中断1
#define SW2_INT_ID 62 //按键PL中断2
#define INTC_DEVICE_ID XPAR_PS7_SCUGIC_0_DEVICE_ID //中断设备
#define INT_TYPE_RISING_EDGE 0x03 //上升沿敏感
#define INT_TYPE_HIGHLEVEL 0x01 //高电平敏感
#define INT_TYPE_MASK 0x03
#define INT_CFG0_OFFSET 0x00000C00
//---------------------------------------------------------
// 函数声明
//---------------------------------------------------------
void IntcTypeSetup(XScuGic *InstancePtr, int intId, int intType);
int IntcInitFunction(u16 DeviceId);
init.c文件的代码如下:
#include "init.h"
static XScuGic INTCInst;
//---------------------------------------------------------
// 中断处理程序
//---------------------------------------------------------
static void SW_intr_Handler(void *param)
{
int sw_id = (int)param;
printf("SW%d int\n\r", sw_id); //根据中断请求源打印相关信息
}
//---------------------------------------------------------
// 中断敏感类型设置函数
//---------------------------------------------------------
void IntcTypeSetup(XScuGic *InstancePtr, int intId, int intType)
{
int mask;
intType &= INT_TYPE_MASK;
mask = XScuGic_DistReadReg(InstancePtr, INT_CFG0_OFFSET+(intId/16)*4);
mask &= ~(INT_TYPE_MASK << (intId%16)*2);
mask |= intType << ((intId%16)*2);
XScuGic_DistWriteReg(InstancePtr, INT_CFG0_OFFSET+(intId/16)*4, mask);
}
//---------------------------------------------------------
// 中断初始化函数
//---------------------------------------------------------
int IntcInitFunction(u16 DeviceId)
{
XScuGic_Config *IntcConfig; //存储中断设备的配置信息
int status;
/* 初始化中断控制器 */
IntcConfig = XScuGic_LookupConfig(DeviceId);
status = XScuGic_CfgInitialize(&INTCInst, IntcConfig, IntcConfig->CpuBaseAddress);
if(status != XST_SUCCESS) return XST_FAILURE; //检测初始化状态
/* 设置并打开中断异常处理功能 */
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler, &INTCInst);
Xil_ExceptionEnable();
/* 为中断设置中断处理函数 */
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;
/* 将中断设置为上升沿敏感类型 */
IntcTypeSetup(&INTCInst, SW1_INT_ID, INT_TYPE_RISING_EDGE);
IntcTypeSetup(&INTCInst, SW2_INT_ID, INT_TYPE_RISING_EDGE);
/* 使能中断 */
XScuGic_Enable(&INTCInst, SW1_INT_ID);
XScuGic_Enable(&INTCInst, SW2_INT_ID);
return XST_SUCCESS;
}
main.c文件的代码如下:
#include "init.h"
int main(void)
{
//初始化中断
IntcInitFunction(INTC_DEVICE_ID);
while(1);
return 0;
}
SDK Terminal中添加串口,下载程序。按下按键终端会显示相应的信息。由于PL中没有对按键做消抖处理,直接作为中断请求源,有时会“过于灵敏”。
相关函数使用说明
我们只使用了两个PL中断,在硬件平台中可以看到,自动从低bit开始分配,即使用IRQ_F2P[1:0],对应的中断号是62-61。为了编程方便在init.h中对这两个中断号做宏定义。
1.中断控制器实例
init.c定义了一个XScuGic类型的结构体变量,设计者需要为系统中的每个中断设备分配一个此类型的变量。其它中断相关的API需要指向此变量的指针作为传入参数。
中断初始化函数中又定义了一个指向XScuGic_Config类型的结构体变量的指针,这个结构体包含了中断设备的配置信息。两个结构体的原型如下(注意XScuGic_Config也是XscuGic的一个成员):
typedef struct
{
u16 DeviceId; /**< Unique ID of device */
u32 CpuBaseAddress; /**< CPU Interface Register base address */
u32 DistBaseAddress; /**< Distributor Register base address */
XScuGic_VectorTableEntry HandlerTable[XSCUGIC_MAX_NUM_INTR_INPUTS];/**<
Vector table of interrupt handlers */
} XScuGic_Config;
typedef struct
{
XScuGic_Config *Config; /**< Configuration table entry */
u32 IsReady; /**< Device is initialized and ready */
u32 UnhandledInterrupts; /**< Intc Statistics */
} XScuGic;
2.中断控制器初始化
中断初始化函数中使用XScuGic_LookupConfig()函数和XScuGic_CfgInitialize()函数完成中断控制器的初始化。其实上面这部分处理和GPIO部分的一样,只是处理对象从“GPIO设备”换成了“中断设备”。
XScuGic_LookupConfig()函数的原型接口如下。传入参数为设备ID,返回的是一个XScuGic_Config类型的指针。如果存在该中断设备,则返回该配置信息的列表;未找到则返回NULL。
XScuGic_Config *XScuGic_LookupConfig(u16 DeviceId){}
XScuGic_CfgInitialize()函数的原型接口如下。该函数用来初始化传入的XScuGic类型的变量(可称作中断控制器实例),初始化该结构体中的成员字段。第二个参数便是用于初始化中断控制器实例的配置信息表。第三个参数EffectiveAddr是虚拟内存地址空间中的设备基地址,如果没有使用地址转换功能,传入Config->BaseAddress即可。
s32 XScuGic_CfgInitialize(XScuGic *InstancePtr,
XScuGic_Config *ConfigPtr, u32 EffectiveAddr) {}
3.中断异常处理
中断控制器初始化完成后,使用Xil_ExceptionRegisterHandler()和Xil_ExceptionEnable()两个函数设置中断异常处理功能。
Xil_ExceptionRegisterHandler()为异常注册处理函数。处理器运行过程中遇到该异常,便会调用设置的处理函数。该函数原型如下,第一个为异常的ID号,第二个为设置的处理函数,第三个是处理函数被调用时传给它的参数。
void Xil_ExceptionRegisterHandler(u32 Exception_id,
Xil_ExceptionHandler Handler, void *Data)
Xil_ExceptionEnable()启用IRQ异常,没有参数和返回值。异常处理机制并不是中断独有的,在xil_exception.h文件中有所有异常情况的ID列表。下面贴出init.c中的代码,更进一步理解:
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler, &INTCInst);
XIL_EXCEPTION_ID_INT表示IRQ中断异常。XScuGic_InterruptHandler是库中提供的函数,它可以解析那些中断时活跃的和启用的,并调用相应的中断处理程序,先服务优先级最高的中断。该函数是保证中断系统正常运行的基础,其原型如下:
void XScuGic_InterruptHandler(XScuGic *InstancePtr)
它需要一个指向XscuGic类型的指针参数。在调用Xil_ExceptionRegisterHandler()时通过设置第三个参数便可完成参数传入。
4.中断源设置
之后使用XScuGic_Connect()函数将中断源和中断处理程序联系起来,当中断发生时会调用设置的中断处理程序。函数原型如下,Int_ID是中断源对应的ID号;Handler是中断处理程序;最后一个void*类型的CallBackRef在中断处理程序被调用时会作为参数传入。
s32 XScuGic_Connect(XScuGic *InstancePtr, u32 Int_Id,
Xil_InterruptHandler Handler, void *CallBackRef)
再之后便是用自定义的函数完成中断敏感类型的设置,主要是对中断相关寄存器的配置。最后使用XScuGic_Enable()函数启用中断源。函数原型如下,Int_Id为中断源对应的ID号。
void XScuGic_Enable(XScuGic *InstancePtr, u32 Int_Id)
5.中断处理程序
static void SW_intr_Handler(void *param)
{
int sw_id = (int)param;
printf("SW%d int\n\r", sw_id); //根据中断请求源打印相关信息
}
中断处理程序只能传入void*类型的参数,因此在内部使用时需要做适当的类型转换。我们通过两个按键中断传入不同的参数,便可在一个中断处理程序中识别出到底是哪个中断源发出了中断请求。