当前位置:首页 > > ZYNQ


常用外设设计


使用UART与ZYBO进行通信

ZYNQ学习过程中一个重要环节是进行调试,当然在SDK中进行调试时,设置断点进行单步调试非常高效。但是ZYNQ中毕竟涉及到FPGA的硬件部分,于是如果可以通过UART与ZYNQ器件进行双向的通信会使得调试非常方便。


Step1: 确认ZYBO板上硬件连接。


在ZYBO的用户手册中有以下描述,如图 21所示。

图 21


其ZYBO开发板的原理图也可知MIO Bank的电压为1.8V。


于是可以得到以下几点:

1. 硬件上连接串口的IO为MIO48和MIO49。

2. IO的电压为1.8V。

3. 默认使用的参数为:115200波特率以及其他对应参数,见图 21。


Step2: 在Vivado的Block Design中配置ZYNQ7 Processing System时,需要配置UART外设,如图 22所示。

图 22


Step3: 按照“Vivado中进行ZYNQ硬件部分设计”中介绍的,直到在SDK中编写C代码步骤。需要使用UART进行通行,分为通过UART向外发送数据和通过UART接收数据。


Step4: PC上安装串口调试助手,硬件上连接串口线到PC。向ZYNQ中下载硬件配置后,在Windows Device Manager查看UART所使用的端口,如图 23所示。

图 23


并将串口调试助手设置成相应的配置,如图 24所示。

图 24


Step5: 至此就可以在C代码中发送或者读取数据了,下面给出一段示例代码。

图 25


程序首先运行,输出” Please enter any number from 0~9”。此后会停在等待输入处,直到通过串口助手键入了数字之后,才会跳到下一行代码,并将键入的数字输出在串口调试助手中。

图 26


至此可以通过串口输出各种信息(因为可以输出字符串),同时也可以通过串口输入进行菜单选择,因为虽然不能接收字符串,但可以键入10个数字以及26个英文字母,这也应该足够了。


Step6: 除了使用外部的串口调试助手外,SDK内部也提供了Terminal工具。通过如下图所示的方式显示Terminal窗口且进行配置,同样可以当作串口调试助手使用,如图 28所示。

图 27


图 28


将用户逻辑设计封装成IP

参考工程见“ZYBO_demo_packageIP.xpr”。


ZYNQ中PS与PL的联系主要有两个,分别是GP端口和HP端口。其中HP端口可以理解为PS和PL可以访问同一片存储空间,于是可以以DDR空间为中介,进行大量的数据通信。而平时在纯FPGA设计中的那些逻辑模块与PS的通信方式就是依靠GP端口完成的。


纯RTL逻辑模块的控制或者输出都是通过模块的输入输出端口实现的。在ZYNQ的架构下,就是将这些输入输出端口改为寄存器,原来的输入端口改为只写的控制寄存器;原来的输出端口改为只读的状态寄存器。于是每个逻辑模块都有一个标准的AXI-Lite Slave接口,用于与PS相连接,使得PS可以控制读写逻辑模块内的寄存器。于是每一个逻辑模块就相当于PS的一个外设,与PS自带的外设,如UART、I2C控制器控制器来是一样的。这就是ZYNQ的硬件可扩展性,通俗的说就是当ARM需要各种要求的外设时,都可以通过在PL端进行逻辑设计实现。


下面就介绍如何一步一步的完成一个PL端的逻辑设计,并将其封装成为IP,并在ZYNQ的Block Design中加入到硬件设计中,并在SDK中通过代码来控制该外设。


本例中所涉及的逻辑模块功能为:IP的外部接口为4盏LED,内部寄存器有3个,分别是工作模式寄存器(MODE),使能寄存器(ENABLE)和状态寄存器(STATUS),如图 29所示。

图 29


Step1: 在Vivado GUI中,选择ToolCreate and Package New IP…。一般情况下,选择“Create a new AXI4 Peripheral”,这种IP就是之前提到的,由PS读写寄存器进而控制的IP,在后面的菜单中选择该IP的Interface接口以及内部需要多少寄存器,如图 30所示。

图 30


一般来说,IP首先需要一个AXI-Lite的Slave接口,用于与PS连接。除此以外还可以增加其他AXI接口,例如增加一个AXI4的Master接口,用于逻辑设计中访问PS端的存储器;又比如可以增加一个AXI-Lite的Master,用于读写其他逻辑模块的寄存器等等。在这里选择好接口后,工具在生成IP的时候会自动生成相应的AXI接口代码,用户可以直接使用或者稍作修改即可。


另外就是选择寄存器的个数,因为稍后生成的代码中会自动完成指定数量寄存器的读写控制代码,于是如果设置少了,后面需要手工添加代码,会比较麻烦,不如配置时将寄存器个数设置的多一些。


Step2: 完成配置后工具自动生成代码,在生成的代码中进行修改。

图 31


在自动生成的代码中,结构如下:

图 32


需要注意的是自动生成的代码只是简单完成了指定数量寄存器的读写时序,至于每个寄存器的逻辑功能和含义是没有的,需要用户将这些寄存器引到S00_AXI模块的上层,与用户设计的逻辑进行通信。同时自动生成的代码中所有的寄存器都是可读可写的,如果需要设置只读或者只写的寄存器,需要在S00_AXI模块中自行修改代码。


Step3: 为逻辑设计增加外部接口以及全局参数。


该例程中需要设置4个外部输出管脚,控制外部LED。并且设置一个全局参数为4盏LED的初始状态。首先在代码中指定位置进行修改,如图 33所示。

图 33


保存修改后,会在GUI界面中看到“Customization Parameter”、“Ports and Interfaces”以及“Customization GUI”的标识都变了,代表检测到了代码中的变化。

图 34


点击GUI中如下所示的指令后,工具自动完成更新。

图 35


Step4: 在GUI如图 34所示的界面中修改参数设置,对IP进行配置。


Step5: 完成IP的封装,如图 36所示,在Review and Package中点击Re-Package IP。

图 36


此时会关闭IP封装界面,回到ZYNQ Block Design界面。在IP Catalog中会出现刚才封装完成的IP。

图 37


此后的操作就和“Vivado中进行ZYNQ硬件部分设计”中介绍的一样,将该IP当成与其他IP一样的外设,进行Block Design设计,同样需要对于外接接口添加约束文件,之后生成Bit文件。


Step6: 在Block Design中需要将封装的逻辑模块IP需要引到外部的IO进行设置。


右击需要引出的端口,点击“Make External”。之后重新生成HDL Wrapper即可。

图 38


Step7: 在Block Design中的Address Editor中给IP分配总线地址。

图 39


Step8: 封装好的IP进行底层修改。


如果需要对已经设计好的IP进行底层修改,就在IP Catalog中显示的该IP处右击,选择“Customize IP”。需要注意的是,工具会默认弹出一个路径,作为修改IP的工作路径,但是该路径是一个临时路径,与之前存放IP的路径不同。

图 40


IP底层修改完成之后,重新Re-Package IP后,回到Block Design中工具自动检测到IP有更新,只要通过工具的提示进行IP的更新即可。

图 41


Step9: 封装好的IP的BSP以及SDK程序的编写。


首先封装的IP有自己在AXI总线上的地址,于是IP内部的所有寄存器的地址就是IP的Base地址加上各个寄存器的偏移量。于是在SDK代码中就可以通过唯一的地址访问到这些寄存器。由于内部的寄存器都是用户自己设计的,所以不需要BSP封装太多的函数,BSP也无法知道用户设计的逻辑,从而也无法自动封装API。用户直接通过底层调用Xil_Out32()和Xil_In32()完成对于封装逻辑模块IP的控制。如果需要,用户可以自行封装更上层的API函数。

使用Zynq processor仿真Customized IP


由于ZYNQ中除了常用的FPGA逻辑部分外还有ARM内核部分,所以在进行类似于FPGA设计仿真时会遇到问题,就是如果仿真ARM内核部分。Xilinx提供的一个方案是Zynq BFM(Bus Functional Model),其介绍如下。

图 42


但是该Model是需要额外购买的,该章节介绍一种无需购买Zynq BFM就可以仿真Customized IP的方法。基本思路是对于PL端的Customized IP而言,控制端是ZYNQ中的ARM内核还是普通FPGA中的MicroBlaze都是一样的,所以可以将Customized IP例化到以MicroBlaze为处理器的Block Design中,由于MicroBlaze的仿真无需额外的License,所以可以将SDK的程序导入到MicroBlaze的内核,进而仿真Customized IP。


Step1: 按照“将用户逻辑设计封装成IP”中介绍的,完成Customized IP的设计和封装;在Block Design中加入MicroBlaze和Customized IP,完成Block Design设计;完成硬件平台的综合、实现、生成Bit文件,并导入SDK;在SDK中完成软件设计。综上就是完成“将用户逻辑设计封装成IP”中的Step9,区别只是用MicroBlaze替代了ZYNQ Processing System。

图 43


Step2:对SDK中设计的软件程序完成编译,工具自动生成elf文件。默认的存放地址为SDK project下Src文件夹中。例如:C:\***\A7_microblaze\A7_microblaze.sdk\microblaze_customized_ip\Debug

图 44


Step3:回到Vivado开发平台,将elf文件导入至Simulation Source中。

图 45


Step4:将导入的elf文件链接到对应的处理器,使其作为该处理器的启动程序。注意,elf文件只允许associate至Microblaze,工具不支持链接到ZYNQ。

图 46


Step5:正常启动仿真。


可以看到MicroBlaze核发出的AXI通信协议,如图 47所示。

图 47


于是对应的IP的输出可以看到如“将用户逻辑设计封装成IP”中模式2设计的一样,输出的4路LED不断反转。

图 48

ZYNQ对Memory的操作
参考工程见“ZYBO_Memory_GPIO_Interrupt_demo.xpr”。ZYNQ有专用的DDR Controller接口,如果外部硬件连接了DDR器件,于是在ZYNQ Processing System中正确配置了相应的信号和参数后,DDR就可以成为ZYNQ的内存,在SDK中可以直接使用memcpy、memset以及类似的函数对于Memory空间进行操作。Step1:查看ZYBO的原理图,找到相应的配置。ZYBO原理图中与DDR相关的部分如图 49所示。图 49于是得到两个信息,第一个所使用的芯片是MT41J128M16JT-125,第二个是两片DDR3颗粒是通过位拼接完成的,也就是数据位宽为32bit。Step2:在Block Design中对DDR部分的参数进行配置。图 50Step3:完成Block Design设计,产生Bitstream,导入SDK。图 51Step4:在SDK中编写Memory测试代码。
#include #include #include "platform.h"#include "xil_printf.h"#include "xil_types.h"#include "xil_io.h"int main(){ u32 test_src[100]; int i; int readback;  init_platform(); u32 *result = (u32*) malloc(sizeof(u32) * 100);  if (result) { memset(result, 0, sizeof(u32) * 100); } else { return 0; }  for(i=0;i<99;i++) { test_src[i]=i; }  memcpy(result,test_src,100 * sizeof(u32));  for(i=0;i<100;i++) { readback = Xil_In32(result+i);

其中特别需要学习的就是malloc与memcpy的使用方法。

ZYNQ中MIO/EMIO GPIO的使用

参考工程见“ZYBO_Memory_GPIO_Interrupt_demo.xpr”。

MIO是PS端的外部引脚,共有54个;EMIO是PL端的外部引脚,共有64个。ZYNQ支持通过配置将PS的控制器信号通过EMIO输出,例如PS自带的UART Controller,如果正常选择引脚只能选择MIO引脚输出,但是通过设置可以选择连接到EMIO引脚。同时EMIO引脚也可以作为PS端的扩展引脚,即经过扩展PS一共可以控制118个引脚。

该例程演示将4个EMIO设置为PS的扩展引脚,这4个EMIO连接着LED。于是,与“将用户逻辑设计封装成IP”中的实验相比,同样是控制外部4个LED,就不需要另外设计一个逻辑模块,并封装成IP作为PS的外设了,可以直接通过SDK的程序进行控制。

注意:

1. 用于扩展GPIO的EMIO和用于扩展外设的EMIO是完全独立的,GPIO的EMIO共有64个,由2个bank组成,如图 52所示。

图 52

2. EMIO的内部排序按照EMIO54、EMIO55... ... EMIO117,以此类推。有了EMIO的编号之后就与内部控制EMIO的寄存器一一对应;而EMIO在外部与外部引脚的对应关系又是可以通过管脚约束进行更改的。于是可以得出:不能通过EMIO的外部引脚的关系确定其内部寄存器的地址。工具对于EMIO GPIO的连接关系是按照从EMIO54开始依次向上排列。

Step1:在Block Design中加入ZYNQ7 Processing System,在ZYNQ7 Processing System配置中添加EMIO GPIO,如图 53所示。通过设置EMIO GPIO Width来选择扩展EMIO GPIO的个数,此时就完成了与内部寄存器之间的对应关系,规则就是从EMIO54开始向上排列。

图 53

Step2:将ZYNQ的EMIO连接到外部引脚。右击生成的GPIO信号,点击Make External。图 54Step3:约束EMIO与外部引脚Pad的对应关系以及EMIO的电平标准。方法有两种:
  • 第一种是通过XDC约束文件进行约束,需要先将Block Design生成HDL Wrapper,这样才能知道其引脚名称。

图 55
  • 第二种方法就是Open Elaborated Design,在GUI中设置电平和引脚。

图 56

Step4:完成Block Design的综合、实现、生成Bitstream并导入SDK。

Step5:SDK中完成代码的编写,EMIO的代码编写需要包含的库文件是"xgpiops.h"。

#include "xgpiops.h"static XGpioPs emio;#define EMIO_54   54#define EMIO_55   55#define EMIO_56   56#define EMIO_57   57 int main(){ //定义GPIOPS型指针,用于初始化时绑定硬件 XGpioPs_Config *ConfigPtrPS; init_platform(); //初始化GPIOPS,将ConfigPtrPS与硬件绑定 ConfigPtrPS = XGpioPs_LookupConfig(0); XGpioPs_CfgInitialize(&emio, ConfigPtrPS, ConfigPtrPS- > BaseAddr); //设置EMIO的方向,并使能EMIO XGpioPs_SetDirectionPin(&emio, EMIO_54, 1); XGpioPs_SetOutputEnablePin(&emio, EMIO_54, 1); XGpioPs_SetDirectionPin(&emio, EMIO_55, 1); XGpioPs_SetOutputEnablePin(&emio, EMIO_55, 1); XGpioPs_SetDirectionPin(&emio, EMIO_56, 1); XGpioPs_SetOutputEnablePin(&emio, EMIO_56, 1); XGpioPs_SetDirectionPin(&emio, EMIO_57, 1); XGpioPs_SetOutputEnablePin(&emio, EMIO_57, 1);  while(1) { // 向EMIO写入数据,即驱动EMIO引脚 XGpioPs_WritePin(&emio, EMIO_54, 0x0); XGpioPs_WritePin(&emio, EMIO_55, 0x0); XGpioPs_WritePin(&emio, EMIO_56, 0x0); XGpioPs_WritePin(&emio, EMIO_57, 0x0); usleep(200000); XGpioPs_WritePin(&emio, EMIO_54, 0x1); XGpioPs_WritePin(&emio, EMIO_55, 0x1); XGpioPs_WritePin(&emio, EMIO_56, 0x1); XGpioPs_WritePin(&emio, EMIO_57, 0x1); usleep(200000); }  cleanup_platform(); return 0;}


Step6:如果需要将EMIO作为输入端口,只需要将IO的方向设置为input。对于IO,作为输出的时候需要Enable,但是作为输入是永远使能的,不需要额外的Enable。具体代码如图 57所示。

图 57

补充说明:MIO和EMIO都属于PS的GPIO,用于指示的变量类型为XGpioPs;而使用AXI_GPIO外设的GPIO,由于是属于PL的,所以指示这些IO的变量类型为XGpio。MIO和EMIO的控制对于SDK是完全相同的,其地址偏移量也是排在一起的,MIO从0排到53,EMIO接着从54开始。示例代码中显示的是EMIO作为输出和MIO作为输入,只需要将引脚编号的宏定义改为需要的MIO或者EMIO编号即可使用。在硬件配置时MIO的配置方法与EMIO有所不同,EMIO的配置如图 53所示。而MIO由于不像EMIO,外部管脚是确定的,所以可以在ZYNQ7 Processing System配置时同时完成属性以及电平的设置,如图 58所示。

图 58

ZYNQ中Interrupt使用参考工程见“ZYBO_Memory_GPIO_Interrupt_demo.xpr”。ZYNQ中的中断管理是通过Generic Interrupt Controller(GIC)完成的。任何的中断功能都需要两步,第一步是配置相应的中断,第二步是设置中断触发之后的服务函数。配置相应中断分以下几个步骤:1. 使能相应的功能,例如GPIO中断需要首先使能和配置GPIO;Timer中断需要首先使能和配置Timer;

图 59

2. 初始化并配置使能GIC,还要使能异常处理。第1步中的操作对于每个中断源来说都不相同,但是这一步的配置对于不同中断源而言是类似的。不同之处在于有一个参数:中断ID,即例子中的52是变化的,52是GPIO的中断号,其他中端需要使用不同的ID。该值可以在UG585中断的相关章节查询到,如图 61所示。另一个区别就是XScuGic_Connect时的服务子函数不同。

图 60

图 61

3. 编写中断服务函数,需要注意的是进入服务函数后首先需要禁止中断,保证在处理中断时不会再次因触发中断而程序跳转;另外就是需要清除中断标志位,否则会不断触发中断。

图 62

源代码见附件。

ZYNQ CPU内部任何有定时器,在Vivado的ZYNQ配置中无需任何操作就可以在SDK中直接使用。与GPIO中断类似,Timer的中断也包含相同的几步操作,下面给出各个阶段的代码片段,完整代码见附件。

1. 初始化Timer。

图 63

2. 初始化GIC,设置中断服务函数入口。

图 64

3. 配置Timer工作模式,导入计数初值,使能中断。

图 65

4. 编写中断服务函数,进入中断后首先Disable中断,清楚中断标志位;然后进行中断处理;退出中断服务函数前重新使能中断。

图 66

Appendix 1: 配套工程

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭