Xilinx ZYNQ 7000+Vivado2015.2系列(六)创建一个基于AXI总线的GPIO IP并使用

前言:

FPGA+ARM是ZYNQ的特点,那么PL部分怎么和ARM通信呢,依靠的就是AXI总线

这个实验是创建一个基于AXI总线的GPIO IP,利用PL的资源来扩充GPIO资源。

通过这个实验迅速入门开发基于总线的系统。

使用的板子是zc702。

AXI总线初识:

AXI (Advanced eXtensible Interface),由ARM公司提出的一种总线协议。

总线是一组传输通道, 是各种逻辑器件构成的传输数据的通道, 一般由数据线、地址线、 控制线构成。

Xilinx从6系列的 FPGA 开始对 AXI 总线提供支持, 此时 AXI 已经发展到了 AXI4 这个版本, Vivado里都是基于AIX4的 IP。

ZYNQ支持三种AXI总线,拥有三种AXI接口,用的都是AXI协议:

AXI4:主要面向高性能地址映射通信的需求,是面向地址映射的接口,允许最大256轮的数据突发传输。

AXI4-Lite:是一个轻量级的地址映射单次传输接口, 占用很少的逻辑单元。

AXI4-Stream:面向高速流数据传输,去掉了地址项,允许无限制的数据突发传输。

数据在总线上是遵守协议定的规则来传输的:

①AXI信号传输先是传地址,

②然后检测READY+VALID,都为高电平时开始传数据,

③当主机发送最后一个数据时LAST信号拉高,

④通知从机传输结束。

握手协议

在介绍读写如何进行前先介绍握手协议:

READY,VALID握手通信机制,

主机产生 VLAID 信号来指明何时数据或控制信息有效。

从机产生 READY 信号来指明已经准备好接受数据或控制信息。

传输发生在 VALID和 READY 信号同时为高的时候。

(还有一个LAST信号表示什么时候传到最后一个数据了)

读时序

① 地址线上发来地址,

② 地址准备和地址有效都高时,

③ 开始发送要读的数据,

④ 读准备和读有效都高时数据被读取到,

⑤ 发最后一个数据时读LAST信号拉高。

20171206221511965

写时序

① 地址线上发来地址。

② 地址准备和地址有效都高时,开始发送要写的数据。

③ 写准备和写有效都高时数据写入,发最后一个数据时写LAST信号拉高。

④ 写数据多了一个反馈信号,反馈给主机。

⑤ 主机接收到这个信号,就知道写成功了。

20171206221546708

这个协议可以暂时不去理清,知道大致信号关系,后面会通过观察波形进一步加深印象,这次实验的重点是学习通过编程操作寄存器完成读写!

第一步:创建AXI总线IP

新建一个工程,Tools-->Create and Pacakge IP-->选择Create AXI4 Peripheral

20171206222956359

创建完以后(起个易理解的名字,放到能找到的路径下),有三项需要设置:接口类型,数据类型和寄存器数量

20171207090703601

我们按默认这是就好,记住这里的设置:

选择AXI_Lite总线,数据位宽是32位,也就是4字节,寄存器4个,

实际我们用到的只有一个,但这里最低要求4个,

没关系,多出的不用就是,待会我们就要通过操作寄存器完成对数据的读写。

然后选择Edit IP,

打开ip的工程后,先打开这个文件:

20171207091613697

这个就是基于AXI_Lite总线协议的模块,可以看到我们设置的数据位宽和寄存器数量:

20171207092010561

20171207094254876

AXI总线向寄存器写数据:

20171207094400560

AXI总线下读寄存器的数据:

20171207094512249

接下来我们添加一个信号,将寄存器绑定到用户输出,用这个输出控制LED灯,这样可以通过观察LED的亮灭看有没有写入成功。

20171207095216127

然后打开顶层文件:

20171207095511963

将添加的信号加上去:

20171207095641131

20171207095708343

保存,Tools-->Create and Package IP:

20171207095754105

overwrite原来的文件。

在IP自己创建的工程文件夹里,打包好的IP就是这个文件夹,可以将其拷贝放到任意地方:

20171207100025629

至此,基于AXI_Lite总线的IP就完成了。可以将这个文件夹拷到你之前建的工程目录下,我是放在myip文件夹下。

第二步:使用基于AXI总线的IP

将我们自定义的IP添加到库里:

20171207101108486

Create Block Design,命名为GPIO_AXI_LED,

添加zynq核,双击修改ddr信号,其他默认设置:

20171207100749396

添加我们自己创建的IP,然后点击自动连接:

20171207101327893

会自动出现互联模块和复位模块,互联模块主要是起管理主从设备的作用:

20171207101517924

本来我们还应该添加逻辑分析仪观察AXI总线的各信号波形,但是为了先上手体验怎么开发基于AXI的系统,我们先略过,放在下一个实验中。

再点击Run Block Automatiom:

20171207102135968

将LED信号也输出出来,右击GPIO_LED,Make External。

右击空白处,选择Regenerate layout,美化一下排版:

20171207102323268

这样我们的系统就搭建成功了,下面就是一些例行操作:

第三步: 检查设计

检验一下我们的设计:

20171207102519342

保存一下我们的设计:

20171207102527469

右键bd文件,复位一下系统,Reset Output Products:

20171207102625880

右键bd文件,Geberate Output Products,

右键bd文件,Create HDL Wrapper。

第四步: 添加管脚约束

然后就是添加管脚约束,把GPIO_LED信号连接到LED灯上:

zc702的管教约束如下:

#GPIO PMOD1
set_property PACKAGE_PIN E15 [get_ports {GPIO_LED[7]}]
set_property IOSTANDARD LVCMOS25 [get_ports {GPIO_LED[7]}]
set_property PACKAGE_PIN D15 [get_ports {GPIO_LED[6]}]
set_property IOSTANDARD LVCMOS25 [get_ports {GPIO_LED[6]}]
set_property PACKAGE_PIN W17 [get_ports {GPIO_LED[5]}]
set_property IOSTANDARD LVCMOS25 [get_ports {GPIO_LED[5]}]
set_property PACKAGE_PIN W5 [get_ports {GPIO_LED[4]}]
set_property IOSTANDARD LVCMOS25 [get_ports {GPIO_LED[4]}]
#GPIO PMOD2
set_property PACKAGE_PIN V7 [get_ports {GPIO_LED[3]}]
set_property IOSTANDARD LVCMOS25 [get_ports {GPIO_LED[3]}]
set_property PACKAGE_PIN W10 [get_ports {GPIO_LED[2]}]
set_property IOSTANDARD LVCMOS25 [get_ports {GPIO_LED[2]}]
set_property PACKAGE_PIN P18 [get_ports {GPIO_LED[1]}]
set_property IOSTANDARD LVCMOS25 [get_ports {GPIO_LED[1]}]
set_property PACKAGE_PIN P17 [get_ports {GPIO_LED[0]}]
set_property IOSTANDARD LVCMOS25 [get_ports {GPIO_LED[0]}]

添加完先综合一下,看看连线有没有错误。综合完成生成比特流文件。

至此,大功告成,下面就到了本实现的重点,进入SDK写代码来读写寄存器!

第五步: 文件导入SDK

将硬件系统信息和bit文件导入SDK:

20171207103913127

然后Lanch SDK,新建一个空的工程:

20171207104323564

在src文件下建一个c文件:

20171207104453117

c大家都知道,用到什么函数要将这个函数所在的文件添加到头文件,这类先把头文件添加进去:

#include <stdio.h>
#include "xparameters.h"
#include "xil_io.h"
#include "sleep.h"
#include "xil_types.h"

Xinlin提供的

读函数是Xil_Out32((BaseAddr) + (u32)(RegOffset)),

写函数是Xil_Out32((BaseAddr) + (u32)(RegOffset), (u32)(Data)),

读写都是相对于Master而言的,读当然是In,写当然是Out了。

前面我们提到了,读写是对我们定义的寄存器操作,

我们这里8个led灯,只要用到寄存器0的低8位就可以了。

既然要操作寄存器,肯定要知道寄存器的地址,

所有设备的地址都放在bsp文件下的include文件里的xparameters.h文件里,并且以宏定义,方便调用:

20171207105234429

例如我们的自定义IP在这里,GPIO_Zhu,第一个是基地址,第二个是最高地址,:

20171207105529321

寄存器0所在地址就是基地址,偏移量为0,

因为我们定义的位宽是32位,4个字节,

寄存器1所在地址就是基地址+4,依次类推。

这里我们让8个Led灯依次闪烁,1秒移动一次,并读取寄存器的数据打印到串口:

#include <stdio.h>
#include "xparameters.h"
#include "xil_io.h"
#include "sleep.h"
#include "xil_types.h"

int main(){
	u8 i=0;
	u8 ledValue=0;
	Xil_Out32(XPAR_GPIO_ZHU_V1_0_0_BASEADDR+0*4,0X00);
	while(1){
		for(i=0;i<=7;i++){
			Xil_Out32(XPAR_GPIO_ZHU_V1_0_0_BASEADDR+0*4,1<<i); //写入1,8个Led,依次左移
			ledValue=Xil_In32(XPAR_GPIO_ZHU_V1_0_0_BASEADDR+0*4);
			xil_printf("ledValue=%x\r\n",ledValue); //打印到串口
			sleep(1); //1s移动一次
		}
		i=0;
	}
}

第六步: 准备测试

板子上电,连接好,以Debug方式运行:

20171207111029420

下载好后,打开串口:

20171207111336080

20171207111319628

第七步: 开始运行

点击开始运行:

20171207111538585

Led开始依次闪烁了!,并且在串口看到打印出的数据:

20171207111920050

至此,实验成功,开启了我们ARM+FPGA开发之路!以后可以尝试开发更复杂的系统。

posted @ 2024-03-27 16:49  L707  阅读(130)  评论(0编辑  收藏  举报