软件版本:VIVADO2017.4
操作系统:WIN10 64bit
硬件平台:适用米联客 ZYNQ系列开发板
米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!!
4.1 概述
本课对ZYNQ芯片的GPIO进行介绍,通过设计PS端点亮LED的功能,讲解了PS端MIO使用方法。
4.2 GPIO简介
Zynq7000系列芯片有54个MIO(multiuse I/O),它们分配在 GPIO 的Bank0 和Bank1隶属于PS部分,这些IO与PS直接相连。不需要添加引脚约束,MIO信号对PL部分是透明的,不可见。所以对MIO的操作可以看作是纯PS的操作。
GPIO的控制和状态寄存器基地址为:0xE000_A000,SDK软件底层操作是对于内存地址空间的操作。
Bank0:MIO [31:0] GPIO PIN脚号:0~31
Bank1:MIO[32:53] GPIO PIN脚号:32~53
以上描述和我们原理图中和VIVADO ZYNQ IP中定义的有冲突,这个留给大家去讨论,下图中,是ZYNQ IP部分MIO电压分配,从这个软件上看,BANK0 IO 是0~15,BANK1 IO是16~53,这个是奇怪的事情,笔者现在还没搞懂。实际应用以软件设定和原理图为准。
Bank2:EMIO [31 : 0] GPIO PIN脚号:54~85
Bank3:EMIO [63:32] GPIO PIN脚号:86~ 117
4.2.1 GPIO的控制寄存器地址空间
SDK软件下的底层操作是对这些寄存器的操作,具体的相关参数请参考技术手册ug585-Zynq-7000-TRM.pdf
4.2.2 MIO内部构造分析
DATA_RO: 此寄存器使能软件观察PIN脚,当GPIO被配置成输出的时候,这个寄存器的值会反应输出的PIN脚情况。
DATA:此寄存器控制输出到GPIO的值,读这个寄存器的值可以读到最后一次写入该寄存器的值。
MASK_DATA_LSW:位操作寄存器,写入GPIO 低16bit 其他没有改变的位置保存原先的状态
MASK_DATA_MSW:位操作寄存器,写入GPIO 高16bit 其他没有改变的位置保存原先的状态
DIRM:此寄存器控制输出的开关,当DIRM[x]==0时候,禁止输出
OEN: 输出使能,当OEN[x]==0 的时候输出关闭,PIN脚处于三态
因此,如果要读IO状态就得读DATA_RO的值,如果是对某一位进行操作就是写MASK_DATA_LSW/MASK_DATA_MSW
具体的相关参数请参考技术手册ug585-Zynq-7000-TRM.pdf
4.2.3 EMIO的特性
与MIO大部分类似但是一下几点需要注意下
• EMIO在PL部分,输入与OEN寄存器无关,当DIRM设置为0的时候设置为输入可以读DATA_RO寄存器获取数据。
• 输出不能设置成三态,当DIRM设置为1的时候为输出,写入DATA寄存器或者MASK_DATA_LSW/MASK_DATA_MSW寄存器
• EMIOGPIOTN[x]=DIRM[x] & OEN[x],实现输出的控制。
具体的相关参数请参考技术手册ug585-Zynq-7000-TRM.pdf
4.3 电路分析及实验预期
开发板上有一个MIO是与开发板上的一个LD9相连的,这个MIO就是MIO7。实验通过操作该MIO来实现LD9的闪烁。
原理图
4.4 搭建BD工程
Step1:新建一个名为为Miz_sys的工程。
Step2:创建一个BD文件,并命名为system,添加并且配置好ZYNQ IP。读者需要根据自己的硬件类型配置好输入时钟频率、内存型号、串口,连接时钟等。新手不清楚这些内容个,请参考“CH01 HelloWold/DDR/网口测试及固化”这一节课。
Step3:ZYNQ IP MIO配置
MIO部分其他功能的配置可以参考参考“CH01 HelloWold/DDR/网口测试及固化”这一节课
Step2:建立一个空的工程
Step3: 在我们提供例程的文件夹中找到main.c文件,并进行复制。
Step4: 点击MIO_Test旁边的箭头使其展开,然后选中src,按下Ctrl+V快捷键,粘贴main.c文件
Step5:右击工程,选择Debug as ->Debug configurations。
Step6:选中system Debugger,双击创建一个系统调试,点击Apply,点击Debug。
Step7:单击窗口上的运行按钮,运行程序
4.7 测试结果
可看到核心板LD9闪烁
4.8 程序分析
接下来对程序进行分析。
分析1
语句:static XGpioPs psGpioInstancePtr;
含义:这是一个指针实例,指向添加的GPIO端口。
具体分析:
XGpiops:绿色标识的一个结构体。SDK中结构体都用绿色标识。
将鼠标停留在XGpiops上或右键Open Declaration,查看这个结构体所包含的内容。
分析2
语句:XGpioPs_Config* GpioConfigPtr
含义:这是一个指针实例。
具体分析:
XGpioPs_Config:绿色标识的一个结构体。
将鼠标停留在XGpiops上或右键Open Declaration,查看这个结构体所包含的内容。
此结构体存放的是GPIO的设备地址和基地址。
分析3
语句:int iPinNumber= 7;
含义:参数
具体分析:iPinNumber这个参数,是告知程序,操作的MIO是哪一个,因为我们要操作的是MIO7,所以这里所以这里的iPinNumber等于7。(在第三章EMIO中也使用了这个参数,具体请参看下一节内容,这里仅作铺垫)
分析4
语句:GpioConfigPtr = XGpioPs_LookupConfig(XPAR_PS7_GPIO_0_DEVICE_ID);
if(GpioConfigPtr == NULL)
return XST_FAILURE;
含义:查找GPIO配置程序。此处用到xparameters.h中XPAR_PS7_GPIO_0_DEVICE_ID。这段话的整体意思为查找GPIO的配置,判断其是否为空,若为空则返回查找失败。
具体分析:
分析5
语句:
xStatus=XGpioPs_CfgInitialize(&psGpioInstancePtr,GpioConfigPtr,GpioConfigPtr->BaseAddr);
if(XST_SUCCESS != xStatus)
print(" PS GPIO INIT FAILED \n\r");
含义: 完成gpio配置的初始化工作,如果初始化不成功,将通过串口打印出初始化失败的信息。
分析6
语句:XGpioPs_SetDirectionPin(&psGpioInstancePtr, iPinNumber,uPinDirection)
含义:指定pin脚的方向设置。
具体分析:
程序完成指定pin脚的方向设置。程序首先读取bank号,对应的子程序:
/* Get the Bank number and Pin number within the bank. */
XGpioPs_GetBankPin((u8)Pin, &Bank, &PinNumber);
将鼠标停留在XGpioPs_GetBankPin函数上,按F3查看下其功能。
图中画红框部分为程序查找bank号对应代码。程序首先判断ZYNQ的类型,本课第一节GPIO简介介绍7010和7020有四个bank,因此当程序执行后,程序首先执行else部分的程序。此时看else部分程序。程序给出了四个bank的bank号的最大值,初始化bank号为0,while语句限制了bank的最大数量为4。然后用pin的序号从bank0到bank3逐个比对,若是此时pin的序号小于或等于当前bank的最大值,则可以判断出pin是属于这个bank的,跳出while语句,否则bank号进行自加操作直到得出符合的bank号。接下来if语句,判断bank号是否为bank0,若是则将PinNumber直接赋值,否则经过计算一段公式得出PinNumber。
接下来回到XGpioPs_SetDirectionPin函数分析其他的子程序。获取了bank号后,开始读取寄存器,程序如下:
DirModeReg = XGpioPs_ReadReg(InstancePtr->GpioConfig.BaseAddr,
((u32)(Bank) * XGPIOPS_REG_MASK_OFFSET) +
XGPIOPS_DIRM_OFFSET);
这里重点观察第二个参数,这是一个任务寄存器偏移+DIRM_OFFSET的参数。此时打开xilinx的编程手册ug585-zynq-7000-TRM(接下来的内容中我们将将其简称为ug585),来具体看看这个参数含义。
复制DIRM,在ug585中查找到这么一段话:
此时得知这是一个方向寄存器,当它等于0的时候输出被禁止,只有输入进行,即此时为输入功能。等于1时做输出使用。在GPIO的通道示意图中也能发现有这个部分构成。
回到XGpioPs_SetDirectionPin的分析,再得到了bank号与要写哪个寄存器的地址后,接下来的if else语句就是对这对pinbumer这一位单独做一些操作,最后把方向寄存器的值写入到读出的那个寄存器当中。
分析7
语句:XGpioPs_SetOutputEnablePin(&psGpioInstancePtr, iPinNumber,1);
含义:配置输出
分析8
语句:XGpioPs_WritePin(&psGpioInstancePtr, iPinNumber, 1)
含义:写数据到pin脚
具体分析:
XGpioPs_WritePin的参数分别为gpio的基地址、要操作的MIO号和写入的数据。定义如下:
观察图中方框圈起来部分,此处观测到有两个陌生的偏移,此时可在ug585中查看其具体含义。
此时可以得知,语句是要写入数据的高16位偏移量和低16位偏移量。程序是通过判断PinNumber的值来决定寄存器偏移量是用高16位偏移量还是低16位偏移量。
此时再看XGpioPs_WritePin函数的接下来的这段程序:
这段程序完成向指定MIO写入某个值的操作。分析这段程序,如果要向MIO7写入1,程序一开始已经把要写入的值赋值给了DataVar,此段程序又将DataVar与0x01与操作,操作后DataVar的值还是为1。
接下来的value就是要写入寄存器的值。~ ((u32)1 << (u32)PinNumber):表示把PinNumber加上16(也就是把pinNumber移到高16位)赋值为1,然后再取反,执行完后这一段的值为~(80000)h,也就是(FFF7FFFF)h。
((DataVar << PinNumber) | 0xFFFF0000U):已经得到DataVar的值为1,因此这里的意思为把pinNumber位赋值为1,再与FFFF0000或操作,执行完这一段的值为(80)h | (FFFF0000)h,也就是(FFFF0080)h,整句执行完之后就是(FFF7FFF)h & (FFFF0080)h=(FFF70080)h。也就是此时Value的值为FFF70080。
XGpioPs_WriteReg这个函数功能是往寄存器中写入数据。
从图上可知,第一个参数为设备的基地址, 第二个参数为偏移量,此处为0,第三个参数为要写入寄存器的数据。另外程序还可直接使用寄存器函数对MIO进行操作,其用法参照之前的分析,寄存器函数操作如下所示:
XGpioPs_WriteReg(0xE000A000,0x00000000, 0xFF7FFFFF&0xFFFF0080); usleep(500000); //延时 XGpioPs_WriteReg(0xE000A000,0x00000000, 0xFF7FFFFF&0xFFFF0000); usleep(500000); //延时 |
按照之前讲解的方法,开发人员可自行对库函数进行学习分析。