本节作为前两节的补充,将TF卡上的图片通过VDMA的方式显示在HDMI设备上,作为VDMA的实战演练
因为本文内容和前两节有大量的重复雷同工作,所以本节只介绍关键的几个地方,需要看详细工程创建的请看 VDMA的第一和第二节内容
(备注 此章节内容适用于 Smart ZYNQ SP和SL 版的板子,如果是Smart ZYNQ 标准版请看对应板子目录)
以下工程均在vivado2018.3下实现,其他版本请自行尝试
VIVADO工程的修改
vivado 在blockdesign里 使能SD功能,(之前两个工程其实已经做了这一步)
![](http://www.hellofpga.com/wp-content/uploads/2022/12/image-73.png)
SDK部分的修改
接下来SDK部分我们作如下修改, 因为我们要加载TF卡,而TF卡的格式是FAT32的,所以这里我们需要加载FatFs库,xilinx已经帮我们集成了这部分库的功能,我们只需要使能功能就好,操作如下
![](http://www.hellofpga.com/wp-content/uploads/2022/12/image-70.png)
添加 XILFFS 库
![](http://www.hellofpga.com/wp-content/uploads/2022/12/image-71.png)
接下来需要使能 文件名称的功能 默认是关闭的
![](http://www.hellofpga.com/wp-content/uploads/2022/12/image-72.png)
代码的编写
复制以下代码到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地址
![](http://www.hellofpga.com/wp-content/uploads/2022/12/image-77.png)
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不同画面的切换
![](http://www.hellofpga.com/wp-content/uploads/2022/12/image-76.png)
上机调试
之后在电脑上插入FAT32格式的TF卡,并将下列文件下载并解压并存入TF卡的根目录(切记 TF卡必须为fat32格式)
其中A,B是1080P的BMP,C,D是720P的BMP图片文件
![](http://www.hellofpga.com/wp-content/uploads/2022/12/image-74.png)
![](http://www.hellofpga.com/wp-content/uploads/2022/12/image-75.png)
之后回到SDK,对程序进行正常编译,并debug,没问题的话,应该可以看到两张720p的图片在循环播放的
备注 切换vdma缓存的方法 暂时没有找到更简单的,所以就向上面这样,通过寄存器完整的修改了,后续如果找到更简单的方法我再修改
以下是720P图片显示的完整工程 仅供参考(需要事先先把TF卡中放入对应分辨率和名称的bmp图片文件,上文中有提供下载)
1080P 的工程类似, 要注意的是1080P的工程里 加载的是 A.bmp 和B.bmp (720P的工程是C.bmp和D.bmp),下面是1080P的完整工程
PS和前两节的VDMA初始化不同,前两节用的是库函数,而这里直接对寄存器写入来实现VDMA初始化,两种方式都是可以的
备注 :如遇到自己的工程显示不稳定的情况(1080P情况下),可尝试在时序约束向导Timing Constraints Wizard 里对单倍图像时钟进行约束(在Generated Clocks页面下),设置成5分频即可(1/5),之后再重新生成bit并且EXPORT
![](http://www.hellofpga.com/wp-content/uploads/2023/05/image-33-1024x434.png)