前言
ZYNQ PS 中包含一组丰富的外设,如常用的 UART 控制器、 I2C控制器,CAN 控制器以及 GPIO 等等。他们提供了各种工业标准的接口,用于和外部设备进行通信。 其中 GPIO 外设作为最基本的一种外设也是我从单片机过度来最熟悉的了,一般用于控制一些简单的外设,如LED 和蜂鸣器,此时 GPIO用作输出;也可以用于观测一些简单外设的状态,如按键,此时 GPIO用作输入。在实际的项目工程中,也常常需要软件复位操作,或者作为使能位,甚至还能作为中断用(想不到吧惊不惊喜。中断控制器16个中断不够用了,就可以用GPIO中断啊)相比之前在单片机上不复用时的PWM,输入捕获等应用,也不失为一种新的思路吧,简单但也不要小觑。
GPIO 可以通过 MIO 连接到 PS 端的引脚,也可以通过 EMIO 连接到 PL,也可以通过AXI总线,将AXI_GPIO看做GPIO的IP核,挂在PS上。
一.MIO
1. 简介
阅读芯片手册(14章GPIO部分),Zynq-7000 系列芯片一般有54 个 MIO,即multiuse I/O,多功能IO接口在芯片外部有54个引脚。这些引脚可以用在GPIO、SPI、UART、TIMER、Ethernet、USB等功能上,用于观测和控制器件引脚的状态。每个引脚都同时具有多种功能,故叫多功能。这些 IO 与 PS 直接相连。不需要添加引脚约束,MIO 信号对 PL部分是透明的,不可见。所以对 MIO 的操作可以看作是纯 PS 的操作。
下图 1.1 是 GPIO 的框图,从中我们可以看到 GPIO 分为4个Bank,其中Bank0和 Bank1连接到MIO;而 Bank2和Bank3连接到 EMIO。除 Bank1之外的 Bank 都具有32bit,Bank只具有 22bit 是因为总共只有54 MIO,其中32bit 的Bank0 控制了 MIO[0~31],剩下的 MIO[31~53]就由 22bit 的 Bank1 控制。Bank2和Bank3 用于控制扩展的MIO 即 EMIO,也就是说总共可以有32+32=64个EMIO。
PS 所有的外设都可以通过 MIO 访问,这些外设也是与 MIO 进行连接,每个 MIO 虽然可以独立控制,以及独立驱动单个引脚的外设,但对于 QSPI、 USB、以太网等这些外设,其于MIO的连接有着特殊的要求,如图1.2 所示,(图中灰色框表示在 CLG225 封装的芯片中不可用)对于以太网而言,其只能与 MIO16~27和 MIO28~39 引脚连接,而且以太网与 MIO28 连接的引脚只能作为以太网的 tx_clk 使用,可见当其作为以太网的接口引脚时,相应的 MIO 的功能就已经确定下来了。 MIO 还有一特点,如 MIO28~39 引脚即可以与以太网进行连接,也可以作为 USB 以及其它外设的接口引脚,所以当我们设计 PS 的外设时要合理分配 MIO。从图 2.1.2 MIO 一览表中我们可以看到 MIO 一但选定,引脚位置就已经确定下来了,不需要添加引脚约束。
通过图 1.2 MIO一览表我们了解到了MIO与外设的连接情况,例如当我们想要两个 MIO 作为UART的接口时,可以使用 MIO8,9或者 MIO10、11等引脚,如果选用 MIO8、9 作为 UART 的接口,MIO8 就是UART 的tx引脚,MIO9 为rx引脚。MIO的PS外设的大多数 I/O信号( USB 除外)可以通过 MIO 路由到 PS 引脚,或通过 EMIO 路由到 PL 引脚。除千兆以太网外,大多数外设还在MIO和EMIO之间保持相同的协议。千兆以太网为减少引脚数,使用4位的 RGMII 接口以 250 MHz 数据速率( 125MHz 时钟,双倍数据速率)通过 MIO。如果接到EMIO,就使用一个以125 MHz 数据速率运行的8位 GMII接口。需要注意的是Quad-SPI、USB 和SMC接口不适用于PL的 EMIO 接口。
PS 通过 APB 总线对控制、状态寄存器的读写实现对 GPIO 的驱动,有诸如DATA_RO数据只读寄存器DIRM方向模式寄存器OEN使能输出寄存器等,从这些寄存器中我们可以看到,如果配置引脚为输出,不仅需要设置方向,还要使能输出。关于这些寄存器的具体介绍,可参考手册。需要说明的是我们在程序中操作 MIO 时直接调用 Xilinx 官方提供的函数即可,无需直接操作这些寄存器。再次强调的还是 MIO 信号对 PL 部分是透明的,所以对 MIO 的操作是纯 PS 的操作,且每个 GPIO都可独立动态编程为输入、输出或中断检测,此处需要注意的是 MIO7 和 8 只能做为输出 IO 使用。
2.软件设计
逻辑部分就交给伟大的逻辑工程师了,直接跳过来看软件部分
xparameters.h
/* Definitions for peripheral PS7_GPIO_0 */
#define XPAR_PS7_GPIO_0_DEVICE_ID 0
#define XPAR_PS7_GPIO_0_BASEADDR 0xE000A000
#define XPAR_PS7_GPIO_0_HIGHADDR 0xE000AFFF
打开bsp库函数里的xparameters.h,能读到GPIO 的控制和状态寄存器基地址为:0xE000_A000, SDK 下软件操作底层都是对于内存地址空间的操作,不需硬件配置。一个GPIO端口至少需要两个寄存器,一个控制用的通用IO口控制寄存器和一个存放数据的通用IO端口数据寄存器。
GPxCON寄存器为控制寄存器,它的每一位对应一个引脚,其某位设为0,相应的引脚为输出引脚,为1时为输入引脚。GPxDAT为数据寄存器,当引脚设为输入时,读此寄存器可以知道相应的引脚的电平状态为高还是低,当引脚设为输出时,写此寄存器可令此引脚输出为高电平或者低电平。
原理大概就是这些,可以导入官方例程学习调用Xilinx提供的库函数,写自己还是要上手来一遍滴
动手动手(摩拳擦掌)就假设我们要用MIO作为使能,日常调用我们熟悉的xparameters.h
#define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID
#define MEMCTR xx//多少自己填
XGpioPs Gpio;//Xilinx这边的老套路了,基本代码结构都是这样
void main()
{
int Status;
XGpioPs_Config *ConfigPtr; //同样xilinx的常用写法,学着就对了
ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
Status = XGpioPs_CfgInitialize(&Gpio, ConfigPtr,
ConfigPtr->BaseAddr);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
XGpioPs_SetDirectionPin(&Gpio, MEMCTR, 1);//设置指定引脚的方向: 0 输入, 1 输出
XGpioPs_SetOutputEnablePin(&Gpio, MEMCTR, 1);//使能指定引脚输出: 0 禁止输出使能, 1 使能输出
XGpioPs_WritePin(&Gpio, MEMCTR, 0x0);//禁用mem
XGpioPs_WritePin(&Gpio, MEMCTR, 0x1);//使能mem
}
宏定义了 GPIO_DEVICE_ID,使其为 XPAR_XGPIOPS_0_DEVICE_ID,便于修改
宏定义了 MEMCTR,其值为xx。一般对于这种 MIO 的使用,驱动某一引脚,在代码中使用该引脚对应的 MIO 数字标号即可。
主函数里先获取 GPIO 的 ID 和基址信息并初始化其配置,以及判断是否初始化成功。代码中XGpioPs_SetDirectionPin 和 XGpioPs_SetOutputEnablePin 函数分别是设置 GPIO 的方向(输入还是输出)函数和使能输出函数, XGpioPs_WritePin 是向指定 GPIO 引脚写入数据的函数,这三个函数是官方提供的,可以直接调用,如有需要可以在顶层继续封装。
二.EMIO
为什么引进EMIO呢?Extended MIO,顾名思义,拓展MIO。分布在BANK2、BANK3,依然属于Zynq的PS部分,只是连接到了PL上,再从PL的引脚连到芯片外面实现数据输入输出。
什么意思呢?也就是说啊,点亮PS的对应的控制器,但是此时的外部引脚是不确定的;必须通过PL端进行管脚约束,才能知道这个控制器的外部引脚连到了哪里。再概括下,PS控制了时序,PL端提供了引脚。还不明白,那无所谓了,会用就行。
当 MIO 不够用时, PS 可以通过驱动 EMIO 控制 PL 部分的引脚 。当我们想通过PS来访问PL又不想浪费AXI总线时,就通过EMIO接口来访问。在54个I/O中,有一些只能用于MIO,大部分可以用于MIO与EMIO,一些接口信号线只能通过EMIO访问。EMIO 有 64 个引脚可供我们使用。
一定记得,逻辑这边能配的是位宽,就是说逻辑分配了多大的位宽,ps这边就有多少个EMIO,自然起始是从54开始的咯,那为啥是54嘞?
前面MIO部分提到了ZYNQ 的 GPIO 被分成了 4 组,其中通过 EMIO 扩展的 GPIO 接口位于BANK2 和 BANK3 中, 如图 3.1.1 所示。在本次实验中我们通过 EMIO 扩展了 1 个 GPIO 信号, 即 BANK2的 EMIO0。由于 GPIO 的 BANK0 和 BANK1 分别有 32 和 22 个信号,所以 BANK2 的 EMIO0 编号为 54(从 0 开始编号)
来写代码吧
#define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID
#define MEMCTR 54//
XGpioPs Gpio;//
void main()
{
int Status;
XGpioPs_Config *ConfigPtr; //
ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
Status = XGpioPs_CfgInitialize(&Gpio, ConfigPtr,
ConfigPtr->BaseAddr);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
XGpioPs_SetDirectionPin(&Gpio, MEMCTR, 1);//设置指定引脚的方向: 0 输入, 1 输出
XGpioPs_SetOutputEnablePin(&Gpio, MEMCTR, 1);//使能指定引脚输出: 0 禁止输出使能, 1 使能输出
XGpioPs_WritePin(&Gpio, MEMCTR, 0x0);//禁用mem
XGpioPs_WritePin(&Gpio, MEMCTR, 0x1);//使能mem
}
等等等等有点眼熟,没错,想不到吧,还是我!
所以得出结论:
通过 EMIO 扩展的GPIO 接口的使用方法和 MIO 没有任何区别。
当然了,EMIO那肯定要用于不同的功能啊,不然闲的没事加这干嘛,工程中还会用到GPIO中断的情况,详细代码贴到zynq中断篇吧
三.AXI_GPIO
之前通过EMIO实现了PS端与PL端的交互,而PS与PL最主要的连接方式则是一组AXI接口。AXI互联和接口作为ZYNQ PS和PL之间的桥梁,能够使两者协同工作,进而形成一个完整的、高度集成的系统。现在将在PL端调AXI GPIO IP 核,并通过AXI4-Lite接口实现 PS与PL中AXI GPIO模块的通信。
AXI GPIO IP核为 AXI 接口提供了一个通用的输入/输出接口。与 PS 端的 GPIO 不同, AXI GPIO 是一个软核( Soft IP),即 ZYNQ 芯片在出厂时并不存在这样的一个硬件电路, 而是由用户通过配置 PL 端的逻辑资源来实现的一个功能模块。 而 PS 端的 GPIO 是一个硬核( Hard IP) ,它是一个生产时在硅片中实现的功能电路。AXI GPIO 可以配置成单通道或者双通道, 每个通道的位宽可以单独设置。 另外通过打开或者关闭三态缓冲器, AXI GPIO 的端口还可以被动态地配置成输入或者输出接口。
贴个基础的AXIGPIO设为输出的代码吧,作为中断的代码也贴到中断篇
#define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID
// u32 uPinDirection = 0x1; //1表示输出,0表示输入
#define LED 0x01
#define GPIO_EXAMPLE_DEVICE_ID XPAR_GPIO_0_DEVICE_ID
#define LED_DELAY 10000000
#define LED_CHANNEL 1
XGpio Gpio; /* The Instance of the GPIO Driver */
int Fireout(void)
{
int Status;
int i;
volatile int Delay;
/* Initialize the GPIO driver */
Status = XGpio_Initialize(&Gpio, GPIO_EXAMPLE_DEVICE_ID);
if (Status != XST_SUCCESS) {
xil_printf("Gpio Initialization Failed\r\n");
return XST_FAILURE;
}
/* Set the direction for all signals as inputs except the LED output */
XGpio_SetDataDirection(&Gpio, LED_CHANNEL, ~LED);
/* Loop forever blinking the LED */
while (1) {
/* Set the LED to High */
XGpio_DiscreteWrite(&Gpio, LED_CHANNEL, LED);//Z7_ZHDL
XGpio_DiscreteWrite(&Gpio, LED_CHANNEL, LED<<1);//Z7_ZHYL,很明显也是由位宽决定的
// /* Wait a small amount of time so the LED is visible */
// for (Delay = 0; Delay < LED_DELAY; Delay++);
//
// /* Clear the LED bit */
// XGpio_DiscreteClear(&Gpio, LED_CHANNEL, LED);
//
// /* Wait a small amount of time so the LED is visible */
// for (Delay = 0; Delay < LED_DELAY; Delay++);
}
xil_printf("Successfully ran Gpio Example\r\n");
return XST_SUCCESS;
}
可以具体参考芯片手册,SDK软件下常用的API函数
int XGpio_Initialize(XGpio * InstancePtr,u16 DeviceId)
void XGpio_SetDataDirection(XGpio * InstancePtr,unsigned Channel,u32 DirectionMask)
void XGpio_DiscreteWrite(XGpio * InstancePtr,unsigned Channel,u32 Data)
u32 XGpio_DiscreteRead(XGpio * InstancePtr,unsigned Channel)
void XGpio_DiscreteClear(XGpio * InstancePtr,unsigned Channel,u32 Mask)
PS:学习zynq时间较短,认知有限,代码也比较简陋。如有错误欢迎批评指正
参考:Xilinx芯片手册
原子的开发手册
CSDN 百度等网络资源