写在前面(VGA驱动模块将SDRAM中缓存的图片信息以VGA时序输出从而在显示器上显示图片) 基于SDRAM缓存的串口传图综合实战(UART + SDRAM + VGA) 本文是SDRAM系列文章的第九篇,前面八篇已经实现了一个简单的SDRAM控制器。正所谓光说不练云玩家,接下来我们搞搞实战,真正把SDRAM给用起来。 本文将结合UART模块、VGA模块、SDRAM模块(含PLL、FIFO)来做一个基于SDRAM缓存的串口传图实验,实现UART发送数据、SDRAM缓存数据、VGA显示数据这一过程。 其他博文链接: 1、总体架构期待实现的功能:在PC端使用串口助手发送一幅分辨率为 640*480 的图片数据给 FPGA,FPGA 以外接 SDRAM 做缓存,将接收到的图片数据通过 VGA 显示器显示出来。 总体架构示意图如下:
这工程一看好像还挺多挺复杂的,那么我接下来就对整个图片的传送显示流程做一个讲解:
2、设计实现接下里就一步一步讲解具体的设计实现了。 2.1、图片预处理图片格式:640 * 480。 图片内容:老婆。 显然我们无法直接通过串口发送图片,需要将图片转化成RGB565的像素值(其他RGB格式不讨论)才能使用VGA接口显示。我们可以先使用MATLAB将图片转换成RGB565,代码如下: %--------------------------------------------------------------------------%-- 图片数据转换:1个像素转换成2个 8bit hex 数据%--------------------------------------------------------------------------clear all;RGB24 = imread('laopo.bmp'); %读取图片文件fid = fopen('rgb565.txt','w+'); %打开文件[ROW,COL,N] = size(RGB24); %获得图片尺寸[高度,长度,维度]for i = 1:ROWfor j = 1:COLRG = bitand(RGB24(i,j,1),248) + bitshift(RGB24(i,j,2),-5); %{R[7:3],3'd0} + {5'd0,G[7:5]}G = bitand(RGB24(i,j,2),28); %{3'd0,G[4:2],2'd0}GB = bitshift( G,3) + bitshift(RGB24(i,j,3),-3); %{G[4:2],5'd0} + {3'd0,B[7:3]}fprintf(fid,'%02x %02x ',RG,GB);%将字符打印到txt文件中endend 2.2、子模块接下来介绍各个子模块的功能及实现方式。这几个子模块,都是我之前的文章写过的模块,所以等下不会介绍的很详细,会贴出链接,感兴趣的朋友可以自己去看看。 2.2.1、串口接收模块由于我们在PC端使用UART来发送图片信息,而UART是串行协议,所以我们需要一个模块来将接收到的串行数据转为并行数据。 串口模块将PC端通过UART接口(含协议)发送过来的图片的像素信息(RGB565格式)接收,并转化为8bit并行数据。串口接收模块的波特率在本次实验中选为115200。 供参考:串口(UART)的FPGA实现 2.2.2、数据拼接模块由于目前的主流串口发送软件很少能支持16bit数据的发送,只能将16bit的像素信息拆成2个8bit数据来发送。所以需要一个模块将分别接收的2个8bit数据合并成1个16bit数据(完整的RGB565)。 数据拼接模块将串口接收模块发送的两个8bit数据转换成16bit数据。 供参考:一个简单方便的数据拼接模块(支持任意位宽、任意整数倍) 2.2.3、SDRAM控制模块SDRAM控制模块可以将接收到串口发送的图片信息暂存到SDRAM芯片,同时显示器也在不停地通过VGA接口对SDRAM控制模块进行操作以读取SDRAM中存取的图片信息。在640*480@60Hz时序下的VGA接口,一幅图片的大小约为640*480*16bit /8 /1024 = 600KB,这个大小显然无法使用FPGA的片内资源来缓存,所以选择SDRAM这种大容量器件来作为接收的缓存。 供参考:相信我,SDRAM真的不难----汇总篇(电梯直达) 2.2.4、VGA驱动模块VGA驱动模块将SDRAM中缓存的图片信息以VGA时序输出,从而在显示器上显示图片。 供参考:如何用VGA接口乳法? 2.2.5、PLL模块使用PLL模块统一生成时钟,有利于统一管理时钟,约束时序。各模块需要用到的时钟如下:
2.3、顶层模块顶层模块主要实现化上述几个子模块,并设定一些参数:
顶层模块完整代码如下: module pic_uart_sdram #( parameter BPS = 115200 //发送波特率) ( input sys_clk , //50M系统时钟 input sys_rst_n , //系统复位 input uart_rxd , //串口接收 output h_sync , //VGA行同步output v_sync , //VGA场同步 output [15:0] rgb_data , //在VGA时序下的RGB数据//SDRAM相关信号output sdram_clk_out ,output sdram_cke ,output sdram_cs_n ,output sdram_ras_n ,output sdram_cas_n ,output sdram_we_n ,output [1:0] sdram_bank ,output [12:0] sdram_addr ,output [1:0] sdram_dqm ,inout [15:0] sdram_dq );localparam CLK_FRE = 50_000_000 ; //输入时钟频率localparam H_PIXEL = 24'd640 ; //水平方向像素个数,用于设置 SDRAM 缓存大小localparam V_PIXEL = 24'd480 ; //垂直方向像素个数,用于设置 SDRAM 缓存大小//8bit转16bit模块 localparam integer DATA_WIDTH = 8 ; //输入数据位宽localparam integer MERGE_STAGE = 2 ; //合并级数,如2则将两个数据合并为一个 //PLL wire rst_n ; //低电平有效的复位信号,除PLL模块外均使用该信号wire locked ; //PLL输出稳定信号,高电平有效wire pll_50M ; //用于串口模块wire pll_100M ; //用于SDRAMwire pll_100M_shift ; //用于SDRAMwire pll_25M ; //用于25M//uart wire [7:0] rx_data; //接收到的串口数据wire rx_flag; //串口数据有效//VGA wire pix_data_req; //RGB数据请求信号,用作SDRAM的读请求wire [15:0] sdram_rd_data; //从SDRAM中读出的数据//merge wire [15:0] data_16bit; //8bit转16bit后的数据wire data_16bit_valid; //16bit数据有效信号assign rst_n = sys_rst_n & locked; //PLL未稳定视为复位状态//例化PLLclk_gen clk_gen_inst ( .areset (~sys_rst_n ), // .inclk0 (sys_clk ), //50M输入 .c0 (pll_50M ), //50M .c1 (pll_100M ), //100M .c2 (pll_100M_shift ), //100M_SHIFT .c3 (pll_25M ), //25M .locked (locked ) //PLL输出稳定信号,高电平有效); //例化VGA驱动模块(640*480@60,时钟25M) vag_driver vag_driver_inst( .vga_clk (pll_25M ), //VGA时钟 .sys_rst_n (rst_n ), //复位信号、低电平有效 .pixel_data (sdram_rd_data ), //输入RGB数据 .h_sync (h_sync ), //行同步信号 .v_sync (v_sync ), //场同步信号 .pixel_x ( ), //行坐标,不需要使用 .pixel_y ( ), //场坐标,不需要使用 .pix_data_req (pix_data_req ), //RGB数据请求信号,比rgb_valid提前一拍 .rgb_data (rgb_data ) //在VGA时序下的RGB数据);//例化8bit转16bit模块merge #( .DATA_WIDTH (DATA_WIDTH ), //输入数据位宽 .MERGE_STAGE (MERGE_STAGE ) //合并级数,如2则将两个数据合并为一个。 ) merge_inst( .sys_clk (pll_50M ), //50M系统时钟 .sys_rst_n (rst_n ), //系统复位 .data_in (rx_data ), .data_in_valid (rx_flag ), .data_out (data_16bit ), .data_out_valid (data_16bit_valid ));//例化串口接收模块uart_rx#( .BPS (BPS ), //发送波特率 .CLK_FRE (CLK_FRE ) //输入时钟频率) uart_rx_inst( .sys_clk (pll_50M ), //50M系统时钟 .sys_rst_n (rst_n ), //系统复位 .uart_rxd (uart_rxd ), //接收数据线 .uart_rx_done (rx_flag ), //数据接收完成标志,当其为高电平时,代表接收数据有效 .uart_rx_data (rx_data ) //接收到的数据,在uart_rx_done为高电平时有效);//例化SDRAM控制模块sdram_top sdram_top_inst(.sdram_clk (pll_100M ), //sdram时钟.sdram_rst_n (rst_n ), //sdram复位信号 .clk_out (pll_100M_shift ), //sdram相位偏移时钟(直接给SDRAM芯片)//写FIFO信号 .wr_fifo_rst (~rst_n ), //写FIFO复位信号.wr_fifo_wr_clk (pll_50M ), //写FIFO写时钟 .wr_fifo_wr_req (data_16bit_valid ), //写FIFO写请求.wr_fifo_wr_data (data_16bit ), //写FIFO写数据.sdram_wr_b_addr (0 ), //写SDRAM首地址.sdram_wr_e_addr (H_PIXEL*V_PIXEL ), //写SDRAM末地址.wr_burst_len (256 ), //写SDRAM数据突发长度.wr_fifo_num ( ), //写fifo中的数据量 //读FIFO信号 .rd_fifo_rd_clk (pll_25M ), //读FIFO读时钟,与VGA时钟一致.rd_fifo_rst (~rst_n ), //读复位信号 .rd_fifo_rd_req (pix_data_req ), //读FIFO读请求.sdram_rd_b_addr (0 ), //读SDRAM首地址.sdram_rd_e_addr (H_PIXEL*V_PIXEL ), //读SDRAM末地址.rd_burst_len (256 ), //读SDRAM数据突发长度.rd_fifo_rd_data (sdram_rd_data ), //读FIFO读数据,用于VGA显示的数据.rd_fifo_num ( ), //读fifo中的数据量//功能信号 .read_valid (1'b1 ), //SDRAM读使能.init_end ( ), //SDRAM初始化完成标志//SDRAM接口信号 .sdram_clk_out (sdram_clk_out ), //SDRAM芯片时钟.sdram_cke (sdram_cke ), //SDRAM时钟有效信号.sdram_cs_n (sdram_cs_n ), //SDRAM片选信号.sdram_ras_n (sdram_ras_n ), //SDRAM行地址选通脉冲.sdram_cas_n (sdram_cas_n ), //SDRAM列地址选通脉冲.sdram_we_n (sdram_we_n ), //SDRAM写允许位.sdram_bank (sdram_bank ), //SDRAM的L-Bank地址线.sdram_addr (sdram_addr ), //SDRAM地址总线.sdram_dqm (sdram_dqm ), //SDRAM数据掩码.sdram_dq (sdram_dq ) //SDRAM数据总线);endmodule 3、仿真略。因为VGA的时序如果不改动参数仿真,那么时间就太长了;改了吧,看起来意义又不大。干脆不仿真了,直接看现象吧。 4、上板验证验证环境:
需要注意的是因为串口的发送速度太慢了,而VGA读取的速度又相对较快,所以看起来,图片是一行、一行的显示出来的。实验结果如下: 5、其他
版本信息文件:V1.0 编号:69 Vivado:无 Modelsim:无 Quartus II:Quartus II 13.1 (64-bit) |
|
来自: 山峰云绕 > 《电子工程与单片机io字符显示技术》