基于Lemon ZYNQ的PS实验二十 用VDMA模块来缓存图像并在HDMI上显示(二)显示TF卡上的BMP格式图片

本节作为前一节的补充,将TF卡上的图片通过VDMA的方式显示在HDMI设备上,作为VDMA的实战演练

因为本文内容和前一节有大量的重复雷同工作,所以本节只介绍关键的几个地方,需要看详细工程创建的请看 VDMA的第一节内容

  • 此章节内容适用于Lemon ZYNQ主板,如是其他板子请看对应板子目录
  • 本文在 vivado2018.3版本上演示

一、VIVADO工程的修改

1)vivado 在blockdesign里 使能SD功能,(之前工程其实已经做了这一步)

2)将上一章的工程分辨率修改设置为720P分辨率

a) 将 clk_out1 修改成1080p的 74.25 ,而clk_out2修改成5倍的clk_out1 即371.25即可。

2)VTC的 参数修改: 为了把hdmi显示的分辨率修改成720P,我们这里还需要修改Video Timing Controller 的参数,如下图所示,双击模块,然后将Video Mode 修改成720p即可

之后对工程进行重新编译和综合,并重新export一遍工程。

二、SDK部分的修改

1)接下来SDK部分我们作如下修改, 因为我们要加载TF卡,而TF卡的格式是FAT32的,所以这里我们需要加载FatFs库,xilinx已经帮我们集成了这部分库的功能,我们只需要使能功能就好,操作如下

2)添加 XILFFS 库

3)接下来需要使能 文件名称的功能 默认是关闭的

4)代码的编写

复制以下代码到main.c中

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "xil_types.h"
#include "xil_cache.h"
#include "xparameters.h"
#include "xaxivdma.h"
#include "xaxivdma_i.h"
#include "ff.h"
#include "sleep.h"



void load_sd_bmp(u8 *frame,unsigned char mode);

void load_sd_bmp(u8 *frame,unsigned char mode)
{
	static 	FATFS fatfs;
	FIL 	fil;
	u8		bmp_head[54];
	UINT 	*bmp_width,*bmp_height;
	UINT 	br;
	int 	i;

	//挂载文件系统
	f_mount(&fatfs,"",1);
	//打开文件
	if(mode==0)f_open(&fil,"C.bmp",FA_READ);
	else f_open(&fil,"D.bmp",FA_READ);
	//移动文件读写指针到文件开头
	f_lseek(&fil,0);
	//读取BMP文件头
	f_read(&fil,bmp_head,54,&br);

	//打印BMP图片分辨率和大小
	bmp_width  = (UINT *)(bmp_head + 0x12);
	bmp_height = (UINT *)(bmp_head + 0x16);
	//读出图片,写入DDR
	for(i=*bmp_height-1;i>=0;i--){
		f_read(&fil,frame+i*(*bmp_width)*3,(*bmp_width)*3,&br);
	}

	//关闭文件
	f_close(&fil);
	
	Xil_DCacheFlush();     //刷新Cache,数据更新至DDR3中
}


#define H_STRIDE	1280
#define H_ACTIVE	1280
#define V_ACTIVE	720

#define VDMA_BASEADDR	XPAR_AXI_VDMA_0_BASEADDR

#define VIDEO_BASEADDR0 0x01000000//帧存0地址
#define VIDEO_BASEADDR1 0x02000000//帧存1地址

void VDMA_init(){
	Xil_Out32((VDMA_BASEADDR + 0x000), 0x3); 		// enable circular mode
	Xil_Out32((VDMA_BASEADDR + 0x05c), VIDEO_BASEADDR0); 	// start address
	Xil_Out32((VDMA_BASEADDR + 0x060), VIDEO_BASEADDR0); 	// start address
	Xil_Out32((VDMA_BASEADDR + 0x064), VIDEO_BASEADDR0); 	// start address
	Xil_Out32((VDMA_BASEADDR + 0x058), (H_STRIDE*3)); 		// h offset (H_STRIDE * 3) bytes
	Xil_Out32((VDMA_BASEADDR + 0x054), (H_ACTIVE*3)); 		// h size (H_ACTIVE * 3) bytes
	Xil_Out32((VDMA_BASEADDR + 0x050),  V_ACTIVE); 			// v size (V_ACTIVE)
}

int main(void)
{
		load_sd_bmp((u8*)VIDEO_BASEADDR0,0);
		load_sd_bmp((u8*)VIDEO_BASEADDR1,1);
		VDMA_init();

	while(1){

		Xil_Out32((VDMA_BASEADDR + 0x05c), VIDEO_BASEADDR0); 	// start address
		Xil_Out32((VDMA_BASEADDR + 0x060), VIDEO_BASEADDR0); 	// start address
		Xil_Out32((VDMA_BASEADDR + 0x064), VIDEO_BASEADDR0); 	// start address
		Xil_Out32((VDMA_BASEADDR + 0x058), (H_STRIDE*3)); 		// h offset (H_STRIDE * 3) bytes
		Xil_Out32((VDMA_BASEADDR + 0x054), (H_ACTIVE*3)); 		// h size (H_ACTIVE * 3) bytes
		Xil_Out32((VDMA_BASEADDR + 0x050),  V_ACTIVE); 			// v size (V_ACTIVE)

		sleep(1);

		Xil_Out32((VDMA_BASEADDR + 0x05c), VIDEO_BASEADDR1); 	// start address
		Xil_Out32((VDMA_BASEADDR + 0x060), VIDEO_BASEADDR1); 	// start address
		Xil_Out32((VDMA_BASEADDR + 0x064), VIDEO_BASEADDR1); 	// start address
		Xil_Out32((VDMA_BASEADDR + 0x058), (H_STRIDE*3)); 		// h offset (H_STRIDE * 3) bytes
		Xil_Out32((VDMA_BASEADDR + 0x054), (H_ACTIVE*3)); 		// h size (H_ACTIVE * 3) bytes
		Xil_Out32((VDMA_BASEADDR + 0x050),  V_ACTIVE); 			// v size (V_ACTIVE)
		sleep(1);
	}
    return 0;
}

代码非常好理解,load_sd_bmp 实现的功能是挂载TF卡,并且根据mode的值来读取TF卡中对应名称的图片,并将图片的内容存入对应的DDR地址内。(mode 是我自己加入的,通过mode的值的大小分别载入两张不同的图片,最终在主函数里实现两张图片显示的动态切换)

VIDEO_BASEADDR0,和VIDEO_BASEADDR1分别对应两帧的缓存地址(只要两个帧之间的地址相差的位置可以调整,只要不要彼此相互有交集产生内存相互影响就可以了)

#define VIDEO_BASEADDR0 0x01000000//帧存0地址
#define VIDEO_BASEADDR1 0x02000000//帧存1地址

VDMA 的初始化,因为之前我们在VIVADO 的blockdesign 里设计的VDMA缓存有3个,所以这里需要同时定义3个缓存区对应的DDR上的地址映射(为了方便演示,我们这里将3个缓存区映射到同一个DDR区)

备注 多缓存区存在的目的是为了防止画面的撕裂,但是VDMA会在多个缓存区中循环的显示内容,比方说如果我们这里设置成3个缓存区地址不同的话,实际我们修改了其中一帧的内容会造成,3帧中的一帧不一致,导致画面另外两帧还是原先的内容,然后画面快速三帧循环,效果就是不停的切换闪烁, 所以这里为了方便演示将VDMA三个缓存区映射到相同的地址进行演示(等效于VDMA 只有一帧的效果)

主程序里 加载两张照片分别到VIDEO_BASEADDR0内存区域,和VIDEO_BASEADDR1内存区域,并完成VDMA初始化

		load_sd_bmp((u8*)VIDEO_BASEADDR0,0);
		load_sd_bmp((u8*)VIDEO_BASEADDR1,1);
		VDMA_init();

主循环也很简单,通过调整VDMA 帧缓存的映射地址(这些地址在之前已存入TF图片信息),来实现HDMI不同画面的切换

三、上机调试

1)之后在电脑上插入FAT32格式的TF卡,并将下列文件下载并解压并存入TF卡的根目录(切记 TF卡必须为fat32格式) HDMI 测试图片 image下载

其中A,B是1080P的BMP,C,D是720P的BMP图片文件

2)将TF卡从电脑正常退出,并插入到我们主板的 TF卡卡槽中

3) 回到SDK界面,对程序进行正常编译,并debug,没问题的话,应该可以看到两张720p的图片在循环播放的。

写在实验后:

1)如果要修改分辨率,只需要对VTC的分辨率,以及Clock时钟,以及main函数分辨率对应位置进行修改即可(本节和上一节内容均有提及),备注TF卡中的图片A.bmp 和B.bmp 对应1080p,C.bmp和D.bmp对应720P。大家调用的时候注意选择。

2)切换vdma缓存的方法暂时没有找到更简单的,所以就像上面这样,通过寄存器完整的修改了,后续如果找到更简单的方法我再修改

3)PS和前一节的VDMA初始化不同,前一节用的是库函数,而这里直接对寄存器写入来实现VDMA初始化,两种方式都是可以的

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注