之前学习了半年的图像处理,所以计划将自己学过的几个图像处理的基础算法,做过的设计记录下来,在OpenHW论坛上发表,计划是这样的,用VGA做显示,使用PC端上位机通过串口发送一幅图片数据到FPGA开发板,FPGA接收数据并做处理最终发送给VGA显示屏显示,计划要写的算法有彩色图像转灰度、均值/中值滤波、Sobel边缘检测等。那么现在这是第一篇,先来写VGA显示的驱动、以及将一幅图片显示到VGA显示屏上。
第一步是硬件平台的选取,我身边正好有一块Xilinx的ZYNQ开发板,ZYNQ算是Xilinx的一款比较高端的板子了,上面有以太网接口、USB2.0/OTG、HDMI双向接口,SD卡槽,而且板子内部还嵌入了双核ARM Cortex-A9处理器,上面可以跑linux,价格也不菲。ZYNQ是一款全可编程逻辑开发板(All Programmable)。什么是全可编程逻辑开发板,就是可以用硬件描述语言变成,可用C语言、Python等软件编程语言编程。这款开发板有PS(Processor Subsystem)和PL(Programmable Logic)两部分,当然我这里这回用到PL部分资源。通过查阅手册和实验确定ZYNQ在PL(Programmable Logic)模式下,开发板引脚L16连接系统时钟晶振为125Mhz,VGA为16位RGB565显示。
VGA扫描显示其实就是两条线,一个是行扫描,一个是场扫描,在行有效和场有效的时候把数据发送给VGA即可显示了。显示标准就是行分辨率x列分辨率@60hz即一秒屏幕刷新60次,拿640x480@60HZ做例子,即行为640个像素,场为480个像素。
VGA显示其实就是两条线,一个是行扫描,一个是场扫描,在行有效和场有效的时候把数据发送给VGA即可显示了。显示标准就是@60hz即一秒屏幕刷新60次,拿640x480@60HZ做例子,即行为640个像素,场为480个像素。
显示器扫描方式分为逐行扫描和隔行扫描:逐行扫描是扫描从屏幕左上角一点开始,从左像右逐点扫描,每扫描完一行,电子束回到屏幕的左边下一行的起始位置,在这期间,CRT对电子束进行消隐,每行结束时,用行同步信号进行同步;当扫描完所有的行,形成一帧,用场同步信号进行场同步,并使扫描回到屏幕左上方,同时进行场消隐,开始下一帧。隔行扫描是指电子束扫描时每隔一行扫一线,完成一屏后在返回来扫描剩下的线,隔行扫描的显示器闪烁的厉害,会让使用者的眼睛疲劳。
图 – VGA屏幕扫描原理
行消隐(HBlank)在将光信号转换为电信号的扫描过程中,扫描总是从图像的左上角开始,水平向前行进,同时扫描点也以较慢的速率向下移动。当扫描点到达图像右侧边缘时,扫描点快速返回左侧,重新开始在第1行的起点下面进行第2行扫描,行与行之间的返回过程称为水平消隐。一幅完整的图像扫描信号,由水平消隐间隔分开的行信号序列构成,称为一帧。扫描点扫描完一帧后,要从图像的右下角返回到图像的左上角,开始新一帧的扫描,这一时间间隔,叫做垂直消隐,也称场消隐(VBlank)。我们称行同步、场同步。
行场消隐信号:是针对老式显像管的成像扫描电路而言的。电子枪所发出的电子束从屏幕的左上角开始向右扫描,一行扫完需将电子束从右边移回到左边以便扫描第二行。在移动期间就必须有一个信号加到电路上,使得电子束不能发出。不然这个回扫线会破坏屏幕图像的。这个阻止回扫线产生的信号就叫作消隐信号,场信号的消隐也是一个道理。
图 – 行扫描
图 – 场扫描
图 – VGA扫描时序
那么扫描时钟是多少呢?这个其实是可以计算出来的,根据一帧(即扫描一次屏幕需要60分之1秒)需要的时间可以计算出每一个像素点需要的时间。我这里就不计算了直接用官方给出的数据就行了,我这里直接给出官方手册,可以自己查阅。如图所示的一些参数和其几种显示格式的显示参数还有RGB的几种颜色的显示组合,我都会给出来,
我这里选用的是640X480@60HZ分辨率显示,ZYBO的板子晶振是125Mhz,所以首先要先进行分频,我这里直接用Vivado调用PLL。还没有过用Vivado调用PLL的博客,现在总结一下吧,以防后面忘记。
为了快速找到想要的IP,在右上角直接搜索Clocking,然后就会出现如图所示界面,双击clocking wizard,进行PLL的配置
弹出如图所示界面后,一次按上图进行配置,在component name中写下IP的名字,把show disabled ports勾选为空,在primitive中选择MMCM,在input clock information中选择输入时钟。这里的MMCM我查了一下就是他包含了PLL,所以我们直接选择MMCM。但是有时候如果选择PLL也会出现错误,所以一般首选MMCM。
选择复位为低电平触发、选择locked
这里可以自定义端口名
里面的内容就是可以直接实例化调用的端口生成。
这样便完成了VGA驱动时钟的生成,这里我把其他几种显示格式的参数和RGB显示的参数贴出来,这样便省去了不少事情。然后就是把你要显示的数据在行扫描有效和场扫描有效的时候输出,即可在显示屏显示了。其实这个显示并不难,那我为什么要专门写篇博客记录呢?因为以前老是看别人的代码,看的多了,自己的能力还是没有多大提升,但这部分显示图片是完全我自己分析时序显示出来的,所以有很大的成就感。学习这个东西还是得有人教导,从我学习FPGA以来,都是自己一个人瞎琢么,学习了一年了还没有一个好的画时序图的习惯,到仿真的时候再凑出时序图,还是要从基础抓起,不能只会敲代码。时序设计是关键,设计思想必须有。
要显示一个彩色条纹也是比较简单的,例如在前一百五十行给VGA的数据为白色的,中间180行给VGA的数据为绿色,后面相同的方法。我这里要做的实验是在显示屏的左上角开一个200x200的框,最后将一幅200x200的图片显示进去,首先开一个200x200的正方形框,实现的方法就是控制行有效计数器计到需要显示正方形框的时候紫色的数据给lcd_data,行计数器为0-200,其他情况还是前面显示条纹的数据,同样的场有效计数器也从0-200计数时显示紫色数据带lcd_data,这样就会在显示屏左上角形成一个正方形的框。如下图所示。
要显示一张图片到VGA就需要调用RAM IP Core,我这里需要调用一个单口RAM IP,使用软件将图片生成十六进制的数据,可以用MATLAB,我这里推荐一款软件,是一个业界名人写的十分好用,如下图所示,只需要将8位或24位位图加载进去,就可以生成你想要的图像数据格式了,这里要注意生成的图像数据RGB的位宽,一定要和你板子的VGA显示位宽一致,否则显示不出来的。
使用这个软件转化之前,除了阅读板子手册看你的板子RAM的容量能装下多大的图像数据,需要找一张合适的图片,这里小提示一下,可以用windows自带的画图软件将图片打开,右键重新调整大小去掉保持纵横比,选择像素,这样就可以任意修改图像大小了!RAM配置可参考我前面的相关博文:基于Vivado调用ROM IP core设计DDS
注:Xilinx是.coe文件、Intel (Altera)是.mif文件,这两种文件只是格式不同但本质上是一样的。
RAM/ROM读取有延时,要在扫描第一个点的前两个时钟周期读取RAM/ROM,我这里用的是双口RAM,在Vivado这里显示的是有两个时钟的周期的延时,也就是当你给读命令时,RAM会把读出来的数据缓存两级在才会输出给你想给数据的地方。
我这里将RAM读取设置为always enable, RAM数据出来的端口是一直有数据的,而且我最终显示出来的静态图片也是完整的,没有观察到像素点的缺失,所以先不管这两个时钟周期的延时。
这次的基于FPGA的图像显示部分代码,完全是我自主设计,虽然不是很高的技术含量,但是总算摆脱了抄别人代码的魔咒了,最后所有工作都做好后,下载板子就会呈现下面这一幅图的样子,这样看来显示效果还挺不错的呢!(lena美女很漂亮,这里看不出是200x200的图片是因为,显示器自动调节合适的显示宽度)
这次的基于FPGA的图像显示部分代码,完全是我自主设计,虽然不是很高的技术含量,但是总算摆脱了抄别人代码的魔咒了,最后所有工作都做好后,下载板子就会呈现下面这一幅图的样子,这样看来显示效果还挺不错的呢!
如果你想获得本文的所有课件和工程代码,请关注本人的个人微信订阅号:开源FPGANingHeChuan或扫描下方二维码关注订阅号,在后台回复图像处理,即可获得本文的所有课件、资料、和工程源码哦!
转载请注明出处:NingHeChuan(宁河川)
个人微信订阅号:开源FPGANingHeChuan
如果你想及时收到个人撰写的博文推送,可以扫描左边二维码(或者长按识别二维码)关注个人微信订阅号
知乎ID:NingHeChuan
微博ID:NingHeChuan
原文地址:http://www.cnblogs.com/ninghechuan/p/7260383.html