之前的Hello World和MIO使用都算是纯PS部分,也就是把Zynq单纯地当作ARM使用。很多人都是因为FPGA+ARM架构才使用的Zynq,有两个关键问题容易引起初学者的兴趣:(1).如何用PS控制PL;(2).如何完成PS与PL之间的数据通信。本系列会介绍解决这两个问题的各种方法。
EMIO就是PS控制PL资源的简单例子。EMIO就是可扩展的MIO,当与PS直接相连的MIO不够用时,可以使用EMIO做“扩展”。使用体会上,感觉就是ARM直接控制了PL部分的管脚。GPIO的bank2和bank3就是通过EMIO接口与PL相连的,本文将先通过PS控制PL部分流水灯的实例感受下EMIO的使用,然后再介绍EMIO相关的基本概念。
Zynq设计与代码详解
建立一个工程,配置好Zynq的时钟和DDR后,需要在MIO Configuration->I/O Peripherals->GPIO中选中EMIO GPIO。控制4个LED的流水,则EMIO GPIO(Width)选择4,相当于扩展了4个GPIO。
IP Integrator中选中GPIO_0,右键->Make External创建端口,如下图。
如果想修改添加端口名称,单击选中,在External Interface Properties窗口中修改(白板的中是不能修改的):
由于我们使用了PL中的管脚,所以必须要进行管脚约束。打开任一设计阶段的I/O Planning视图,在I/O Ports窗口中可以看到4个EMIO端口(都是inout双向端口)。根据硬件情况设置管脚号和电平标准,保存为XDC文件(当然也可以直接编辑XDC文件)。
生成bit流后导入到SDK中。SDK中新建工程,添加源文件,代码清单如下:
#include "xgpiops.h"
#include "sleep.h"
XGpioPs GpioPs_Init()
{
XGpioPs_Config* GpioConfigPtr;
XGpioPs psGpioInstancePtr;
GpioConfigPtr = XGpioPs_LookupConfig(XPAR_PS7_GPIO_0_DEVICE_ID);
XGpioPs_CfgInitialize(&psGpioInstancePtr, GpioConfigPtr, GpioConfigPtr->BaseAddr);
return psGpioInstancePtr;
}
int main()
{
static XGpioPs psGpioInstancePtr;
psGpioInstancePtr = GpioPs_Init(psGpioInstancePtr); //GPIO初始化
//EMIO配置为输出
XGpioPs_SetDirectionPin(&psGpioInstancePtr, 54,1);
XGpioPs_SetDirectionPin(&psGpioInstancePtr, 55,1);
XGpioPs_SetDirectionPin(&psGpioInstancePtr, 56,1);
XGpioPs_SetDirectionPin(&psGpioInstancePtr, 57,1);
//使能EMIO输出
XGpioPs_SetOutputEnablePin(&psGpioInstancePtr, 54,1);
XGpioPs_SetOutputEnablePin(&psGpioInstancePtr, 55,1);
XGpioPs_SetOutputEnablePin(&psGpioInstancePtr, 56,1);
XGpioPs_SetOutputEnablePin(&psGpioInstancePtr, 57,1);
while(1)
{
XGpioPs_WritePin(&psGpioInstancePtr, 54, 1);//EMIO的第0位输出1
usleep(200000); //延时
XGpioPs_WritePin(&psGpioInstancePtr, 54, 0);//EMIO的第0位输出0
usleep(200000); //延时
XGpioPs_WritePin(&psGpioInstancePtr, 55, 1);//EMIO的第1位输出1
usleep(200000); //延时
XGpioPs_WritePin(&psGpioInstancePtr, 55, 0);//EMIO的第1位输出0
usleep(200000); //延时
XGpioPs_WritePin(&psGpioInstancePtr, 56, 1);//EMIO的第2位输出1
usleep(200000); //延时
XGpioPs_WritePin(&psGpioInstancePtr, 56, 0);//EMIO的第2位输出0
usleep(200000); //延时
XGpioPs_WritePin(&psGpioInstancePtr, 57, 1);//EMIO的第3位输出1
usleep(200000); //延时
XGpioPs_WritePin(&psGpioInstancePtr, 57, 0);//EMIO的第3位输出0
usleep(200000); //延时
}
return 0;
}
用到的API函数与上一篇MIO的完全相同,不再赘述。不过注意54个MIO占用的管脚号为053;64个EMIO占用的管脚号为54117。这次用的延时函数为usleep,该函数输入参数为long型,延时以微妙为单位。
EMIO介绍
关于GPIO的相关概念已经在上一篇中讲述。EMIO只是GPIO信号的bank2、bank3与PL部分连接的接口,使用的寄存器接口与MIO的完全相同。这里对有差别的相关操作进行补充说明。
- 输入是来自PL的连线,与输出值或OEN寄存器无关。若DIRM设置为0,可以从DATA_RO寄存器中读取输入值。
- 输出不支持三态,因此不受OEN寄存器的控制。输出时将DIRM设置为1,使用DATA、MASK_DATA_LSW、MASK_DATA_MSK寄存器配置输出值。
- 输出使能线由PS输出到PL,这些使能线受到DIRM和OEN寄存器的控制,如EMIOGPIOTN[x]=DIRM[X]&OEN[X]。
EMIO的I/O不能与MIO的I/O连接在一起的:EMIO的输入不能与MIO的输出接在一起;MIO的输入也不能与EMIO的输出结在一起。这是因为它们对应的GPIO属于不同的bank,每个bank都是独立的。
总结
本系列4-5篇介绍了Zynq中GPIO的使用,包括MIO和EMIO。UG585中还详细介绍了GPIO的各种操作流程和示例,但在SDK中实际编程时,已经有了封装好的库函数,一般学习这些函数的用法即可。此外,每个GPIO都可以使用中断功能,这部分内容在后面用到时再做介绍。