| 软件版本:VIVADO2021.1 操作系统:WIN10 64bit
 硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA
 实验平台:米联客-MLK-H3-CZ08-7100开发板
 板卡获取平台:https://milianke.tmall.com/
 登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!
 1 概述
 当正常的程序在运行的时候,中断资源可以打断正在运行的程序,让CPU进入中断函数进行一些事务的处理。使用中断处理可以实现多任务的实时处理,可以提高多任务处理的效率。本实验继续前面的实验完成PS GPIO-MIO/EMIO输入中断的实验,请在前一个实验的基础上完成本实验。 本文实验目的: 1:掌握理解中断输入的应用场合 2:PS MIO的中断寄存器功能定义 3:掌握vitis-SDK下MIO中断功能的使用2系统框图 PS MIO一般会分配到固定的外设,包括FLASH、EMMC、TFCARD、UART、USB2.0、ETH以太网。PS的IO也可以单独配置成普通的GPIO,如果IO不够用也可以通过EMIO扩展更多IO,对于ZYNQ最多支持扩展64个EMIO。     3中断资源概述
 
     
 上图中,红色部分为PS的IO中断资源通过共享外设中断接口接入到中断控制派发器。共享资源中断资源可以同时发送给CPU0或者CPU1,ZYNQ可以确保只能有一个CPU响应当前激活的中断,另外的CPU会接收到中断号为ID# 1023的虚假中断或者下一个挂起的中断。 4PS-MIO/PS-EMIO中断寄存器介绍
 
     
 中断触发可以是上升沿,下降沿,低电平或高电平。触发方式通过INT_TYPE,INT_POLARITY和INT_ANY寄存器进行编程设置。 GPIO(PSMIO/EMIO)共享(IRQ ID#52)中断号。当检测到中断后,GPIO的INT_STAT(中断状态)会被设置成1。INT_EN和INT_DIS寄存器用于使能或者屏蔽中断。如果使能了GPIO中断,(IRQ ID#52)号中断会传入中断控制器。 如果屏蔽中断,INT_STAT的状态会一直保持,直到被清除。由于是共享一个中断号,所以软件还要通过读取INT_MASK和INT_STAT的值用来确认具体哪一个GPIO产生的中断。 通过将写1到 INT_EN使能中断,通过写1到INT_DIS寄存器来屏蔽中断。 
 PSMIO-BANK1:NT_MASK_1中断掩码寄存器偏移地址:XGPIOPS_INTMASK_OFFSET(0x0000024C) | Field Name | Bits | Type | Reset Value | Description |  | INT_MASK_1 | 31:0 | Ro | 0x0 | 中断掩码寄存器只读 0:中断使能 1:中断屏蔽 | 
 
 PSMIO-BANK1:INT_DISABLE_1中断禁用寄存器偏移地址:XGPIOPS_INTMASK_OFFSET(0x00000254) | Field Name | Bits | Type | Reset Value | Description |  | INT_DISABLE_1 | 31:0 | wo | 0x0 | 中断禁用 0:无改变 1:禁用中断 | 
 
 GPIO的INT_ENABLE_1中断使能寄存器偏移地址XGPIOPS_INTEN_OFFSET(0x00000250) | Field Name | Bits | Type | Reset Value | Description |  | INT_ENABLE_1 | 31:0 | Rw | 0x0 | 0:不改变 1:清除中断屏蔽,使能中断 | 
 
 PSMIO-BANK1:INT_STATUS_1中断状态寄存器偏移地址:XGPIOPS_INTSTS_OFFSET(0x00000258) | Field Name | Bits | Type | Reset Value | Description |  | INT_STATUS_1 | 31:0 | wtc | 0x0 | 读操作: 0:无中断 1:有中断产生 写操作: 0:无变化 1:清除中断 | 
 
 PSMIO-BANK1:INT_TPYE_1中断类型寄存器偏移地址XGPIOPS_INTTYPE_OFFSET(0x0000025C) | Field Name | Bits | Type | Reset Value | Description |  | INT_TPYE_1 | 31:0 | Rw | 0xFFFFFFFF | 0:电平触发 1:边沿触发 | 
 
 PSMIO-BANK1:INT_POLARITY_1中断触发极性寄存器偏移地址XGPIOPS_INTPOL_OFFSET(0x00000260) | Field Name | Bits | Type | Reset Value | Description |  | INT_TPYE_1 | 31:0 | Rw | 0x0 | 0:低电平或者下降沿 1:高电平或者上升沿 | 
 
 PSMIO-BANK1:INT_ON_ANY_1中断边沿类型寄存器偏移地址XGPIOPS_INTANY_OFFSET (0x00000264) | Field Name | Bits | Type | Reset Value | Description |  | INT_ON_ANY_1 | 31:0 | Rw | 0x0 | 0:当触发类型为边沿触发,单边沿触发 1:当触发类型为边沿触发,双边沿触发 | 
 
 可以设置的中断类型总结如下: | 类型 | INT_TYPE | INT_POLARITY | INT_ON_ANY |  | 上升沿中断 | 1 | 1 | 0 |  | 下降沿中断 | 1 | 0 | 0 |  | 上升沿下降沿都中断 | 1 | x | 1 |  | 高电平中断 | 0 | 1 | x |  | 低电平中断 | 0 | 0 | x | 
 关于更多寄存器的定义说明,可以参考技术手册ug585-Zynq-7000-TRM.pdf5硬件电路分析 
     
 配套工程的FPGA PIN脚定义路径为soc_prj/uisrc/04_pin/ fpga_pin.xdc。6搭建SOC系统工程 详细的搭建过程这里不再重复,对于初学者如果还不清楚如何创建SOC工程的,请学习“01Vitis Soc开发入门”这篇文章。 本文中的PS设置内容是新增加的EMIO部分,关于DDR、MIO、CPU时钟等设置请参考“01Vitis Soc开发入门”这篇文章。6.1MIO/EMIO配置 01Vitis Soc开发入门”这篇文章中已经对特定功能的MIO做了设置,只有剩余的MIO可以用于其他的自定义功能。以下设置未分配功能的MIO,以及需要扩展的EMIO的数量。     6.2EMIO在PL中定义
 设置好后,右击GPIO_0引出GPIO_0并且改名为ps_emio.     
 由于ps_emio最终需要定义到FPGA的IO中,所以我们需要引出ps_emio信号到顶层文件     
 
 复制代码`timescale 1 ps / 1 ps
module system_wrapper
   (DDR_addr,
    DDR_ba,
    DDR_cas_n,
    DDR_ck_n,
    DDR_ck_p,
    DDR_cke,
    DDR_cs_n,
    DDR_dm,
    DDR_dq,
    DDR_dqs_n,
    DDR_dqs_p,
    DDR_odt,
    DDR_ras_n,
    DDR_reset_n,
    DDR_we_n,
    FIXED_IO_ddr_vrn,
    FIXED_IO_ddr_vrp,
    FIXED_IO_mio,
    FIXED_IO_ps_clk,
    FIXED_IO_ps_porb,
    FIXED_IO_ps_srstb,
    ps_emio_tri_io);
  inout [14:0]DDR_addr;
  inout [2:0]DDR_ba;
  inout DDR_cas_n;
  inout DDR_ck_n;
  inout DDR_ck_p;
  inout DDR_cke;
  inout DDR_cs_n;
  inout [3:0]DDR_dm;
  inout [31:0]DDR_dq;
  inout [3:0]DDR_dqs_n;
  inout [3:0]DDR_dqs_p;
  inout DDR_odt;
  inout DDR_ras_n;
  inout DDR_reset_n;
  inout DDR_we_n;
  inout FIXED_IO_ddr_vrn;
  inout FIXED_IO_ddr_vrp;
  inout [53:0]FIXED_IO_mio;
  inout FIXED_IO_ps_clk;
  inout FIXED_IO_ps_porb;
  inout FIXED_IO_ps_srstb;
  inout [5:0]ps_emio_tri_io;
  wire [14:0]DDR_addr;
  wire [2:0]DDR_ba;
  wire DDR_cas_n;
  wire DDR_ck_n;
  wire DDR_ck_p;
  wire DDR_cke;
  wire DDR_cs_n;
  wire [3:0]DDR_dm;
  wire [31:0]DDR_dq;
  wire [3:0]DDR_dqs_n;
  wire [3:0]DDR_dqs_p;
  wire DDR_odt;
  wire DDR_ras_n;
  wire DDR_reset_n;
  wire DDR_we_n;
  wire FIXED_IO_ddr_vrn;
  wire FIXED_IO_ddr_vrp;
  wire [53:0]FIXED_IO_mio;
  wire FIXED_IO_ps_clk;
  wire FIXED_IO_ps_porb;
  wire FIXED_IO_ps_srstb;
  wire [0:0]ps_emio_tri_i_0;
  wire [1:1]ps_emio_tri_i_1;
  wire [2:2]ps_emio_tri_i_2;
  wire [3:3]ps_emio_tri_i_3;
  wire [4:4]ps_emio_tri_i_4;
  wire [5:5]ps_emio_tri_i_5;
  wire [0:0]ps_emio_tri_io_0;
  wire [1:1]ps_emio_tri_io_1;
  wire [2:2]ps_emio_tri_io_2;
  wire [3:3]ps_emio_tri_io_3;
  wire [4:4]ps_emio_tri_io_4;
  wire [5:5]ps_emio_tri_io_5;
  wire [0:0]ps_emio_tri_o_0;
  wire [1:1]ps_emio_tri_o_1;
  wire [2:2]ps_emio_tri_o_2;
  wire [3:3]ps_emio_tri_o_3;
  wire [4:4]ps_emio_tri_o_4;
  wire [5:5]ps_emio_tri_o_5;
  wire [0:0]ps_emio_tri_t_0;
  wire [1:1]ps_emio_tri_t_1;
  wire [2:2]ps_emio_tri_t_2;
  wire [3:3]ps_emio_tri_t_3;
  wire [4:4]ps_emio_tri_t_4;
  wire [5:5]ps_emio_tri_t_5;
  IOBUF ps_emio_tri_iobuf_0
       (.I(ps_emio_tri_o_0),
        .IO(ps_emio_tri_io[0]),
        .O(ps_emio_tri_i_0),
        .T(ps_emio_tri_t_0));
  IOBUF ps_emio_tri_iobuf_1
       (.I(ps_emio_tri_o_1),
        .IO(ps_emio_tri_io[1]),
        .O(ps_emio_tri_i_1),
        .T(ps_emio_tri_t_1));
  IOBUF ps_emio_tri_iobuf_2
       (.I(ps_emio_tri_o_2),
        .IO(ps_emio_tri_io[2]),
        .O(ps_emio_tri_i_2),
        .T(ps_emio_tri_t_2));
  IOBUF ps_emio_tri_iobuf_3
       (.I(ps_emio_tri_o_3),
        .IO(ps_emio_tri_io[3]),
        .O(ps_emio_tri_i_3),
        .T(ps_emio_tri_t_3));
  IOBUF ps_emio_tri_iobuf_4
       (.I(ps_emio_tri_o_4),
        .IO(ps_emio_tri_io[4]),
        .O(ps_emio_tri_i_4),
        .T(ps_emio_tri_t_4));
  IOBUF ps_emio_tri_iobuf_5
       (.I(ps_emio_tri_o_5),
        .IO(ps_emio_tri_io[5]),
        .O(ps_emio_tri_i_5),
        .T(ps_emio_tri_t_5));
  system system_i
       (.DDR_addr(DDR_addr),
        .DDR_ba(DDR_ba),
        .DDR_cas_n(DDR_cas_n),
        .DDR_ck_n(DDR_ck_n),
        .DDR_ck_p(DDR_ck_p),
        .DDR_cke(DDR_cke),
        .DDR_cs_n(DDR_cs_n),
        .DDR_dm(DDR_dm),
        .DDR_dq(DDR_dq),
        .DDR_dqs_n(DDR_dqs_n),
        .DDR_dqs_p(DDR_dqs_p),
        .DDR_odt(DDR_odt),
        .DDR_ras_n(DDR_ras_n),
        .DDR_reset_n(DDR_reset_n),
        .DDR_we_n(DDR_we_n),
        .FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn),
        .FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp),
        .FIXED_IO_mio(FIXED_IO_mio),
        .FIXED_IO_ps_clk(FIXED_IO_ps_clk),
        .FIXED_IO_ps_porb(FIXED_IO_ps_porb),
        .FIXED_IO_ps_srstb(FIXED_IO_ps_srstb),
        .ps_emio_tri_i({ps_emio_tri_i_5,ps_emio_tri_i_4,ps_emio_tri_i_3,ps_emio_tri_i_2,ps_emio_tri_i_1,ps_emio_tri_i_0}),
        .ps_emio_tri_o({ps_emio_tri_o_5,ps_emio_tri_o_4,ps_emio_tri_o_3,ps_emio_tri_o_2,ps_emio_tri_o_1,ps_emio_tri_o_0}),
        .ps_emio_tri_t({ps_emio_tri_t_5,ps_emio_tri_t_4,ps_emio_tri_t_3,ps_emio_tri_t_2,ps_emio_tri_t_1,ps_emio_tri_t_0}));
endmodule
 
 以上代码中,原语IOBUF对于很多初学者来说,可能会有疑惑,所以有必要介绍下如何使用。 复制代码   IOBUF IOBUF_inst (
      .O(O),   // 1-bit output: Buffer output  方向是外部引脚IO 输入到FPGA内部逻辑
      .I(I),   // 1-bit input: Buffer input方向是FPGA内部逻辑输出到外部引脚IO 
      .IO(IO), // 1-bit inout: Buffer inout (connect directly to top-level port) 外部引脚IO
      .T(T)    // 1-bit input: 3-state enable input 定义输入还是输,当T=0 IO是三态的可以用于输入,当T=1 是输出
   );
 6.3编译并导出平台文件
 以下步骤简写,有不清楚的看第一篇文章。 1:单击Block文件à右键àGenerate the Output ProductsàGlobalàGenerate。 2:单击Block文件à右键à Create a HDL wrapper(生成HDL顶层文件)àLet vivado manager wrapper and auto-update(自动更新)。 3:添加配套工程路径下uisrc/04_pin/fpga_pin.xdc约束文件 4:生成Bit文件。 5:导出到硬件: FileàExport HardwareàInclude bitstream 6:导出完成后,对应工程路径的soc_hw路径下有硬件平台文件:system_wrapper.xsa的文件。根据硬件平台文件system_wrapper.xsa来创建需要Platform平台。     7搭建Vitis-sdk工程
 创建soc_base sdk platform和APP工程的过程不再重复,如果不清楚请参考本章节第一个demo。 7.1创建SDK Platform工程 
     
 右击soc_base编译,编译的时间可能会有点长7.2创建mio_emio_intr APP工程 1:创建测试APP工程     
     
   
     
     
     
 2: 在我们提供例程的文件夹中找到源码文件,并进行复制。     
     
 3:右击编译工程 4:右击工程,选择Debug as ->Debug configurations。     
 打开串口     
 5:单击窗口上的运行按钮
    编辑,运行程序
      8程序分析
 8.1init_intr_sys函数
 
 复制代码void init_intr_sys(void) {
Init_Intr_System(&Intc);
Gpiops_Setup_Intr_System(&Intc, &Gpio, GPIO_INTERRUPT_ID);
Setup_Intr_Exception(&Intc); }
 
 此函数在程序中通过调用相关中断函数实现中断功能。为了实现多类型中断,用户必须根据以下方式设置中断。8.2Init_Intr_System(&Intc)函数 
 复制代码int Init_Intr_System(XScuGic * IntcInstancePtr) {
int Status;
XScuGic_Config *IntcConfig; /*
* Initialize the interrupt controller driver so that it is ready to
* use. */
IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID); if (NULL == IntcConfig) {
return XST_FAILURE; }
Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig, IntcConfig->CpuBaseAddress);
if (Status != XST_SUCCESS) {
return XST_FAILURE; }
return XST_SUCCESS; }
 1:XScuGic_LookupConfig(INTC_DEVICE_ID) 函数
 
     右击XScuGic_ConfigTable查看参数定义:
 
     可以继续右击以下参数查看其定义
 
 ZYNQ中定义     
 MPSOC中定义     
 2: XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,IntcConfig->CpuBaseAddress)
 中断部分主要内容是回调函数的设置,以下回调函数相关参数定义如下:
 
     以下代码对所有为定义的全局中断进行定义回调函数和回调参数。
 
     StubHandler函数定义如下:
 
     8.3Setup_Intr_Exception函数
 
 复制代码void Setup_Intr_Exception(XScuGic * IntcInstancePtr) {
/* Enable interrupts from the hardware */
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler, (void *)IntcInstancePtr);
Xil_ExceptionEnable(); }
 
 这个函数中对设置全局中断回调函数,以及使能全局中断1:Xil_ExceptionInit()函数 这个函数目前说明都没做,只是未来保留兼容性,预留在这里。     2:Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,(Xil_ExceptionHandler)XScuGic_InterruptHandler,(void *)IntcInstancePtr)函数
 
     
 2.1XIL_EXCEPTION_ID_INT定义     
 2.2: XExc_VectorTableEntry定义 任何的中断都可以理解为异常处理,这里定义了MPSOC或者ZYNQ支持的异常处理类型。     
 2.3XScuGic_InterruptHandler函数 可以看到这个函数会根据读取到的中断号调用相应的回调函数,这个回调函数会在具体的外设中断初始化中设置。     
 2.3中断异常回调函数     
 由于XIL_EXCEPTION_ID_INT=5所以Xil_ExceptionNullHandler在中断产生的时候会被调用。     
     3:Xil_ExceptionEnable()函数
 这条函数最终指向了汇编指令:     
     8.4Gpiops_init函数
 
 复制代码int Gpiops_init(XGpioPs *InstancePtr, u32 DeviceId)
{
        u8 Bank;
        u8 PinNumber0;
        u8 PinNumber1;
        u8 PinNumber2;
        int Status;
        XGpioPs_Config *ConfigPtr;
        /* Initialize the GPIO driver. */
        ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
        Status = XGpioPs_CfgInitialize(InstancePtr, ConfigPtr,ConfigPtr->BaseAddr);
     //set LED0~LED4 输出
        XGpioPs_SetDirectionPin(InstancePtr, LED0, 0x1);
        XGpioPs_SetDirectionPin(InstancePtr, LED1, 0x1);
        XGpioPs_SetDirectionPin(InstancePtr, LED2, 0x1);
        XGpioPs_SetDirectionPin(InstancePtr, LED3, 0x1);
        XGpioPs_SetDirectionPin(InstancePtr, LED4, 0x1);
        //set BTN0~BTN2 输入
        XGpioPs_SetDirectionPin(InstancePtr,BTN0, 0x0);
        XGpioPs_SetDirectionPin(InstancePtr,BTN1, 0x0);
        XGpioPs_SetDirectionPin(InstancePtr,BTN2, 0x0);
     // set LED0~LED4 输出使能
        XGpioPs_SetOutputEnablePin(InstancePtr, LED0, 1);
        XGpioPs_SetOutputEnablePin(InstancePtr, LED1, 1);
        XGpioPs_SetOutputEnablePin(InstancePtr, LED2, 1);
        XGpioPs_SetOutputEnablePin(InstancePtr, LED3, 1);
        XGpioPs_SetOutputEnablePin(InstancePtr, LED4, 1);
// set LED0~LED4 输出高电平1
        XGpioPs_WritePin(InstancePtr, LED0, 1);
        XGpioPs_WritePin(InstancePtr, LED1, 1);
        XGpioPs_WritePin(InstancePtr, LED2, 1);
        XGpioPs_WritePin(InstancePtr, LED3, 1);
        XGpioPs_WritePin(InstancePtr, LED4, 1);
//获取当前GPIO所在的BANK以及所在BANK的IO序号
        XGpioPs_GetBankPin(BTN0, &Bank, &PinNumber0);
        XGpioPs_GetBankPin(BTN1, &Bank, &PinNumber1);
        //边沿触发,下降沿触发
        XGpioPs_SetIntrType(InstancePtr, Bank, 0xFFFFFFFF, 0x0, 0x00);
        XGpioPs_IntrEnable(InstancePtr, Bank, (1 << PinNumber0)|(1 << PinNumber1));
        XGpioPs_GetBankPin(BTN2, &Bank, &PinNumber2);
        //边沿触发,下降沿触发
        XGpioPs_SetIntrType(InstancePtr, Bank, 0xFFFFFFFF, 0x0, 0x00);
        XGpioPs_IntrEnable(InstancePtr, Bank, (1 << PinNumber2));
    return Status;
}
 
 gpiops_intr.c文件中,int Gpiops_init(XGpioPs *InstancePtr, u32 DeviceId)函数负责初始化GPIO的输入,输出,以及设置GPIO的中断触发方式。 为了正确初始化相关GPIO BANK的中断寄存器,这里调用来XGpioPs_GetBankPin()函数,通过预先定义的GPIO序号,获取当前GPIO所在的BANK以及所在BANK的IO序号。然后执行XGpioPs_SetIntrType(InstancePtr, Bank, 0xFFFFFFFF, 0x0, 0x00);设置中断触发的方式。最后执行XGpioPs_IntrEnable(InstancePtr, Bank, (1 << PinNumber0)|(1 << PinNumber1));使能相关GPIO中断。1:XGpioPs_LookupConfig(DeviceId)函数 此函数中XGpioPs_ConfigTable定义了GPIO的参数信息,右击可以查看具体信息     
 
     
 继续右击以下GPIO地址定义,可以查到GPIO外设的基地址:     
 
     2:XGpioPs_CfgInitialize(InstancePtr, ConfigPtr, ConfigPtr->BaseAddr)函数
 
     3:XGpioPs_SetDirectionPin(InstancePtr, LED0,1)函数
 设置IO方向,最后一个参数0代表输入,1代表输出。     
 以上代码首先需计算IO所在的BANK和在这个BANK中IO序号。通过函数XGpioPs_GetBankPin((u8)Pin, &Bank, &PinNumber)计算。这里以MIO51来说,其位于BANK1中 每个BANK的PS IO方向控制寄存器偏移计算=BANK *XGPIOPS_REG_MASK_OFFSET+ XGPIOPS_DIRM_OFFSET我们主要看XGPIOPS_DIRM_OFFSET。 其中XGPIOPS_DIRM_OFFSET = 0x00000204代表了相对每个BANK的偏移地址,我们看方向寄存器的定义: 4:XGpioPs_SetOutputEnablePin(InstancePtr, LED, 1)函数| Field Name | Bits | Type | Reset Value | Description |  | DIRECTION_1 | 31:0 | Rw | 0x0 | 0:输入 1:输出 | 
 
     
 以上代码首先需计算IO所在的BANK和在这个BANK中IO序号。通过函数XGpioPs_GetBankPin((u8)Pin, &Bank, &PinNumber)计算。以MIO51来说,其位于BANK1每个BANK的PS IO方向控制寄存器偏移计算=BANK *XGPIOPS_REG_MASK_OFFSET+ XGPIOPS_OUTEN_OFFSET我们主要看XGPIOPS_ XGPIOPS_OUTEN_OFFSET其中XGPIOPS_OUTEN_OFFSET = 0x00000208代表了相对每个BANK的偏移地址,我们看方向寄存器的定义: 
 5:XGpioPs_SetIntrType(InstancePtr, Bank, 0xFFFFFFFF, 0x0, 0x00)| Field Name | Bits | Type | Reset Value | Description |  | OP_ENABLE_1 | 31:0 | Rw | 0x0 | 0:禁止输出 1:使能输出 | 
 
     
 XGpioPs_SetIntrType共有5个参数,我们重点看IntrType、IntrPolarity、IntrOnAny这三个参数: IntrType寄存器相关位:0电平触发;1边沿触发 IntrPolarity寄存器相关位:0低电平或下降沿触发;1上升沿或高电平触发 IntrOnAny寄存器相关位:0单边沿触发;1双边沿触发 我们这里设置的是单边下降沿触发,所以IntrType=0xffffffff;IntrPolarity=0x0;IntrOnAny=0x0; 参数的设置通过以下三个函数设置寄存器,具体参考ZYNQ ug585中的说明: IntrType寄存器地址:(Bank) * XGPIOPS_REG_MASK_OFFSET) +XGPIOPS_INTTYPE_OFFSET IntrPolarity寄存器地址:(Bank) * XGPIOPS_REG_MASK_OFFSET) +XGPIOPS_INTPOL_OFFSET, IntrPolarity) IntrOnAny寄存器地址:(Bank) * XGPIOPS_REG_MASK_OFFSET) +XGPIOPS_INTANY_OFFSET, IntrOnAny) 我们这里以BANK1的MIO50计算下相关寄存器的地址: IntrType寄存器地址  = 1* 0x00000040U  + 0x0000021CU IntrPolarity寄存器地址    =1* 0x00000040U  + 0x00000220U IntrOnAny寄存器地址     =1* 0x00000040U  + 0x00000224U 
 PSMIO-BANK1:INT_TPYE_1中断类型寄存器偏移地址XGPIOPS_INTTYPE_OFFSET(0x0000025C)=0x00000040U+0x0000021C | Field Name | Bits | Type | Reset Value | Description |  | INT_TPYE_1 | 31:0 | Rw | 0xFFFFFFFF | 0:电平触发 1:边沿触发 | 
 
 PSMIO-BANK1:INT_POLARITY_1中断触发方式寄存器偏移地址XGPIOPS_INTPOL_OFFSET(0x00000260)= 0x00000040U+0x00000220 | Field Name | Bits | Type | Reset Value | Description |  | INT_TPYE_1 | 31:0 | Rw | 0x0 | 0:低电平或者下降沿 1:高电平或者上升沿 | 
 
 PSMIO-BANK1:INT_ON_ANY_1边沿类型寄存器偏移地址XGPIOPS_INTANY_OFFSET (0x00000264)= 0x00000040U+0x00000224 | Field Name | Bits | Type | Reset Value | Description |  | INT_ON_ANY_1 | 31:0 | Rw | 0x0 | 0:当触发类型为边沿触发,单边沿触发 1:当触发类型为边沿触发,双边沿触发 | 
 6:XGpioPs_IntrEnable(InstancePtr, Bank, (1 << PinNumber0)|(1 << PinNumber1))
 
     
 GPIO的INT_ENABLE_1中断使能寄存器偏移地址XGPIOPS_INTEN_OFFSET=0x00000250 8.5Gpiops_Setup_Intr_System函数| Field Name | Bits | Type | Reset Value | Description |  | INT_ENABLE_1 | 31:0 | Rw | 0x0 | 0:不改变 1:清除中断屏蔽,使能中断 | 
 
 复制代码void Gpiops_Setup_Intr_System(XScuGic *GicInstancePtr, XGpioPs *InstancePtr, u16 IntrId)
{
        XScuGic_Connect(GicInstancePtr, IntrId,
                        (Xil_ExceptionHandler)XGpioPs_IntrHandler,//set up the interrupt
                        (void *)InstancePtr);
            /* Set the handler for gpio interrupts. */
            XGpioPs_SetCallbackHandler(InstancePtr, (void *)InstancePtr, Gpiops_IntrHandler);
    XScuGic_Enable(GicInstancePtr, IntrId);//enable the interrupt for GPIO at GIC
 }
 1:XScuGic_Connect(GicInstancePtr, IntrId, (Xil_ExceptionHandler)XGpioPs_IntrHandler, (void *)InstancePtr);
 这里把GIC的48号中断对应的回调函数XGpioPs_IntrHandler和回调参数InstancePtr关联,这样当XGpioPs_IntrHandler函数回调的时候,可以通过参数InstancePtr继续回调。
 
     
 以下是GPIO的GIC中断号     读者可以看前面Setup_Intr_Exception函数中关于Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,(Xil_ExceptionHandler)XScuGic_InterruptHandler,(void *)IntcInstancePtr)函数的说明再看这里就能明白了。
 
 再看下回调函数XGpioPs_IntrHandler(const XGpioPs *InstancePtr)     
 这是GPIO的中断回调函数,当GPIO中断产生后这个函数会被调用,在这个函数中再进一步判断是哪一个BANK,哪一个IO产生了中断,并且再次调用 InstancePtr->Handler(InstancePtr->CallBackRef, Bank,(IntrStatus & IntrEnabled))函数,再次调用回调函数,并且传输IO所在BANK和哪一个IO产生了中断。 这里面我们有必要看下XGpioPs_IntrGetStatus(InstancePtr, Bank)和XGpioPs_IntrGetEnabled(InstancePtr,Bank)函数。 XGpioPs_IntrGetStatus(InstancePtr, Bank)读取当前bank的中断状态寄存器;     
 PSMIO-BANK1:INT_STATUS_1中断状态寄存器偏移地址:XGPIOPS_INTSTS_OFFSET= 0x00000258 | Field Name | Bits | Type | Reset Value | Description |  | INT_STATUS_1 | 31:0 | wtc | 0x0 | 读操作: 0:无中断 1:有中断产生 写操作: 0:无变化 1:清除中断 | 
 XGpioPs_IntrGetEnabled(InstancePtr,Bank)读取对应bank已经设置了中断使能     
 INT_MASK_1中断掩码寄存器偏移地址:XGPIOPS_INTMASK_OFFSET=0x0000020C | Field Name | Bits | Type | Reset Value | Description |  | INT_MASK_1 | 31:0 | Ro | 0x0 | 只读寄存器 0:中断使能 1:中断屏蔽 | 
 
 最后通过if ((IntrStatus & IntrEnabled) != (u32)0)判断那个BANK 哪一个IO产生了中断。 2:XGpioPs_SetCallbackHandler(InstancePtr, (void *)InstancePtr, Gpiops_IntrHandler)函数
 这个函数就是前面的中断回调函数中进一步调用     
 我们看下参数XGpioPs_Handler FuncPointer,可以看到整个回调函数就是我们前面GPIO中断回调函数进一步回调的函数。定义这个回调函数的目的主要是未来给用户自己编写的中断回调传递相关的参数,配合vitis-sdk的中断函数使用。 
     3:XScuGic_Enable(GicInstancePtr, IntrId)函数
 在以下代码中,首先绑定中断号,CPU号,我们也可以进一步分中断     
 5.1XScuGic_InterruptMaptoCpu(InstancePtr, Cpu_Id, Int_Id)函数 在这个函数中具体中断和CPU如何关联,如何设置SPI(共享外设中断)中断寄存器的相关位我们无法进一步分分析,到此为止。     8.6 Gpiops_IntrHandler(void *CallBackRef, u32 Bank, u32 Status)函数
 这个函数中是用户代码中的回调函数,可以直接获取IO所在BANK以及整个BANK中哪一个IO位产生了中断。 复制代码static void Gpiops_IntrHandler(void *CallBackRef, u32 Bank, u32 Status) {
printf("Data read from GPIO intrrupt bank= 0x%x , IO_bit=0x%x \n\r", Bank ,Status); }
 9方案演示
 9.1硬件准备
 实验需要用到JTAG下载器、USB转串口外设,另外需要把核心板上的2P模式开关设置到JTAG模式,即ON ON
 
     9.2实验结果
 每次有按键按下的时候,会产生一个中断,中断函数中会读取每个按键的按键值     
 
 
 |