软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA
实验平台:米联客-MLK-H3-CZ08-7100开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!
1概述 本文的FPGA硬件工程部分和前文一样,本文的demo实现从SD卡读取图片在液晶屏上的显示。
本文实验目的:
1:掌握基于VDMA IP的图像显示系统的搭建方法
2:掌握VDMA Parked模式的简单应用
4:掌握HDMI显示器、7寸液晶屏的使用
5:掌握从SD卡读取bmp格式图片,并通过显示器显示
2系统框图
如下图所示,该方案中 PS 部分的 CPU 读取 SD 卡中的图片到 DDR 中, VDMA 从 PS DDR 中读取图片数据,发送给 AXI4-Stream to Video Out IP( 简称 VID OUT),VID OUT IP 把 Stream 流数据转为 RGB 时序给 HDMI 输出 IP, 这样就能驱动 HDMI 显示器了。
这里还用到了 Video Timing Controller IP( 简称 VTC), 该 IP 产生 RGB 视频时序,和 VID OUT IP 配合实现 AXIStream 视频数据流转为 RGB 视频数据。
如果是米联客 7 寸液晶屏,可以支持 LVDS(必须修改 BANK 电压支持 LVDS 通信,如果有不清楚请咨询米联 客技术支持)接口或者 RGB 接口,比如 LVDS 接口的系统框图中也是把 RGB 时序转为 LVDS 驱动液晶屏。
如果是使用RGB接口可以直接用RGB时序驱动液晶屏.
3硬件电路分析 硬件接口和子卡模块请阅读“附录1”
配套工程的FPGA PIN脚定义路径为soc_prj/uisrc/04_pin/ fpga_pin.xdc。
4搭建SOC系统工程 请阅读“04视频图形显示方案(VDMA)”一文中“5.4搭建SOC系统工程”这一章节
5搭建Vitis-sdk工程 创建soc_base sdk platform和APP工程的过程不再重复,如果不清楚请参考本章节第一个demo。
5.1创建SDK Platform工程
勾选对于FAT格式文件系统的支持
5.2创建sd_img_read测试工程 Sd_img_read APP使用到了parked模式,使用parked对缓存切换控制可以防止图片的撕裂
6程序分析
6.1sd_img_test.c主程序
/********************MILIANKE**************************
*Company : MiLianKe Electronic Technology Co., Ltd.
*WebSite:https://www.milianke.com
*TechWeb:https://www.uisrc.com
*tmall-shop:https://milianke.tmall.com
*jd-shop:https://milianke.jd.com
*taobao-shop1: https://milianke.taobao.com
*Create Date: 2021/10/15
*File Name: vdma_out_test.c
*Description:
*Declaration:
*The reference demo provided by Milianke is only used for learning.
*We cannot ensure that the demo itself is free of bugs, so users
*should be responsible for the technical problems and consequences
*caused by the use of their own products.
*Copyright: Copyright (c) MiLianKe
*All rights reserved.
*Revision: 1.0
****************************************************/
#include "xil_exception.h"
#include "xil_printf.h"
#include "xil_cache.h"
#include "vdma_pl.h"
#include "sleep.h"
#include "xsHDMIs.h"
#include "ff.h"
extern XAxiVdma VDMA;
extern XAxiVdma_DmaSetup WriteCfg;
extern XAxiVdma_DmaSetup ReadCfg;
#define BUF_SIZE 1280*720*3 //图像大小
u32 GFrame[3][BUFFERSIZE] __attribute__ ((__aligned__(256)));
static FATFS SD_Dev; // File System instance
char *SD_Path = "0:/"; // string pointer to the logical drive number
u8 RD_Buf[3][BUF_SIZE] __attribute__ ((aligned(32))); //分配3个缓存保持图像
//把图像数据读出后,改成4字节对齐后再写入到内存中
void show_img( unsigned char * addr,u32 * vdma_pbuf,u32 size_x, u32 size_y)
{
u32 x=0;
u32 y=0;
u32 r,g,b;
for(y=size_y;y>0;y--)
{
for(x=0;x<size_x;x++)
{
b = *(addr++);
g = *(addr++);
r = *(addr++);
Xil_Out32((vdma_pbuf+(((y-1)*size_x)+x)),((r<<16)|(g<<8)|(b<<0)));//write image data to ddr
}
}
// flush all dcache data to ddr
Xil_DCacheFlushRange((u32)(vdma_pbuf), 1280*720*4);
}
//Initialize SD
int SD_init()
{
FRESULT result;
//-----------------------mount dev-----------------------------------------------
result = f_mount(&SD_Dev,SD_Path, 0);
if (result != 0) {
return XST_FAILURE;
}
return XST_SUCCESS;
}
int main()
{
u8 mode_s =0;
u32 vcnt,hcnt;
u32* img_ptr;
SD_init();
//sleep(1);
//read image from sd card to ddr buffer
BMP_Picture((u8 *)"0001.bmp" , &RD_Buf[0],BUF_SIZE); //读取第1张图片
BMP_Picture((u8 *)"0002.bmp" , &RD_Buf[1],BUF_SIZE); //读取第2张图片
BMP_Picture((u8 *)"0003.bmp" , &RD_Buf[2],BUF_SIZE); //读取第三张图片
//set mm2s read channel address
ReadCfg.FrameStoreStartAddr[0] = (UINTPTR)(&GFrame[0]); //VDMA第1个缓存地址
ReadCfg.FrameStoreStartAddr[1] = (UINTPTR)(&GFrame[1]); //VDMA第2个缓存地址
ReadCfg.FrameStoreStartAddr[2] = (UINTPTR)(&GFrame[2]); //VDMA第3个缓存地址
//setup mm2s read channel
MM2S_ReadSetup(XPAR_AXIVDMA_0_DEVICE_ID, &VDMA, ReadCfg); //设置VDMA的读通道
xil_printf("VDMA Generic Video start! \r\n");
//read image from sd to ddr buffer
show_img(&RD_Buf[0],&GFrame[0],1280,720); //第1张图片刷入到DDR
show_img(&RD_Buf[1],&GFrame[1],1280,720); //第2张图片刷入到DDR
show_img(&RD_Buf[2],&GFrame[2],1280,720); //第3张图片刷入到DDR
while(1)
{
//parking ddr buffer to screen
XAxiVdma_StartParking(&VDMA, mode_s,XAXIVDMA_READ); //通过Park模式,每间隔1S更新一张图片
sleep(1);
if(mode_s >=2 ) mode_s = 0;
else mode_s ++;
}
return XST_SUCCESS;
} 复制代码
以上函数中主要2个知识点:1、读取SD卡中的bmp格式图片到DDR内存中;2、把DDR内存中的图片通过VDMA的Park模式输出到显示器上。
读bmp格式图片
由于bmp格式图片读出来是rgb888(24bit)对齐,所以还用使用以下函数把rgb888改为rgbx888(32bits)
这样三副图片会保存到GFrame[0]、GFrame[1]、GFrame[2]内存地址中。
6.2bmp.c程序 #include "bmp.h"
#include "ff.h"
/****************************************************************************
* Function Name : BMP_ReadHeader
* Description : 将读取到的数组函数转换位BPM文件信息结构体类型。由于在内存
* * 上面数组的存储方式与结构体不同,所以要转换,而且SD读取到的
* * 文件信息是小端模式。高位是低字节,低位是高字节,跟我们常用
* * 的正好相反所以将数据转换过来。
* Input : header:要转换的数组
* * bmp:转换成的结构体
* Output : None
* Return : None
****************************************************************************/
void BMP_ReadHeader(uint8_t *header, BMP_HeaderTypeDef *bmp)
{
bmp->fileHeader.bfType = ((*header) << 8) | (*(header + 1));
header += 2;
bmp->fileHeader.bfSize = ((*(header + 3)) << 24) | ((*(header + 2)) << 16) |
((*(header + 1)) << 8) | (*header);
header += 8;
bmp->fileHeader.bfOffBits = ((*(header + 3)) << 24) | ((*(header + 2)) << 16) |
((*(header + 1)) << 8) | (*header);
header += 4;
bmp->infoHeader.bitSize = ((*(header + 3)) << 24) | ((*(header + 2)) << 16) |
((*(header + 1)) << 8) | (*header);
header += 4;
bmp->infoHeader.biWidth = ((*(header + 3)) << 24) | ((*(header + 2)) << 16) |
((*(header + 1)) << 8) | (*header);
header += 4;
bmp->infoHeader.biHeight = ((*(header + 3)) << 24) | ((*(header + 2)) << 16) |
((*(header + 1)) << 8) | (*header);
header += 6;
bmp->infoHeader.biBitCount = ((*(header + 1)) << 8) | (*header);
header += 2;
bmp->infoHeader.biCompression = ((*(header + 3)) << 24) | ((*(header + 2)) << 16) |
((*(header + 1)) << 8) | (*header);
header += 4;
bmp->infoHeader.biSizeImage = ((*(header + 3)) << 24) | ((*(header + 2)) << 16) |
((*(header + 1)) << 8) | (*header);
header += 4;
bmp->infoHeader.biXPelsPerMeter = ((*(header + 3)) << 24) | ((*(header + 2)) << 16) |
((*(header + 1)) << 8) | (*header);
header += 4;
bmp->infoHeader.biYPelsPerMeter = ((*(header + 3)) << 24) | ((*(header + 2)) << 16) |
((*(header + 1)) << 8) | (*header);
}
/****************************************************************************
* Function Name : BMP_Picture
* Description : 显示BMP格式的图片
* Input : dir:要显示的图片路径和名字
* Output : None
* Return : None
****************************************************************************/
void BMP_Picture(uint8_t *dir , uint8_t * buf ,uint32_t len)
{
FRESULT res;
FIL fsrc;
UINT br;
UINT a;
uint8_t buffer[1024];
BMP_HeaderTypeDef bmpHeader;
/* 打开要读取的文件 */
res = f_open(&fsrc, (const TCHAR*)dir, FA_READ);
if(res == FR_OK) //打开成功
{
/* 读取BMP文件的文件信息 */
res = f_read(&fsrc, buffer, sizeof(buffer), &br);
/* 将数组里面的数据放入到结构数组中,并排序好 */
BMP_ReadHeader(buffer, &bmpHeader);
a = bmpHeader.fileHeader.bfOffBits; //去掉文件信息才开始是像素数据
res=f_lseek(&fsrc, a);
if(res)
{
return 0;
}
res = f_read(&fsrc, buf, len, &br);
}
f_close(&fsrc); //不论是打开,还是新建文件,一定记得关闭
} 复制代码
Bmp.c函数中重要是需要对BMP图片格式的头部进行解析,并且获取图像数据的开始位置,然后用 f_lseek(&fsrc, a)函数,定位到图像数据的位置,之后再读出图像的数据。
#ifndef _bmp_H
#define _bmp_H
#include <stdio.h>
typedef struct
{
uint16_t bfType; //文件类型,BMP格式为字符串BM
uint32_t bfSize; //图片大小,单位为KB
uint16_t bfReserved1; //保留位
uint16_t bfReserved2; //保留位
uint32_t bfOffBits; //从文件头到实际图像数据之间的字节偏移量
} BMP_FileHeaderTypeDef;
typedef struct
{
uint32_t bitSize; //BMP_InfoHeaderTypeDef结构体所需要的字节数
uint32_t biWidth; //图片宽度,像素位单位
int32_t biHeight; //图片高度,像素为单位。正为倒立,负为正向。
uint16_t biPlanes; //颜色平面数,总为1
uint16_t biBitCount; //比特数/像素。其值为:1、4、8、16、24或32
uint32_t biCompression; //数据压缩类型
uint32_t biSizeImage; //图像大小
uint32_t biXPelsPerMeter;//水平分辨率
uint32_t biYPelsPerMeter;//垂直分辨率
uint32_t biClrUsed; //颜色索引数
uint32_t biClrImportant; //重要颜色索引数
}BMP_InfoHeaderTypeDef;
typedef struct
{
BMP_FileHeaderTypeDef fileHeader;
BMP_InfoHeaderTypeDef infoHeader;
}BMP_HeaderTypeDef;
void BMP_ReadHeader(uint8_t *header, BMP_HeaderTypeDef *bmp);
void BMP_Picture(uint8_t *dir , uint8_t * buf ,uint32_t len);
#endif 复制代码
7HDMI方案演示 对于 MLK-H3-CZ08-7100FC 不支持从 SD 卡启动, 因此必须通过 JTAG 调试模式完成本实验
7.1硬件准备 复制路径soc_prj/uisrc/06_doc/testimage路径下的测试图片到SD卡,并且插入到开发中。
本实验需要用到 JTAG 下载器、USB 转串口外设,另外需要把核心板上的 2 P 模式开关 设置到 JTAG 模式,即 ON ON (注意新版本的 MLK-H3-CZ08-7100FC(米联客 7X 系列),支持 JTAG 模式,对于老版本的核心板,JTAG 调试 的时候一定要拔掉 TF 卡,并且设置模式开关为 OFF OFF)
7.2实验结果
8液晶屏LVDS方案演示 对于 MLK-H3-CZ08-7100FC 不支持从 SD 卡启动, 因此必须通过 JTAG 调试模式完成本实验
8.1硬件准备 复制路径soc_prj/uisrc/06_doc/testimage路径下的测试图片到SD卡,并且插入到开发中。
液晶屏,模式开关1脚切到OFF
本实验需要用到 TF 卡方测试图片
8.2实验结果