【ZYNQ 详细案例五】采用AXI4总线封装自定义VGA显示IP核 显示自定义图片或者字符内容 基于ZEDBOARD

【ZYNQ 详细案例五】采用AXI4总线封装自定义VGA显示IP核 彩条实验 基于ZEDBOARD

第一部分:PL部分

首先我们先创建工程

在这里插入图片描述
然后创建block design
在这里插入图片描述
添加PS处理器
在这里插入图片描述
自动配置 ZEDBOARD的预设。

好了以后我们创建一个新 IP 核
在这里插入图片描述
因为我们要创建一个AXI4总线标准的IP核,所以这里我们选择AXI4总线的外设
在这里插入图片描述
NEXT
在这里插入图片描述
NEXT
在这里插入图片描述
这一步要选择接口类型、接口模式、以及数据位宽还有寄存器个数。
这里因为我们是一个作为VGA显示的驱动,所以数据是由上层输入给这个IP核,然后由这个IP在输出VGA口,所以我们是从设备,这里接口模式就选择从设备(Slave),数据位宽保持默认就好,因为会比较方便和其他IP连接。但实际上我们只用到里面一些数据。

寄存器个数这里使用默认的4个就足够了。

NEXT
在这里插入图片描述
FINISH
进入IP工程编辑窗口
在这里插入图片描述
来到这个界面我们即可开始实现我们的IP核。
首先将我们上一节写的代码拿出来。添加到工程中
在这里插入图片描述
这里Bram没有实现可以先暂时不用处理,我们最后用【IP Catalog】生成一个bram就行。这是上次案例的东西。
现在我们假想AXI4外部顶层是一个统一的包装。但是核心是我们的VGA驱动,但是用这个核心的包装我们可以和其他模块进行交流。因此,我们将我们需要输入输出的端口 在这个“包装”里面声名。也就是在AXI4 IP 核自动生成的顶层.v文件下加入我们的接口。
也就是下面的接口。
在这里插入图片描述
我们在如图所示的区域声名,vivado已经帮我们留出了位置
在这里插入图片描述
接下来,在【zed_vga_v1_0_S00_AXI_inst】模块例化时也要加上。
因为【Zed_vga_v1_0】模块相当于是一个外面的包装,【zed_vga_v1_0_S00_AXI_inst】是内包装,我们核心的【vga_driver】才是核心。他们的区别:
【Zed_vga_v1_0】是单纯为了实现包装而存在,而
【zed_vga_v1_0_S00_AXI_inst】是实际实现了AXI总线传输协议时序的模块。用户一般实例化自己的模块也是在这个.v文件里面例化。
现在我们先把【zed_vga_v1_0_S00_AXI_inst】完整例化,
从某中角度上说,我们现在是一个自顶向下操作的。
在这里插入图片描述
然后在【zed_vga_v1_0_S00_AXI_inst】模块中声明端口
在这里插入图片描述
【zed_vga_v1_0_S00_AXI_inst】代码可能比较多,但是绝大部分你都不用关心,声名了端口后,滑动到最下方有一个 Add user logic here 的注释,在这里,我们完成我们的关于AXI4总线的逻辑和核心模块的例化,以及他们两个的交互关系。
在这里插入图片描述
在这里插入图片描述
这里,我们对每一个信号逐一分析。
【sys_clk】:是系统输入的时钟,是写入缓存的时钟,所以是100MHZ的系统时钟,在这里我们可以直接用AXI的时钟 【S_AXI_ACLK】
【rst_i】:复位信号高电平有效 ,不复位的话,直接可以拉低 为【1’b0】
【we_addr】( 是否写入地址 )
【we_data】( 是否写入数据 )
这里对于we信号我们可以做一个逻辑判断,将【slv_reg_wren】与【axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB]】的值相遇,当都要写入的话,先判断【axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB]】的值是0还是1,如果是0就是【we_data】有效,是1就是【we_addr】有效。这个功能将会与我们在驱动内部实现的一个小部分对应。
【din】( 将要写入的数据) 【S_AXI_WDATA】

最后的实现:
在这里插入图片描述
现在【ctrl+s】保存看看我们的整个IP核结构
在这里插入图片描述
一层套一层。底层很复杂,越到上层接口越少,逻辑实现也越简单越简洁。就像代码语言一样,从下到上封装得越来越好,高级语言越来越简单方便。

现在,我们注意!在vga_driver中外部数据输入部分的缓存时钟是我们的系统时钟100MHZ,这是个爱出bug的点。若采用25MHZ最后生成图像会花屏,很恐怖。
在这里插入图片描述
最后一步工作了,我们该例化我们的bram IP核了。这个上节我们应该讨论过了。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里初始化文件可以选也可以不用
在这里插入图片描述
对了,这里B,读出端口的 第一季缓存寄存器我们可以不用设置
在这里插入图片描述
然后生成一下BRAM IP
在这里插入图片描述
最后好了以后,回到这个界面
在这里插入图片描述
点一下更新
在这里插入图片描述
最后重新打包一下
在这里插入图片描述
现在完成IP核的创建了,我们回到bd界面,可以添加我们的IP核了。
在这里插入图片描述
在这里插入图片描述
现在添加一个25MHZ的时钟
在这里插入图片描述
连接好以后就点击自动连线就好,最后的布局:
在这里插入图片描述
然后生成bd
在这里插入图片描述
生成顶层HDL文件
在这里插入图片描述
可以看到现在的工程结构
在这里插入图片描述
现在添加管脚约束

 

set_property PACKAGE_PIN  Y19  [get_ports {v_sync}]
set_property IOSTANDARD LVCMOS33 [get_ports {v_sync}]

set_property PACKAGE_PIN  AA19  [get_ports {h_sync}]
set_property IOSTANDARD LVCMOS33 [get_ports {h_sync}]

set_property PACKAGE_PIN  V20   [get_ports {R[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {R[0]}]
set_property PACKAGE_PIN    U20 [get_ports {R[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {R[1]}]
set_property PACKAGE_PIN   V19  [get_ports {R[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {R[2]}]
set_property PACKAGE_PIN   V18  [get_ports {R[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {R[3]}]

set_property PACKAGE_PIN  AB22   [get_ports {G[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {G[0]}]
set_property PACKAGE_PIN   AA22  [get_ports {G[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {G[1]}]
set_property PACKAGE_PIN   AB21  [get_ports {G[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {G[2]}]
set_property PACKAGE_PIN   AA21  [get_ports {G[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {G[3]}]

set_property PACKAGE_PIN   Y21  [get_ports {B[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {B[0]}]
set_property PACKAGE_PIN   Y20  [get_ports {B[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {B[1]}]
set_property PACKAGE_PIN   AB20  [get_ports {B[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {B[2]}]
set_property PACKAGE_PIN   AB19  [get_ports {B[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {B[3]}]
 


生成bit流
在这里插入图片描述
好了以后
在这里插入图片描述
点击cacel,然后去生成硬件顶层文件,接着Launch SDK
在这里插入图片描述
在这里插入图片描述

PS软件部分

首先创建一个FSBL应用
在这里插入图片描述
在这里插入图片描述
稍等片刻。。
在这里插入图片描述

再创建我们的主程序,【vga_test】
在这里插入图片描述
模板用Helloworld就行,或者空项目也行。

现在我们先编写VGA显示部分的代码,首先先写一个【vga_utils.c】
在这里插入图片描述
vga_utils.c
宏定义类型

#include "stdlib.h"

#define u16 unsigned short
#define u32 unsigned int 
#define u8  unsigned char

然后在 ASCII table网站上可以找到字符编码集合
在这里插入图片描述
我们取出一些常见的来使用

然后,我们 定义我们的地址和数据寄存器。
打开vivado,找到【address editior】
在这里插入图片描述
可以找到,我们的AXI的寄存器开始地址是 0x43C00000,当时我们只用到了2个寄存器对吧,一个传数据另一个传地址。因为是32位的寄存器,所以这里0号寄存器我们用来传数据,1号用来传地址它们相差4个字节(一个字节八位),
故有

#define VGA_DATA_REG *(volatile unsigned int * )(0x43c00000)
#define VGA_ADDR_REG*(volatile unsigned int *) (0x43c00004)

volatile是不自动优化寄存器。

现在和之前做VGA时序一样,我们要绘制VGA,

void VGA_DrawPoint(u16 x,u16 y,u16 dot)
{
  VGA_ADDR_REG = y*640+x;
  VGA_DATA_REG = dot ;
}

给对应的区域声名背景颜色

void VGA_Fill(u16 x1,u16 y1,u16 x2,u16 y2,u16 dot)
{
	u16 x,y;
	for(x=x1;x<=x2;x++)
	{
		for(y=y1;y<=y2;y++)
			VGA_DrawPoint(x,y,dot);
	}
}

在指定字符的位置显示一个char

void VGA_ShowChar(u16 x,u16 y,u8 chr,u16 dot,u16 bg)
{
	u16 temp,t,t1;
	u16 y0=y;
	chr=chr-' ';//得到偏移后的值
	for(t=0;t<16;t++)
	{
		temp=asc2_1608[chr][t];		 //调用1608字体
		for(t1=0;t1<8;t1++)
		{
			if(temp&0x80)VGA_DrawPoint(x,y,dot);
			else VGA_DrawPoint(x,y,bg);
			temp<<=1;
			y++;
			if((y-y0)==16)
			{
				y=y0;
				x++;
				break;
			}
		}
	}
}

显示字符串

void VGA_ShowString(u16 x,u16 y,const u8 *p,u16  dot,u16 bg)
{
	#define MAX_CHAR_POSX 640
	#define MAX_CHAR_POSY 480
	while(*p!='\0')
	{
		if(x>MAX_CHAR_POSX){x=0;y+=16;}//换行
		if(y>MAX_CHAR_POSY){y=x=0;VGA_Fill( 0 ,0,640,480, 0 );}//清屏
		VGA_ShowChar(x,y,*p,dot,bg);
		x+=8;
		p++;
	}
}

显示一幅图像

void VGA_ShowBMP_640x480( u16 * p ){
int i = 640*480 ;
  VGA_ADDR_REG = 0 ;
  while(i--) VGA_DATA_REG = *p++ ;
}

写了这么多方法,应该非常足够我们调试了。

我们在main方法中,写一个测试方法看看是否能够完成我们预期的设想。
在坐标100,100的位置显示一串字符串
在这里插入图片描述
启动的配置在这里插入图片描述
在这里插入图片描述

成功了!,背景的图片是我们默认在bram初始化的图像。
现在我们在ps上试试加载一幅图片。
1、先将VGA显示器默认填充为黑色
2、再读入图片
在这里插入图片描述

关于这个bmp头文件,也就是24位bmp的色位图文件。
在这里插入图片描述

我们看看运行结果
在这里插入图片描述
在这里插入图片描述
现在按照我们的方法,可以随心所欲在VGA上成像了。如果能够编写一些复杂的程序甚至可以做一些图像小游戏。贪吃蛇,走迷宫什么的。。。

不过总而言之,这次实验比较复杂,PL+PS都涉及到了,比较综合,需要多多理解。

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值