软件环境:vivado 2017.4 硬件平台:XC7Z020
由图中可见,中断大体分为三类,包括SGI(软件中断)、PPI(私有中断)、SPI(共享中断)。
每个CPU均有16个SGI(软件中断),如下图。
PPI(私有中断)中包含2个PL到CPU的快速中断,nFIQ,具体如下图。
最后,SPI(共享中断)配置如下图,红圈处就是这次实验用的可由PL触发,PS处理的共享中断。
哔哔完了开始建工程,Vivado 2017.4 Create Block Design后,添加ZYNQ7 Processing system,在interrupt中勾PL-PS的IRQ_F2P[15:0]如图所示。
添加IP ------------> Vector(逻辑门),设置为not。添加IP------------>concat(链接)。
如下图链接好后,记得添加IO管脚约束。
我是用PL侧的开关来触发产生的中断,IO约束如下。最后Generate the output products,Create a HDL wrapper,Generate Bitstream,Export Hardware(注意勾选include biestream),最后launch SDK进行测试。
set_property PACKAGE_PIN T19 [get_ports {SW1[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {SW1[0]}]
set_property PACKAGE_PIN R19 [get_ports {SW2[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {SW2[0]}]
SDK中new application然后添加如下测试代码。
#include <stdio.h>
#include "xscugic.h"
#include "xil_exception.h"
#define INT_CFG0_OFFSET 0x00000C00
// Parameter definitions
#define SW1_INT_ID 61
#define SW2_INT_ID 62
#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
static XScuGic INTCInst;
static void SW_intr_Handler(void *param);
static int IntcInitFunction(u16 DeviceId);
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;
// Interrupt controller initialisation
IntcConfig = XScuGic_LookupConfig(DeviceId);
status = XScuGic_CfgInitialize(&INTCInst, IntcConfig, IntcConfig->CpuBaseAddress);
if(status != XST_SUCCESS) return XST_FAILURE;
// Call to interrupt setup
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
&INTCInst);
Xil_ExceptionEnable();
// Connect SW1~SW3 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;
// Set interrupt type of SW1~SW3 to rising edge
IntcTypeSetup(&INTCInst, SW1_INT_ID, INT_TYPE_RISING_EDGE);
IntcTypeSetup(&INTCInst, SW2_INT_ID, INT_TYPE_RISING_EDGE);
// Enable SW1~SW3 interrupts in the controller
XScuGic_Enable(&INTCInst, SW1_INT_ID);
XScuGic_Enable(&INTCInst, SW2_INT_ID);
return XST_SUCCESS;
}
int main(void)
{
print("PL int test\n\r");
IntcInitFunction(INTC_DEVICE_ID);
while(1);
return 0;
}
接下来对程序中的一些函数做一些解释说明:
XScuGic结构体,中断配置初始化的一个接口,内部定义如下。
typedef struct
{
XScuGic_Config *Config; /**< Configuration table entry */
u32 IsReady; /**< Device is initialized and ready */
u32 UnhandledInterrupts; /**< Intc Statistics */
} XScuGic;
其中的XScuGic_Config,包括设备ID号,基址,向量表。
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;
初始化老套路,一上来肯定先是XScuGic_LookupConfig()查查ID,看看设备在不在,在的话拿XScuGic_CfgInitialize找到配置寄存器基址。然后Xil_ExceptionRegisterHandler()是中断注册。其实说是中断注册,但是看介绍说的是,处理特定异常时用的。
void Xil_ExceptionRegisterHandler(u32 Exception_id,
Xil_ExceptionHandler Handler,
void *Data)
{
XExc_VectorTable[Exception_id].Handler = Handler;
XExc_VectorTable[Exception_id].Data = Data;
}
Xil_ExceptionRegisterHandler()初始化时候带的第二个参数XScuGic_InterruptHandler()实际上是主要的中断处理函数,必须将你配置的中断源与这个函数捆绑,否则无法将你配置的中断源在终端控制器中激活,因为这个函数主要解决的就是看哪个中断被使能了,处于激活状态,哪个中断有高优先级,中断处理函数是啥。个人认为,丢到XExc_VectorTable中,就是捆绑的过程。
void XScuGic_InterruptHandler(XScuGic *InstancePtr)
{
u32 InterruptID;
u32 IntIDFull;
XScuGic_VectorTableEntry *TablePtr;
Xil_AssertVoid(InstancePtr != NULL);
IntIDFull = XScuGic_CPUReadReg(InstancePtr, XSCUGIC_INT_ACK_OFFSET);
InterruptID = IntIDFull & XSCUGIC_ACK_INTID_MASK;
if(XSCUGIC_MAX_NUM_INTR_INPUTS < InterruptID){
goto IntrExit;
}
TablePtr = &(InstancePtr->Config->HandlerTable[InterruptID]);
if(TablePtr != NULL) {
TablePtr->Handler(TablePtr->CallBackRef);
}
IntrExit:
XScuGic_CPUWriteReg(InstancePtr, XSCUGIC_EOI_OFFSET, IntIDFull);
}
Xil_ExceptionEnable()就不多说了呀,注册完了使能呗。
#define Xil_ExceptionEnable() \
Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ)
XScuGic_Connect()这个才是重点,所以干脆换了个颜色,作用是将中断Id、中断处理程序Handler和回调参数3者结合起来,当中断被触发时候,由中断号找到中断处理程序,并送个中断处理程序特定回调参数。
s32 XScuGic_Connect(XScuGic *InstancePtr, u32 Int_Id,
Xil_InterruptHandler Handler, void *CallBackRef)
{
/*
* Assert the arguments
*/
Xil_AssertNonvoid(InstancePtr != NULL);
Xil_AssertNonvoid(Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS);
Xil_AssertNonvoid(Handler != NULL);
Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
/*
* The Int_Id is used as an index into the table to select the proper
* handler
*/
InstancePtr->Config->HandlerTable[Int_Id].Handler = Handler;
InstancePtr->Config->HandlerTable[Int_Id].CallBackRef = CallBackRef;
return XST_SUCCESS;
}
XScuGic_Connect()中第三个参数,不论是Xil_ExceptionHandler类型还是Xil_InterruptHandler类型,可以看到,参数是少不了的。由中断函数SW_intr_Handler()可以看到,带的参数其实就是CallBackRef。
typedef void (*Xil_ExceptionHandler)(void *data);
typedef void (*Xil_InterruptHandler)(void *data);
IntcTypeSetup()函数用于设置中断类型,可以看到,其实是寄存器移位屏蔽或者移位置位操作。
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);
}
从最后一行才是真正写相关寄存器的操作,开XScuGic_DistWriteReg()函数可以看到。
#define XScuGic_DistWriteReg(InstancePtr, RegOffset, Data) \
(XScuGic_WriteReg(((InstancePtr)->Config->DistBaseAddress), (RegOffset), \
((u32)(Data))))
当中断ID号是61的时候,被写的寄存器实际上是DistBaseAddress基址+RegOffset偏移量+(intId/16)*4=0xF8F01000+0x00000C00+0x0C=0xF8F01C0C
#define XPAR_PS7_SCUGIC_0_DEVICE_ID 0U
#define XPAR_PS7_SCUGIC_0_BASEADDR 0xF8F00100U
#define XPAR_PS7_SCUGIC_0_HIGHADDR 0xF8F001FFU
#define XPAR_PS7_SCUGIC_0_DIST_BASEADDR 0xF8F01000U
开手册可以看到,确实是配置中断触发的寄存器。
至于mask怎么移的,移多少,你怂管,反正是通用函数,你只用知道1是高电平触发,3是上升沿触发就OJ8K了。
XScuGic_Enable()明摆着中断使能函数,使能中断ID号的中断。
void XScuGic_Enable(XScuGic *InstancePtr, u32 Int_Id)
{
u32 Mask;
/*
* Assert the arguments
*/
Xil_AssertVoid(InstancePtr != NULL);
Xil_AssertVoid(Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS);
Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
/*
* The Int_Id is used to create the appropriate mask for the
* desired bit position. Int_Id currently limited to 0 - 31
*/
Mask = 0x00000001U << (Int_Id % 32U);
/*
* Enable the selected interrupt source by setting the
* corresponding bit in the Enable Set register.
*/
XScuGic_DistWriteReg(InstancePtr, (u32)XSCUGIC_ENABLE_SET_OFFSET +
((Int_Id / 32U) * 4U), Mask);
}
所以在最后总结一下PL产生中断,PS处理中断,在SDK中大致的流程:
通过XGpioPs_LookupConfig函数,找到所设备的基址------------>
通过XGpioPs_CfgInitialize函数,初始化设备配置------------>
通过Xil_ExceptionRegisterHandler函数,进行中断异常注册------------>
通过Xil_ExceptionEnable函数,使能中断异常注册------------>
通过XScuGic_Connect函数,链接中断号、中断处理程序、回调参数------------>
通过IntcTypeSetup函数,设置中断触发模式------------>
通过XScuGic_Enable函数,中断使能。