十四、TFT屏显示图片
本文由杭电网友曾凯峰贡献,特此感谢
学习了小梅哥的TFT显示屏驱动设计后,想着在此基础上通过TFT屏显示一张图片,有了这个想法就开始动工了。首先想到是利用FPGA内部ROM存储图片数据,然后通过控制读取数据地址将图片数据传给TFT驱动模块,从而将每个图片数据显示在对应的像素点上。整个设计的框图如下:
主要是在小梅哥TFT驱动设计基础上增加了图片数据发送控制模块Imgdata_send,该模块包括存储图片数据的rom,和一些简单的逻辑控制。具体的rom IP核的建立我这里就不说了,不懂的可以参看小梅哥的相关内容的视频,我这里主要讲如何将图片转换成mif文件。这里有两种方法,可以作为参考,主要用到如下软件:
步骤1:利用Img2Lcd将图片转化为BMP格式的(当然图片本身为BMP格式就不需要转了,直接进入步骤2);
步骤2:利用BMP2Mif可将图片转化为mif文件。
具体实现如下:
步骤1:先打开Img2Lcd打开一张图片,选择输出格式为BMP格式,输出灰度选择24位真彩色(由于BMP2Mif软件只能载入24位或8位的,所以这里就没有直接选16位的真彩色),最大宽度和高度根据图片实际的大小进行选择的,由于内部rom能存储的数据量有限,我选择了一张像素为160*120的图片如果想显示大的图片,如480*272图片,用这种方法就不能实现(看到这里有人想,如果想显示大点的图片那应该怎么解决,后面我会有其他方法来实现TFT屏保显示)。
步骤2:打开BMP2Mif软件,加载刚转换输出的24位BMP格式图片,选择输出图像格式和文件类型,点击一键转换便将图片转换为了mif文件了,可以将mif文件名更改下以区别不同图片mif文件。
可以用Notepad++将转化的mif文件打开看看,截取部分图如下:
rom IP核设置完成后就是数据发送控制模块Imgdata_send中控制逻辑的编写,主要是让图片显示在屏幕指定的地方,这就需要根据TFT_CTRL模块的TFT行和场扫描计数器输出信号来控制rom的数据地址,从而控制TFT_CTRL的待显示数据data_in。具体代码如下:
1 module Imgdata_send( 2 clk50M, 3 rst_n, 4 tft_de, 5 hcount, 6 vcount, 7 data_in 8 ); 9 10 input clk50M; //系统时钟,默认50M 11 input rst_n; //复位信号,低电平有效 12 input tft_de; //TFT数据使能 13 input [9:0]hcount; //TFT行扫描计数器 14 input [9:0]vcount; //TFT场扫描计数器 15 output [15:0]data_in; //待显示图片数据 16 17 wire img_ack; //图片数据使能 18 19 localparam IMG_H = 160, //图片行像素点个数 20 IMG_V = 120; //图片场像素点个数 21 22 localparam TFT_H = 480, //TFT屏行像素点个数 23 TFT_V = 272; //TFT屏场像素点个数 24 25 localparam IMG_HM = TFT_H - IMG_H, //图片行方向可移动像素点个数 26 IMG_VM = TFT_V - IMG_V; //图片场方向可移动像素点个数 27 28 reg [9:0]img_hbegin = 0; //图片左上角第一个像素点在TFT屏的行向坐标 29 reg [9:0]img_vbegin = 0; //图片左上角第一个像素点在TFT屏的场向坐标 30 31 reg [14:0]addr; //读图片数据rom地址 32 wire [15:0]img_data; //读出图片数据 33 34 rom u4_rom( 35 .address(addr), 36 .clock(clk50M), 37 .q(img_data) 38 ); 39 40 assign img_ack = tft_de && (hcount >= img_hbegin && hcount < img_hbegin + IMG_H) && 41 (vcount >= img_vbegin && vcount < img_vbegin + IMG_V)?1'b1:1'b0; 42 43 always@(posedge clk50M or negedge rst_n) 44 begin 45 if(!rst_n) 46 addr <= 15'd0; 47 else if(img_ack) 48 addr <= (hcount - img_hbegin) + (vcount - img_vbegin)*IMG_H; 49 else 50 addr <= 15'd0; 51 end 52 53 assign data_in = img_ack ? img_data : 16'h0; 54 55 endmodule
接下来就是仿真验证,利用已有的TFT_CTRL模块的hcount、vcount、tft_de作为Imgdata_send模块的输出进行仿真验证,代码如下:
1 `timescale 1ns/1ns 2 `define PERIOD_CLK 20 3 4 module Imgdata_send_tb; 5 6 reg clk50M; 7 reg rst_n; 8 wire tft_de; 9 wire [9:0]hcount; 10 wire [9:0]vcount; 11 wire [15:0]data_in; 12 13 wire clk9M; 14 15 Imgdata_send u0_Imgdata_send( 16 .clk50M(clk50M), 17 .rst_n(rst_n), 18 .tft_de(tft_de), 19 .hcount(hcount), 20 .vcount(vcount), 21 .data_in(data_in) 22 ); 23 24 pll u1_pll( 25 .areset(!rst_n), 26 .inclk0(clk50M), 27 .c0(clk9M) 28 ); 29 30 TFT_CTRL u2_TFT_CTRL( 31 .clk9M(clk9M), 32 .rst_n(rst_n), 33 .data_in(), 34 .hcount(hcount), 35 .vcount(vcount), 36 .tft_rgb(), 37 .tft_hs(), 38 .tft_vs(), 39 .tft_clk(), 40 .tft_de(tft_de), 41 .tft_pwm() 42 ); 43 44 initial clk50M = 1'b1; 45 always #(`PERIOD_CLK/2) clk50M = ~clk50M; 46 47 initial 48 begin 49 rst_n = 1'b0; 50 #(`PERIOD_CLK*200+1) 51 rst_n = 1'b1; 52 end 53 54 endmodule
仿真验证的波形图如下:
从仿真结果可以看出,图片数据是在我们指定的区域输出的。该模块仿真验证正确后,进行顶层电路文件的设计。顶层文件编写如下:
1 module rom_tft_img( 2 3 clk50M, 4 rst_n, 5 6 tft_rgb, 7 tft_hs, 8 tft_vs, 9 tft_clk, 10 tft_de, 11 tft_pwm 12 ); 13 14 input clk50M; 15 input rst_n; 16 17 output [15:0]tft_rgb; 18 output tft_hs; 19 output tft_vs; 20 output tft_clk; 21 output tft_de; 22 output tft_pwm; 23 24 wire [15:0]data_in; 25 wire [9:0]hcount; 26 wire [9:0]vcount; 27 wire clk9M; 28 29 Imgdata_send u0_Imgdata_send( 30 .clk50M(clk50M), 31 .rst_n(rst_n), 32 .tft_de(tft_de), 33 .hcount(hcount), 34 .vcount(vcount), 35 .data_in(data_in) 36 ); 37 38 pll u1_pll( 39 .areset(!rst_n), 40 .inclk0(clk50M), 41 .c0(clk9M) 42 ); 43 44 TFT_CTRL u2_TFT_CTRL( 45 .clk9M(clk9M), 46 .rst_n(rst_n), 47 .data_in(data_in), 48 .hcount(hcount), 49 .vcount(vcount), 50 .tft_rgb(tft_rgb), 51 .tft_hs(tft_hs), 52 .tft_vs(tft_vs), 53 .tft_clk(tft_clk), 54 .tft_de(tft_de), 55 .tft_pwm(tft_pwm) 56 ); 57 58 endmodule
生成的顶层电路图如下:
接下来就是仿真验证,仿真验证程序如下:
1 `timescale 1ns/1ps 2 `define PERIOD_CLK 20 3 4 module rom_tft_img_tb; 5 //模块输入端口 6 reg clk50M; 7 reg rst_n; 8 9 //模块输出端口 10 wire [15:0]tft_rgb; 11 wire tft_hs; 12 wire tft_vs; 13 wire tft_clk; 14 wire tft_de; 15 wire tft_pwm; 16 17 reg [7:0]v_cnt = 0; //扫描帧数统计计数器 18 19 rom_tft_img rom_tft_img( 20 21 .clk50M(clk50M), 22 .rst_n(rst_n), 23 24 .tft_rgb(tft_rgb), 25 .tft_hs(tft_hs), 26 .tft_vs(tft_vs), 27 .tft_clk(tft_clk), 28 .tft_de(tft_de), 29 .tft_pwm(tft_pwm) 30 ); 31 32 initial clk50M = 1'b1; 33 always #(`PERIOD_CLK/2) clk50M = ~clk50M; 34 35 initial 36 begin 37 rst_n = 1'b0; 38 #(`PERIOD_CLK*200 + 1); 39 rst_n = 1'b1; 40 end 41 42 initial 43 begin 44 wait(v_cnt == 3); //等待扫描2帧后结束仿真 45 $stop; 46 end 47 48 always@(posedge tft_vs) //统计总扫描帧数 49 v_cnt = v_cnt + 1'b1; 50 51 endmodule
仿真波形如下:
从波形可以看出,图片数据在指定区域显示。接下来就是板级验证,引脚分配参照芯航线FPGA学习套件引脚分配表进行分配,然后布局布线,板级程序下载最后实现的效果图如下:
我们设置的是显示在屏幕的左上角,与预期效果是一致的,想要改变图片的位置,可以更改Imgdata_send模块的28、29行代码:
28 reg [9:0]img_hbegin = 0; //图片左上角第一个像素点在TFT屏的行向坐标 29 reg [9:0]img_vbegin = 0; //图片左上角第一个像素点在TFT屏的场向坐标
将上面的(0、0)更改为其他的数,图片位置就会改变,如果想让图片在屏幕上自动的移动,可以自己设置一种路径让img_hbegin、img_vbegin的值按你的路径变化就可实现图片的自动移动,读者可以自己改进学习。
如有更多问题,欢迎加入芯航线 FPGA 技术支持群交流学习:472607506
小梅哥
芯航线电子工作室
关于学习资料,小梅哥系列所有能够开放的资料和更新(包括视频教程,程序代码,教程文档,工具软件,开发板资料)都会发布在我的云分享。(记得订阅)链接:http://yun.baidu.com/share/home?uk=402885837&view=share#category/type=0
赠送芯航线AC6102型开发板配套资料预览版下载链接:链接:http://pan.baidu.com/s/1slW2Ojj 密码:9fn3
赠送SOPC公开课链接和FPGA进阶视频教程。链接:http://pan.baidu.com/s/1bEzaFW 密码:rsyh