目录
第一部分、实验说明
1、点名过来看
之前写过一篇VGA接口驱动的博客【FPGA入门】第七篇、FPGA实现VGA接口驱动
,然后有位道友在下面评论这个问题。然后我就研究了一下HDMI,现在研究明白了,请这位道友记得过来学习。🤪🤪🤪
2、实验说明
本次实验有两个
实验一、无需SD卡,将小数据量的图片通过HDMI显示在显示器上;
实验二、借助SD卡,通过读取SD卡内部的BMP图片数据(图片分辨率1920*1080),再借助HDMI将图片显示在显示器上。
2.1、涉及到的知识
(1)、HDMI发送模块的设计原理,参考博客:
【ZYNQ入门】第四篇、HDMI发送端的构成原理-CSDN博客
(2)、ZYNQ内部AXI HP高速接口的读时序以及使用方法,参考博客:
【ZYNQ入门】第五篇、AXI HP口读写数据原理-CSDN博客
(3)、ZYNQ使用SD卡实现读取文本数据和BMP图片数据的原理,参考博客:
【ZYNQ入门】第六篇、ZYNQ驱动SD卡读取TXT文本和BMP图片-CSDN博客
(4)、跨时钟域数据传输的问题、ZYNQ内部数据缓存一致性原理;
一起写在了第五篇博客的第一节【ZYNQ入门】第五篇、AXI HP口读写数据原理-CSDN博客
(6)、MATLAB读取24bit真彩图像方式。直接参考本篇博客第三节。
2.2、使用的硬件
(1)、FPGA开发板: ZYNQ-7020 ,详细型号:xc7z020clg400-1;
(2)、vivado版本:2018.2;
(3)、HDMI接口显示器分辨率:1920*1080@60Hz;
3、测试效果
3.1、实验一效果
这里选了三张分辨率为1000*1000的鲲图,然后三张循环播放,循环间隔3s,实际效果如下
(写博客视频居中的办法:新建一个1*1居中的表格,把视频放入这个表格。)
HDMI显示小数据量图片 |
3.2、实验二效果
这里选了三张科迈罗汽车的照片,每张照片的分辨率都为1920*1080的高清图片。首先将照片存入到SD卡中,然后让FPGA去读取SD卡内的图片数据,每个1s显示一张照片,循环显示三张科迈罗的高清图片。
HDMI显示大数据量图片 |
4、写在前面
再次申明一下,首先,我写博客是为了记录自己的学习过程,同时巩固记忆,因此内容中肯定有差错,还望见谅。
其次,很多老铁看我文章都是为了救急一些课程程设计或者毕业设计,这些我觉得挺好的,但是你不要连博客都不看,直接下载源码,然后出了问题,直接私信我:“博主,我把你的工程编译好之后,烧录了,没有反应,你知道是什么问题吗?”。我感觉你像个铁憨憨🤭🤭🤭。
正常情况下我提供的都是思路,不是结果!不是结果!不是结果!所以我希望家人们要多花点时间调试,想获取结果得弄懂,没弄懂,出了结果你也不知道为什么。我更希望大家问我的问题是:“为什么这里要这么搞?”这种问题我一定会回复,但是你问我:“你的结果为什么出不来?”,我一般不会回复的。
最后,我认为调试是一个漫长的过程,只有静下心来慢慢搞,实现了才会有成就感。特别是借助别人的代码基础实现自己想要的功能,这真的会有很多的成就感呢!共勉!
5、参考文献
米联客_XILINX ZYNQ裸机篇2019版
正点原子_领航者ZYNQSDK开发指南
V3学院FPGA视频课程
第二部分、硬件搭建
整个系统的连接如下,主要是两个模块,一个hdmi数据发送模块,一个AXI HP接口读模块。
两个实验的硬件都是一样的,不需要修改。
第三部分、实现方法
1、实验一
第一个实验,可用于小数据量的图片显示。前期通过MATLAB将图片转换为RGB888格式的真彩数据;然后,将数据放入一个数组;最后再将数组内的数据写入DDR。
缺点:MATLAB生成的数据复制到SDK内部时,SDK软件非常的卡顿,因为数据量太大了。所以小数据量的照片可以用这种办法,如果分辨率接近1920*1080的图片,想用这种方法,那么SDK软件应该会直接卡住动都动不了。
1.1、实验一原理图
如下图为实验1的原理图。
这里的的ZYNQ一般是CPU0,我通过定义数组的方式来存储小数据量的图片。而这个数组它最终还是映射还是DDR3上面(只不过这是cpu0的私有ram,只能cpu0访问),其地址范围可以通过lscript.ld来修改。按道理来说如果数组真的很大,那么是会引起栈溢出的。
1.2、MATLAB图片转换代码
下面这段代码主要是将彩色图片的三通道数据转化为24位的整型数据,其中最高八位为R通道数据,中间八位为G通道数据,低八位为B通道数据。格式为RGB888。
转换好的数据以txt文本的方式保存在MATLAB对应的脚本文件夹下,打开文本,复制数据到提前建立好的数组内部就可以了。
%********************************************************************%
% 程序说明:将三维彩色图片处理成RGB888的格式(R在高八位)
%********************************************************************%
clc;
clear all;
rgbimage=imread('test.jpg/test.bmp');%读取rgb图像,支持多格式图片
[ROW,COL,D]=size(rgbimage); %图片行,列,维度
% RGB通道分离
R=rgbimage(:,:,1); %提取图片中的红色分量
G=rgbimage(:,:,2); %提取图片中的绿色分量
B=rgbimage(:,:,3); %提取图片中的蓝色分量
%存储图片数据(需要强制类型转换为32位,防止溢出)
imgdata=uint32(zeros(1,ROW*COL));%定义一个初值为0的数组,用来存储转换后的图片数据
%转化为RGB888格式
for r=1:ROW
for c=1:COL
imgdata((r-1)*COL+c) = bitshift(uint32(R(r,c)),16) + bitshift(uint32(G(r,c)),8)+ uint32(B(r,c));%移位之前需要对R,G,B的数据类型进行转换,防止溢出
end
end
%打开或生成txt文件,将格式转换完成的数据写入txt文件
fidc=fopen('picture.txt','w+');
%直接输出数组格式的数据{};
for i=1:ROW*COL
if(i == 1)
fprintf(fidc,'{ %d ',imgdata(i));
elseif(i == ROW*COL)
fprintf(fidc,',%d };',imgdata(i));
else
fprintf(fidc,',%d ',imgdata(i));%给了空格
end
end
fclose(fidc);
1.2、C代码
图片数据存储在picture.h文件中,这里应该只用关闭Dcache就可以,关于缓存一致性问题,可参考这篇文章的第一部分内容《【ZYNQ入门】第五篇、AXI HP口读写数据原理-CSDN博客》。
其它没有什么细节要注意了。
/*
* main.c
*
* Created on: 2023年12月30日
* Author: dpt
*/
#include "xparameters.h"
#include "xgpiops.h"
#include "xil_cache.h"//需要关闭cache
#include "sleep.h"
#include "xil_printf.h"
#include "picture.h"//pic data
#define GPIO_DEV_ID XPAR_PS7_GPIO_0_DEVICE_ID
#define RST 54
//图像大小,MATLAB的ROW和COL相反
#define ROW 1000
#define COL 1000
static XGpioPs GpioPs;
static XGpioPs_Config * GpioCnfPtr;
int initGpio();
void wirte_color_band(u32 *srcAddr,u8 count);
int main()
{
u32 *srcAddr = 0x2000000;
u8 count = 0;
//disable cache
Xil_DCacheDisable();//关闭dcache,直接往ddr写
// Xil_ICacheDisable();
initGpio();
XGpioPs_WritePin(&GpioPs,RST,0x00); //RST SET 0
XGpioPs_WritePin(&GpioPs,RST,0x01);//RST SET 1
XGpioPs_WritePin(&GpioPs,RST,0x00); //RST SET 0
while(1)
{
wirte_color_band(srcAddr,count++);
sleep(3);
if(count > 2) count = 0;
}
return 0;
}
//initial gpio func
int initGpio(){
int status;
GpioCnfPtr = XGpioPs_LookupConfig(GPIO_DEV_ID);
status = XGpioPs_CfgInitialize(&GpioPs,GpioCnfPtr,GpioCnfPtr->BaseAddr);
if(status != XST_SUCCESS){
return status;
}
XGpioPs_SetDirectionPin(&GpioPs,RST,0x01);
XGpioPs_SetOutputEnablePin(&GpioPs,RST,0x01);
return status;
}
//往DDR里面写入一个彩条
void wirte_color_band(u32 *srcAddr,u8 count)
{
int k=0;
int row_str = 960 - (ROW>>1);
int row_end = 960 + (ROW>>1);
int col_str = 540 - (COL>>1);
int col_end = 540 + (COL>>1);
for(int j = 0;j<1080;j++)
{
for(int i = 0;i<1920;i++)
{
if(i>=row_str && i<row_end && j>=col_str && j<col_end)
{
if(count == 0)
{
*srcAddr = ikun1[k++];
}
else if(count == 1)
{
*srcAddr = ikun2[k++];
}
else if(count == 2)
{
*srcAddr = ikun3[k++];
}
}
else
{
*srcAddr = 0xffffff;//white
}
srcAddr ++;
}
}
}
1.3、vivado工程链接
关于实验1的完整vivao工程链接如下:实验1工程及参考文件
下载需要积分,没有积分的老铁留下在评论底下留下邮箱也可以🤪😁😉
2、实验二
2.1、实验二原理图
相较于实验一,就多了SD卡模块。ZYNQ先去SD卡读取图片数据,然后再写入到DDR,再让AXI去DDR读取数据,再借助hdmi显示。
2.2、C代码
/*
* sd_card.c
*
* Created on: 2023年12月17日
* Author: dpt
*/
#include "xparameters.h"
#include "xgpiops.h"
#include "xil_cache.h"//需要关闭cache
#include "sleep.h"
#include "xsdps.h"
#include "xil_printf.h"
#include "ff.h"
#include "sd_card.h"
#define GPIO_DEV_ID XPAR_PS7_GPIO_0_DEVICE_ID
#define frame_buffer_addr 0x2000000 //frame buffer的起始地址
#define RST 54
static XGpioPs GpioPs;
static XGpioPs_Config * GpioCnfPtr;
int initGpio();
int main()
{
u8 cnt = 0;
Xil_DCacheDisable(); //disable cache
// Xil_ICacheDisable();
initGpio();
init_sd_card();
XGpioPs_WritePin(&GpioPs,RST,0x00); //CMOS_RST SET 0
XGpioPs_WritePin(&GpioPs,RST,0x01);//CMOS_RST SET 1
XGpioPs_WritePin(&GpioPs,RST,0x00); //CMOS_RST SET 0
while(1)
{
load_sd_bmp((u8 *)frame_buffer_addr,cnt++);//读取图片数据,并且写入到DDR当中
if(cnt > 2) cnt = 0;
sleep(1);
}
return 0;
}
//initial gpio func
int initGpio(){
int status;
GpioCnfPtr = XGpioPs_LookupConfig(GPIO_DEV_ID);
status = XGpioPs_CfgInitialize(&GpioPs,GpioCnfPtr,GpioCnfPtr->BaseAddr);
if(status != XST_SUCCESS){
return status;
}
XGpioPs_SetDirectionPin(&GpioPs,RST,0x01);
XGpioPs_SetOutputEnablePin(&GpioPs,RST,0x01);
return status;
}
2.3、vivado工程链接
关于实验2的完整vivao工程链接如下:实验2的工程和参考文件
下载需要积分和上面一样,没有积分的老铁留下在评论底下留下邮箱即可😇😇😇
第四部分、总结
这两个实验其实差别不大,主要的核心还是HDMI发送端的结构和AXI HP接口驱动。其他的都比较好理解。
我的工程代码仅供参考,最后,希望我的笔记对你的调试有帮助!🤓🤓🤓