PS和PL之间的交互,怎么都逃不过中断,稍微大型的数字系统,PS和PL之间配合使用就需要中断作为桥梁。本文通过按键发起中断请求尝试学习PL请求中断的处理机制。
板子用的是zc702。
ZYNQ是中断类系统框图:
由上图可知,zynq的中断分为三种:
1.软件中断(SGI,Software generatedinterrupts,中断号0-15)(16–26 reserved) :被路由到一个或者两个CPU上,通过写ICDSGIR寄存器产生SGI.
2.私有外设中断(PPI,private peripheralinterrupts ,中断号27-31):每个CPU都有一组PPI,包括全局定时器、私有看门狗定时器、私有定时器和来自PL的FIQ/IRQ.
3.共享外设中断(SPI,shared peripheralinterrupts,中断号32-95):由PS和PL上的各种I/O控制器和存储器控制器产生,这些中断信号被路由到相应的CPU.
中断控制器(GIC,generic interrupt controller ):用于集中管理从PS和PL产生的中断信号的资源集合。控制器可以使能、关使能、屏蔽中断源和改变中断源的优先级,并且会将中断送到对应的CPU中,CPU通过私有总线访问这些寄存器。
PL和PS之间的中断有:
两个CPU都具有各自16个软件中断,CPU可以中断自己,也可以中断其他CPU,上升沿触发,不可修改
CPU的私有中断,这些中断都是固定死的,不能修改:
从PL来的中断先反转然后送到中断控制器,所以触发类型变成active low了。
共享中断就是一些端口共用一个中断请求线, PL部分有16个共享中断,他们的触发方式可以设置:
按键中断硬件部分
新建一个Vivado工程,添加zynq核,勾选中断:
添加两个逻辑门IP,设为非门:
再添加一个合并多路信号到一路的IP,我们这里要合并两路信号:
这里可以修改输出引脚的名称:
连接完是这样的:
常规操作,生成顶层文件。
添加约束如下:
set_property PACKAGE_PIN G19 [get_ports {SW1[0]}]
set_property IOSTANDARD LVCMOS25 [get_ports {SW1[0]}]
set_property PACKAGE_PIN F19 [get_ports {SW2[0]}]
set_property IOSTANDARD LVCMOS25 [get_ports {SW2[0]}]
生成bit文件,导入到SDK。
软件部分
新建一个应用工程:
#include
#include "xscugic.h"
#include "xil_exception.h"
#define INT_CFG0_OFFSET 0x00000C00
#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_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_MASK;
mask = XScuGic_DistReadReg(InstancePtr, INT_CFG0_OFFSET + (intId/16)*4);
mask &= ~(INT_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){
printf("PL int test\n\r");
IntcInitFunction(INTC_DEVICE_ID);
while(1);
return 0;
}
首先解释一下#define INT_CFG0_OFFSET 0x00000C00是个啥:
配置软件中断触发方式的寄存器0的相对地址,这个地址+中断号/16×4变为配置这个中断的寄存器的地址,我们这里是61,62,就跳到共享中断的配置寄存器了:
可以看到11代表上升沿触发:#define INT_TYPE_RISING_EDGE 0x03
中断触发有固定的处理顺序,现阶段我们能看懂函数功能,会改就可以了。
XScuGic //产生一个中断控制器实例
XScuGic_Config //中断控制器配置实例
XScuGic_LookupConfig //找到scugic实体
XScuGic_CfgInitialize //初始化scugic
Xil_ExceptionRegisterHandler //Xilinx提供的通用异常处理程序,中断触发之后统一由XScuGic_InterruptHandler先处理,然后在HandlerTable中查找相应的处理函数
Xil_ExceptionEnable //使能异常处理
XScuGic_Connect //连接到我们自己定义的中断处理函数
XScuGic_Enable //使能我们设立的中断实例
这里我们设置的中断处理函数是:一旦按键按下,串口打印出相关信息:
static void SW_intr_Handler(void *param){
int sw_id = (int)param;
printf("SW%d int\n\r", sw_id);
}
重点:函数的参数是一个回调信息,可以将中断实例信息带给我们的处理函数。