摘要
AXI GPIO是ZYNQ的一个IP核,它能够将PS侧的AXI4-Lite接口转成PL侧的IO口,可解决PS侧IO口不够用的问题。本文就AXI GPIO的概念、作用、配置与使用做了详细说明,展示了示例的Vivado工程和AXI GPIO输入、输出与中断配置的代码。
关键词:AXI GPIO;ZYNQ;AXI4-Lite;GPIO;中断
前言
本文参考:PG144 - AXI GPIO v2.0 Product Guide (v2.0)
一. AXI GPIO的概念与作用
AXI GPIO是ZYNQ PL部分的一个IP软核,此IP核提供AXI-Lite Master接口转GPIO的功能,且一个AXI-Lite Master通过AXI interconnect可以与多个AXI GPIO IP核相连,如下图所示。也就是说PS部分通过GP接口以及AXI GPIO IP核可实现多个GPIO,一个GP接口最多可以接几个AXI GPIO IP核我不知道,手册中没写,我这里接了4个没什么问题。.

我们知道ZYNQ的PS可通过EMIO来使用PL部分的引脚实现GPIO功能,但当使用的引脚很多时,PS程序中控制引脚方向、中断等将变得繁琐,这时如果使用PS的GP接口(AXI3 Master)接口通过AXI Interconnect连接AXI GPIO IP核,就可以在PS部分通过更简单的程序方便地控制众多PL引脚实现GPIO功能。
所以,从功能上来说,PS通过EMIO还是AXI GPIO来实现GPIO没有区别,都能输入/输出以及产生中断,但AXI GPIO在多引脚时在程序的复杂度方面有一定优势。
二. AXI GPIO IP核设置与使用
2.1 AXI GPIO的最大工作时钟频率
需要注意,AXI GPIO的AXI-Lite接口的时钟频率,超频可能会导致此IP核无法正常工作。

2.2 AXI GPIO设置

AXI GPIO IP核默认为单通道,可选GPIO宽度为1~32,可设置GPIO方向(注意如果在这里设置了All Input/Output,那么在PS程序中就无需再设置GPIO方向,设置了也无效,建议不要在这里设置,通过PS程序去设置,保持灵活性),可设置输出默认值与三态默认值。
可勾选Enable Dual Channel,打开双通道。
可勾选Enable Interrupt,打开GPIO中断功能,此中断属于PL对PS的中断,所以需要连接到ZYNQ7核的IRQ_F2P端口,如下图所示。

因为使用了AXI接口,我们知道AXI是读写数据的,它必须对应存储器(RAM,DDR等),所以在设置好IP核并连线完成后,需要对每个AXI GPIO的S_AXI进行存储器地址映射,规定S_AXI去读写哪个地址段。
打开Address Editor窗口,右击选择Assign All即可。

效果如下图。

这里需要说明,因为在PS的库函数中对多个AXI GPIO的情况是有排序的(在xparameters.h中每个AXI GPIO对应不同的ID,如下图),我们需要知道PL的Block Design中哪个AXI GPIO对应PS库中的AXI GPIO 0,哪个对应AXI GPIO 1。
这里的排序依据是ip核的名称,与同GP0接口相连还是GP1接口相连没有关系,PS并不区分axi gpio与哪个GP相连。axi_gpio_0会被设为ID 0,ID从0开始向后排,若不存在axi_gpio_0,则axi_gpio_1会被设为ID 0,以此类推。
明白PL中的axi gpio与PS库函数中的ID的对应关系是重要的,它帮助我们对引脚命名,以及在PS中操作我们想操作的GPIO。

2.3 关于AXI GPIO中断的细节
注意:
1.AXI GPIO只能使能整个通道中断,而无法像EMIO一样单独使能通道中某个引脚的中断,当使能某个通道中断后,该通道所有输入引脚均能产生中断信号,效果完全相同。
一种可行的将中断固定到引脚的办法是设置通道中其它引脚为输出,输出引脚无法产生中断,中断就相当于固定到了唯一的输入引脚上,
2.因为AXI GPIO中断属于IRQ_F2P,而IRQ_F2P的中断类型只能设置为上升沿或者高电平,而不能是下降沿或者低电平。如下图(参考ug585 Table7-4)。

三. 在PS中控制AXI GPIO输入、输出与中断
3.1 AXI GPIO作为输入输出使用,不使用中断
步骤:初始化AXI GPIO -> 设置AXI GPIO方向 -> 读/写AXI GPIO
#include "xgpio.h"
#include "sleep.h"
#define AXIGPIO_CHANNEL_1 1U
#define AXIGPIO_CHANNEL_2 2U
XGpio axiGpio0;
XGpio axiGpio1;
int main(void)
{
// 初始化AXI GPIO
XGpio_Initialize(&axiGpio0, XPAR_GPIO_0_DEVICE_ID);
XGpio_Initialize(&axiGpio1, XPAR_GPIO_1_DEVICE_ID);
// 设置AXI GPIO的方向,对应位为0表示输出,为1表示输入
XGpio_SetDataDirection(&axiGpio0, AXIGPIO_CHANNEL_1, 0x0); // 设置axiGpio0的通道1为全输出
XGpio_SetDataDirection(&axiGpio1, AXIGPIO_CHANNEL_2, 0xFFFF0000); // 设置axiGpio1的通道2高16位输入,低16位输出
u32 axiGpio1_ch2_data = 0;
while(1)
{
sleep(1);
XGpio_DiscreteWrite(&axiGpio0, AXIGPIO_CHANNEL_1, 0x3); // 向axiGpio0的通道1的第0位和第1位写1
axiGpio1_ch2_data = XGpio_DiscreteRead(&axiGpio1, AXIGPIO_CHANNEL_2); // 读取axiGpio1的通道2的值,输出引脚也可读
sleep(1);
}
return 0;
}
3.2 使用AXI GPIO中断
步骤:初始化AXI GPIO -> 打开系统的中断处理功能 -> 初始化中断控制器 -> 设置中断优先级与类型,关联中断处理函数 -> 使能中断
#include "xgpio.h"
#include "xscugic.h"
#include "xil_exception.h"
#include "sleep.h"
#define AXIGPIO_CHANNEL_1 1U
#define AXIGPIO_CHANNEL_2 2U
#define AXIGPIO_IRQ_TYPE_HIGH 1U
#define AXIGPIO_IRQ_TYPE_PEDGE 3U
#define AXIGPIO_IRQ_PRIORITY 128U
XGpio axiGpio0;
XScuGic scuGic;
void axiGpio0_Handler(void *CallbackRef);
int axiGpio0_intrFlag = 0;
int main(void)
{
// 初始化AXI GPIO
XGpio_Initialize(&axiGpio0, XPAR_GPIO_0_DEVICE_ID);
// 打开系统的中断处理功能
Xil_ExceptionInit(); // 初始化异常句柄,只在很早版本的bsp中需要,此处为了兼容性保留
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, &scuGic);
Xil_ExceptionEnable(); // 使能系统中断
// 初始化中断控制器
XScuGic_Config *scuGicConfig;
scuGicConfig = XScuGic_LookupConfig(XPAR_XSCUTIMER_0_DEVICE_ID); // 根据器件ID查找配置
XScuGic_CfgInitialize(&scuGic, scuGicConfig, scuGicConfig->CpuBaseAddress);
// 设置对应ID中断的的优先级与触发类型
XScuGic_SetPriorityTriggerType(&scuGic, XPAR_FABRIC_GPIO_0_VEC_ID, AXIGPIO_IRQ_PRIORITY, AXIGPIO_IRQ_TYPE_HIGH);
// 连接中断ID与中断服务函数
XScuGic_Connect(&scuGic, XPAR_FABRIC_GPIO_0_VEC_ID, (Xil_InterruptHandler)axiGpio0_Handler, &axiGpio0);
// 启用对应中断ID的中断源
XScuGic_Enable(&scuGic, XPAR_FABRIC_GPIO_0_VEC_ID);
// 启动AXI GPIO中断
XGpio_InterruptGlobalEnable(&axiGpio0);
// 使能对应通道的中断
XGpio_InterruptEnable(&axiGpio0, AXIGPIO_CHANNEL_1);
while(1)
{
sleep(1);
if (axiGpio0_intrFlag)
{
xil_printf("This is axiGpio0 interrupt!");
axiGpio0_intrFlag = 0; // 复原中断标志
XGpio_InterruptEnable(&axiGpio0, AXIGPIO_CHANNEL_1); // 重新使能中断
}
sleep(1);
}
return 0;
}
void axiGpio0_Handler(void *CallbackRef)
{
XGpio *axiGpioPtr = (XGpio *)CallbackRef;
axiGpio0_intrFlag = 1;
// 关闭中断
XGpio_InterruptDisable(axiGpioPtr, AXIGPIO_CHANNEL_1);
// 清除中断寄存器,不清除无法再次进入中断
XGpio_InterruptClear(axiGpioPtr, AXIGPIO_CHANNEL_1);
}

徐晓康的博客持续分享高质量硬件、FPGA与嵌入式知识,软件,工具等内容,欢迎大家关注。