S02_CH02_MIO实验
2.1 GPIO简介
Zynq7000系列芯片有54个MIO(multiuse I/O),它们分配在 GPIO 的Bank0 和Bank1隶属于PS部分,这些IO与PS直接相连。不需要添加引脚约束,MIO信号对PL部分是透明的,不可见。所以对MIO的操作可以看作是纯PS的操作。
GPIO的控制和状态寄存器基地址为:0xE000_A000,我们SDK下软件操作底层都是对于内存地址空间的操作。
Bank0:MI0[31:0]
Bank1:MI0[52:53]
Bank2:EMI0[31:0]
Bank3:EMI0[63:32]
2.1.1 GPIO的控制寄存器地址空间
我们在SDK下操作的时候底层都是对这些寄存器的操作,具体的相关参数请参考技术手册ug585-Zynq-7000-TRM.pdf
2.1.2MIO内部构造分析
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
2.1.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
2.2 电路分析及实验预期
在米联系列的开发板上有一个MIO是与开发板上的一个LD9相连的,这个MIO就是MIO7。实验通过操作该MIO来实现LD9的闪烁。
2.3 ZYNQ核的添加及配置
Step1:新建一个名为为Miz_sys的工程,正确配置芯片型号,还未掌握的请参照上一章进行设置。
Step2:单击Create Block Design,创建一个BD文件,并命名为System。
Step3:加入一个ZYNQ CPU IP,根据自己的产品型号,正确配置时钟频率与内存类型,尚未掌握的请重新温习上一章内容。
Step4:由于本章需要用到MIO接口,因此需要确保MIO选项被勾选(默认已勾选)。
Step5:单击OK后退出,系统整体电路如下。
Step6:右击 system.bd, 单击Generate Output Products。
Step7:右击system.bd 选择 Create HDL Wrapper产生顶层的HDL文件。
Step8:File->Export->Export Hardware。
Step9:勾选Include bitstream 直接单击OK。
Step10:File->Launch SDK加载到SDK,单击OK。
2.4新建LED_Flash SDK工程
Step1:在SDK界面中,新建一个名为MIO_Test的工程
Step2:建立一个空的工程
Step3:新建一个C的源文件
Step4:取名为main.c
接下来就向main.c中添加内容了,之前讲过,其中MIO7接到了LD9这个灯上,接下来我们利用程序让他闪起来。
#include "xgpiops.h" #include "sleep.h" int main() { static XGpioPs psGpioInstancePtr; XGpioPs_Config* GpioConfigPtr; int iPinNumber= 7; //LD9连接的是MIO7 u32 uPinDirection = 0x1; //1表示输出,0表示输入 int xStatus; //--MIO的初始化 GpioConfigPtr = XGpioPs_LookupConfig(XPAR_PS7_GPIO_0_DEVICE_ID); if(GpioConfigPtr == NULL) return XST_FAILURE; xStatus = XGpioPs_CfgInitialize(&psGpioInstancePtr,GpioConfigPtr, GpioConfigPtr->BaseAddr); if(XST_SUCCESS != xStatus) print(" PS GPIO INIT FAILED "); //--MIO的输入输出操作 XGpioPs_SetDirectionPin(&psGpioInstancePtr, iPinNumber,uPinDirection);//配置MIO输出方向 XGpioPs_SetOutputEnablePin(&psGpioInstancePtr, iPinNumber,1);//配置MIO的第7位输出 while(1) { XGpioPs_WritePin(&psGpioInstancePtr, iPinNumber, 1);//点亮MIO的第7位输出1 usleep(500000); //延时 XGpioPs_WritePin(&psGpioInstancePtr, iPinNumber, 0);//熄灭MIO的第7位输出0 usleep(500000); //延时 } /**************************************************************** while(1) { XGpioPs_WriteReg(0xE000A000,0x00000000, 0xFF7FFFFF&0xFFFF0080); usleep(500000); //延时 XGpioPs_WriteReg(0xE000A000,0x00000000, 0xFF7FFFFF&0xFFFF0000); usleep(500000); //延时 } ******************************************************************/ return 0; } |
2.5 程序分析
接下来我们对整个程序做一个分析。
这是一个指针实例,指向的我们添加进来的GPIO端口。
绿色标识的一个结构体(SDK中结构体都用绿色标识)XGpiops,我们将鼠标停留在这个结构体上面,就可以看到它里面所包含的内容。
从这个图上可以看到,这个结构体上包含了GPIO的一些参数,分别是:设备的配置、设备是否初始化并准备好、所有状态的处理程序、块处理程序的回调、设备数据、GPIO的最大pin数量和GPIO的最大的bank数量。
也是一个指针实例,按照刚才介绍的方法我们来查看下它的释义。
从中可以看出,此结构体存放的是GPIO的设备地址和基地址。
iPinNumber这个参数,是告知程序,操作的MIO是哪一个,因为我们要操作的是MIO7,所以这里所以这里的iPinNumber等于7,在后一章的EMIO中也有这个参数,具体怎么算请参看下一节内容,这里就做个铺垫吧。
这段程序是一段查找GPIO配置程序。XGpiops_Lookupconfig()这个函数是一个xilinx官方提供的GPIO的查找配置的函数,程序的参数为要查找的GPIO的基地址。基地址可从xparameters.h中查看,单击BSP支持包(此处为MIO_Test_bsp)的小三角形,选择Ps7_Cortexa9_0文件夹下的include文件夹,在其中找到xparameters.h,双击打开它。若未找到,则在主界面下的System.mss界面点击,重新生成BSP支持包,此时只要耐心等待即可。如下图所示。
此处我们用到的就是XPAR_PS7_GPIO_0_DEVICE_ID。这段话的整体意思就是查找GPIO的配置,然后判断其是否为空,若为空则返回查找失败。
上图这段程序也是跟刚才大同小异,完成的是gpio配置的初始化工作,如果初始化不成功的话,将通过串口打印出一串初始化失败的通知信息,在此就不再去对其详细的分析。
本章中具体来看看
这个函数,因为此函数中涉及到了一些ZYNQ中GPIO的硬件结构,将鼠标停留在这个函数上,按F3查看其函数定义。
从上图方框圈出的地方我们可以看到此程序给出的功能说明,它完成的是指定pin脚的方向设置。这个程序中,首先它有一个读取bank号的子程序:我们将鼠标停留在这个函数之上,按F3查看下它是具体怎样来查找bank号的。
上图中方框圈出的地方地方就是程序查找bank号的。一开始程序先判断了ZYNQ的类型,在本章第一节GPIO简介中我们知道,7010和7020其实是有四个bank的,因此当程序执行后,其实程序是执行else部分的程序的。此时再来看看else部分的程序。程序首先给出了四个bank的bank号的最大值,然后初始化了bank号为0,接下来的while语句限制了bank的最大数量为4。接下来用pin的序号从bank0到bank4逐个比对,若是此时pin的序号小于或等于当前bank的最大值,则可以判断出pin是属于这个bank的,跳出while语句,否则bank号进行自加操作直到得出bank号。接下来又是一个if语句,判断bank号是否为bank0,若是则将pinnumber直接赋值,否则经过计算一段公式得出pinnumber。
接下来回到XGpioPs_SetDirectionPin函数分析其他的子程序,在获取了bank号之后,是一个读取寄存器的程序这里重点观察第二个参数,这是一个任务寄存器偏移+DIRM_OFFSET的参数,此时我们可打开xilinx的编程手册ug585-zynq-7000-TRM(接下来的内容中我们将将其简称为ug585),来具体看看这个是个什么东西。
复制DIRM,在ug585中查找到这么一段话:
此时得知这其实就是一个方向寄存器,当它等于0的时候输出被禁止,只有输入被运行,也就是此时是作为输入用的,等于1时做输出用。这在GPIO的通道示意图中也能发现有这个部分构成。
回到XGpioPs_SetDirectionPin的分析,再得到了bank号与要写哪个寄存器的地址后,接下来的if else语句就是对这对pinbumer这一位单独做一些操作,最后把方向寄存器的值写入到读出的那个寄存器当中。
回到main.c的分析当中,接下来的XGpioPs_SetOutputEnablePin函数,其原理与设置方向函数的原理是一样的,我们就不再深层次对其进行分析,它完成的功能在程序中也有注释。
最后,我们看到对单个位操作的函数XGpioPs_WritePin,它与之前的程序结构也是大体一致的,它的三个参数分别为gpio的基地址、要操作的MIO号和写入的数据。按下F3查看一下它的定义。
上图中,我们直接看到方框圈起来的部分,此处我们观测到有两个陌生的偏移,此时我们可在ug585中查看一下它们具体是什么意思。
此时可以得知,这两个分别是要写入数据的高16位偏移量和低16位偏移量。此时即可得知这段程序是通过判断pinNumber的值来决定寄存器偏移量是用高16位偏移量还是低16位偏移量。
此时再看XGpioPs_SetOutputEnablePin函数的接下来的这段程序:
这段程序完成的就是向指定MIO写入某个值的操作。我们分析一下这段程序,比如我们要向MIO7写入1,程序一开始已经把要写入的值赋值给了DataVar,在此段程序程序一开始又将DataVar与0x01与操作,此操作后DataVar的值还是为1。
接下来的value就是要写入寄存器的值,我们来看看它是怎么操作的。这里的意思为把PinNumber加上16(也就是把pinNumber移到高16位)赋值为1,然后再取反,执行完后这一段的值为~(80000)h,也就是(FFF7FFFF)h。
再看后半段,之前已经得到DataVar的值为1,因此这里的意思为把pinNumber位赋值为1,再与FFFF0000或操作,执行完这一段的值为(80)h | (FFFF0000)h,也就是(FFFF0080)h,整句执行完之后就是(FFF7FFF)h & (FFFF0080)h=(FFF70080)h。也就是此时Value的值为FFF70080。
XGpioPs_WriteReg这个函数就是往寄存器中写入数据,按下F3查看函数定义。如下图所示。
从图上可知,第一个参数为设备的基地址, 第二个参数为偏移量,此处为0,第三个参数为要写入寄存器的数据。
另外程序还可直接使用寄存器函数对MIO进行操作,其用法参照我们之前的分析,寄存器函数操作如下所示:
XGpioPs_WriteReg(0xE000A000,0x00000000, 0xFF7FFFFF&0xFFFF0080); usleep(500000); //延时 XGpioPs_WriteReg(0xE000A000,0x00000000, 0xFF7FFFFF&0xFFFF0000); usleep(500000); //延时 |
按照之前我们讲过的方法,大家可自行对库函数进行分析。
2.6 本章小结
本章讲解了ZYNQ芯片的GPIO的一些知识,然后通过使用SDK进行编程点亮一个LED。同时分析了程序的代码。测试结果说明了,库函数使用方便,但是效率地下,寄存器效率高,但是使用不方便。因此在设计系统的时候如何优化是需要综合考虑的。