【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

时间:2023-03-09 09:56:47
【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示
十五、串口发送图片数据到SRAM在TFT屏上显示

之前分享过rom存储图片数据在TFT屏上显示,该方法只能显示小点的图片,如果想显示TFT屏幕大小的图片上述方法rom内存大小不够。小梅哥给了个方案,利用串口将图片数据传给SRAM,传完后在从SRAM中读取图片数据进行显示。有了梅哥的提示后就开始动工了,首先是设计SRAM的控制程序。

SRAM(静态随机访问存储器)是一种半导体存储器。“静态”一词表明只要有电源供电,数据就会保存,而不会“动态”改变。

本实验平台是基于小梅哥出品的芯航线FPGA开发平台,该平台的SRAM芯片采用的是ISSI的IS61LV25616,它是一个256K*16位字长的高速率静态随机存取存储器。

通过查阅手册得知,除了地址总线和数据总线外,该芯片还包含五个控制信号(手册上的符号与这个有差别,手册是符号上一横线代表低电平有效)。

ce_n(芯片使能或芯片选择):禁止或使能芯片。

we_n(写使能):禁止或使能写操作。

oe_n(输出使能):禁止或使能输出。

lb_n(低字节使能):禁止或使能数据总线的低字节。

ub_n(高字节使能):禁止或使能数据总线的高字节。

所有这些信号都是低电平有效,后缀_n用于强调这一特性。功能表如表1所示:信号ce_n用于存储器扩展,信号we_n和oe_n用于写操作和读操作,lb_n和ub_n用于字节配置。

表1 SRAM控制信号的真值表

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

接下来分析SRAM的读写时序图,两种类型的读操作时序如图1(a)和图1(b)所示

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

(a)地址控制的读周期时序图(ce_n=0,we_n=1,oe_n=0)

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

(b)oe_n控制的读周期时序图

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

(c)部分时序参数的介绍

图1 读操作的时序图和部分参数

本实验数据用的是16位,所以lb_n和ub_n控制位我们一直给低电平即可。关于ce_n控制位在复位后一直给低电平即可。

芯片手册上关于写操作时序有四种类型,这里就简单介绍其中一种,其他的类似,写操作时序如图2所示:

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

(a)写操作时序图

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

(b)部分时序参数的介绍

图2 读操作的时序图和部分参数

根据上面的读操作和写操作时序,结合小梅哥的芯航线开发平台,取读写周期为20ns,这样可以直接采用平台已有的50Mhz的时钟,根据上面的时间限制,在读操作时,可以在使能读操作后,采用在时钟上升沿时改变地址,这样在下个时钟上升沿到来时就可以读取该地址的数据,也就是数据相对与给的地址是有一个时钟周期的延时。在写操作时,同样也是在时钟的上升沿给地址和待写入的数据,这样可以满足参数的时间要求。

SRAM控制器的设计如下:

   module sram_ctrl(
clk50M,
rst_n,
address,
chipselect_n,
read_n,
write_n,
byteenable_n,
writedata,
readdata, sram_addr,
sram_dq,
sram_ce_n,
sram_oe_n,
sram_we_n,
sram_lb_n,
sram_ub_n
); input clk50M; //系统时钟,默认50M
input rst_n; //异步复位,低电平有效 input [:] address; //数据传输地址
input chipselect_n; //SRAM片选信号,低电平有效
input read_n; //数据读控制信号,低电平有效
input write_n; //数据写控制信号,低电平有效
input [:]byteenable_n;//数据高低字节使能,低电平有效
input [:]writedata; //待写入RAM的数据
output [:]readdata; //读RAM的数据 output [:]sram_addr; //操作RAM数据的地址
inout [:]sram_dq; //RAM的数据端口
output sram_ce_n; //SRAM片选信号,低电平有效
output sram_oe_n; //SRAM读数据控制信号,低电平有效
output sram_we_n; //SRAM写数据控制信号,低电平有效
output sram_lb_n; //数据低字节有效
output sram_ub_n; //数据高字节有效 //signal declaration
reg [:]addr_reg;
reg [:]rdata_reg, wdata_reg;
reg ce_n_reg, lb_n_reg, ub_n_reg, oe_n_reg, we_n_reg; //body
//registers
always@(posedge clk50M or negedge rst_n)
begin
if(!rst_n)
begin
addr_reg <= 'd0;
rdata_reg <= 'd0;
wdata_reg <= 'd0;
ce_n_reg <= 'b1;
oe_n_reg <= 'b1;
we_n_reg <= 'b1;
lb_n_reg <= 'b1;
ub_n_reg <= 'b1;
end
else
begin
addr_reg <= address;
rdata_reg <= sram_dq;
wdata_reg <= writedata;
ce_n_reg <= chipselect_n;
oe_n_reg <= read_n;
we_n_reg <= write_n;
lb_n_reg <= byteenable_n[];
ub_n_reg <= byteenable_n[];
end
end //to fpga interface
assign readdata = rdata_reg; //to SRAM
assign sram_addr = addr_reg;
assign sram_ce_n = ce_n_reg;
assign sram_oe_n = oe_n_reg;
assign sram_we_n = we_n_reg;
assign sram_ub_n = ub_n_reg;
assign sram_lb_n = lb_n_reg;
//SRAM tristate data bus
assign sram_dq = (~we_n_reg)?wdata_reg:'bz; endmodule

SRAM的数据线是输出输入数据共用的,要将其设计成三态门形式,具体如代码84行所示。接下就是编写tb文件来验证驱动程序,代码如下:

   `timescale 1ns/1ns
`define PERIOD_CLK module sram_tb;
reg clk50M;
reg rst_n; reg [:]address;
reg read_n;
reg write_n; reg [:]writedata;
wire [:]readdata; wire [:]sram_addr;
wire [:]sram_dq;
wire sram_ce_n;
wire sram_oe_n;
wire sram_we_n;
wire sram_lb_n;
wire sram_ub_n; integer i; sram_ctrl sram_ctrl_u0(
.clk50M(clk50M),
.rst_n(rst_n),
.address(address),
.chipselect_n('b0),
.read_n(read_n),
.write_n(write_n),
.byteenable_n('b00),
.writedata(writedata),
.readdata(readdata), .sram_addr(sram_addr),
.sram_dq(sram_dq),
.sram_ce_n(sram_ce_n),
.sram_oe_n(sram_oe_n),
.sram_we_n(sram_we_n),
.sram_lb_n(sram_lb_n),
.sram_ub_n(sram_ub_n)
); initial clk50M = 'b1;
always #(`PERIOD_CLK/) clk50M = ~clk50M; initial
begin
rst_n = 'b0;
read_n = 'b1;
address = ;
write_n = 'b1;
writedata = 'h0;
#(`PERIOD_CLK* + )
rst_n = 'b1; write_n = 'b0;
for(i=; i<; i=i+)
begin
#(`PERIOD_CLK);
address = address + ;
writedata = writedata + ;
end
write_n = 'b1;
#(`PERIOD_CLK*); #;
address = ;
read_n = 'b0;
for(i=; i<; i=i+)
begin
#(`PERIOD_CLK);
address = address + ;
end
read_n = 'b1;
#(`PERIOD_CLK*); #;
$stop;
end endmodule

仿真结果如下:

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

写操作控制信号放大后波形如下:

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

读操作控制信号放大后波形如下:

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

这里需要说明一下,就是读操作读出的数据没有值,主要是没有真正的接SRAM,还没想到怎么去验证读数据,但是仿真结果可以看出,读写时序与按预期设计的一致。如果想进一步进行板级验证,也是可以的,这就需要使用SignalTap II Logic Analyzer工具对写入的数据和读取的数据进行抓取和比较,从而判断控制驱动设计的对错,具体的操作后面会提到。关于SRAM的控制驱动就说这么多,其他的可以参考芯片手册做更进一步的设计,本人经验不足,还望前辈们批评指正。

接下来还是进入今天的主题,就是通过串口的传图片数据到SRAM,然后通过读取SRAM的图片数据在tft上显示完整的图片,主要是解决上次通过读rom数据显示图片不能显示整个tft屏的问题。主要的设计框图如下:

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

框图中除了UART2SRAM模块是没有设计的,其余模块都已经进行了设计和验证,串口接收模块和tft屏的驱动参考的小梅哥教程里的。UART2SRAM模块主要有两个功能一个是将串口接收来的8位的数据每两个合成一个16位的数据传给writedata,还有一个是向SARM里写入数据和读取数据。数据的合成首先对串口接收模块的输出数据进行一个计数,然后通过计数器的数将每两个8位合成一个16位的数据,也就是个数为偶数时进行一次合成。具体代码如下:

      //串口数据个数计数器
reg [:]data_cnt;
always@(posedge clk50M or negedge rst_n)
begin
if(!rst_n)
data_cnt <= 'd0;
else if(ctrl_state)
data_cnt <= 'd0;
else if(data8bit_en)
data_cnt <= data_cnt + 'd1;
else
data_cnt <= data_cnt;
end //2个8位串口合成一个16位数据
//step1:将接收的串口数据存储起来
reg [:]r1_data8bit;
//reg [7:0]r2_data8bit;
always@(posedge clk50M or negedge rst_n)
begin
if(!rst_n)
begin
r1_data8bit <= 'd0;
end
else
begin
r1_data8bit <= data8bit;
end
end //step2:产生数据合成标志位,即将data8bit_en延时1个周期的信号
reg r1_data8bit_en;
always@(posedge clk50M or negedge rst_n)
begin
if(!rst_n)
begin
r1_data8bit_en <= 'b0;
end
else
begin
r1_data8bit_en <= data8bit_en;
end
end //step3:数据合成
reg [:] data16bit;
always@(posedge clk50M or negedge rst_n)
begin
if(!rst_n)
data16bit <= 'd0;
else if(r1_data8bit_en && data_cnt[]==)
data16bit <= {r1_data8bit,data8bit};
else
data16bit <= data16bit;
end

这个代码根据串口接收模块的不同稍有差别,主要是是看你设计的串口接收模块接收完成标志位,输出数据的时序关系,大概有两种不同的时序,如下图所示:

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

本实验串口接收模块的时序是右边的图,如果是左边的时序图,上述代码需要做相应的修改,主要是产生合成数据标志位有所变化,此时标志位就直接为data8bit,不用延时一时钟周期,具体时序如下图所示:

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

两种不同的时序稍有差别,总的思路是一样的,具体实现可根据实际的情况而定。

接下来就是向SARM写入数据和读取数据,本实验是先将合成的16位的数据写入SRAM,然后再通过读取SRAM数据进行图片的显示。写入数据主要是写控制位ce_n和地址的控制,本实验没有加入按键等外部的控制,写控制就直接从接收串口数据开始,图片数据接收完成截止。具体代码如下:

//一帧图片数据传输完成标志
always@(posedge clk50M or negedge rst_n)
begin
if(!rst_n)
rx_img_done <= 'b0;
else if(r1_data8bit_en && data_cnt == rx_data_cnt_max)
rx_img_done <= 'b1;
else
rx_img_done <= 'b0;
end //写数据控制
always@(posedge clk50M or negedge rst_n)
begin
if(!rst_n)
write_n <= 'b1;
else if(rx_img_done)
write_n <= 'b1;
else if(data_cnt > && r1_data8bit_en)
write_n <= 'b0;
else
write_n <= write_n;
end

写入数据地址在每次合成数据时加1。为了保证写入的数据是从地址0开始的,在复位状态下将初始地址设为最大18'h3ffff,这样在第一次有效16位的数据时,地址正好是从0开始。具体代码如下:

//SRAM写入数据地址变化,每接收两个串口数据加1
always@(posedge clk50M or negedge rst_n)
begin
if(!rst_n)
wirteaddr <= 'h3ffff;
else if(r1_data8bit_en && data_cnt[]==)
wirteaddr <= wirteaddr + 'd1;
else
wirteaddr <= wirteaddr;
end

上面判断data_cnt[0]==0是判断计数器奇偶的。

数据的读取,和rom读取数据类似了,这里只多了一个读取控制,本实验将该控制信号在数据写完后就将其变成有效,便可进行数据的读取,数据读取的地址主要是依据tft驱动模块的行扫描和场扫描计数器来计算的。具体代码如下:

//读数据控制位
assign read_n = (~ctrl_state)?'b0:1'b1;
//从SRAM读取数据地址,依据据TFT行和场扫描计数器变化
always@(posedge clk50M or negedge rst_n)
begin
if(!rst_n)
readaddr <= 'd0;
else if(tft_de&&(~read_n))
readaddr <= hcount + vcount * h_pixel;
else
readaddr <= 'd0;
end

这样就完成了UART2SRAM模块的设计,整个设计的代码如下:

   module uart2sram(
clk50M,
rst_n,
data8bit,
data8bit_en, vcount,
hcount,
tft_de, address,
write_n,
writedata,
read_n,
rx_img_done
); input clk50M; //系统时钟
input rst_n; //系统异步复位
input [:]data8bit; //串口接收的8位数据
input data8bit_en; //串口接收完成标志位 input [:]hcount; //TFT行扫描计数器
input [:]vcount; //TFT场扫描计数器
input tft_de; //TFT数据使能 output [:]address; //写入或读取数据的SRAM地址
output reg write_n; //写数据控制位
output [:]writedata; //写入数据到SRAM数据
output read_n; //读数据控制位 output reg rx_img_done; //一张图片数据传送完成标志位 reg [:]writeaddr; //写入数据到SRAM地址
reg [:]readaddr; //从SRAM读取数据的地址 reg ctrl_state; //读写控制状态,1代表可写状态,0代表可读状态 localparam h_pixel = , //屏的行像素点
v_pixel = ; //屏的场像素点 parameter rx_data_cnt_max = h_pixel*v_pixel; //最大串口接收数据量,根据屏的大小而定 //串口数据个数计数器
reg [:]data_cnt;
always@(posedge clk50M or negedge rst_n)
begin
if(!rst_n)
data_cnt <= 'd0;
else if(ctrl_state == 'b0) //可读状态,串口传数据无效
data_cnt <= 'd0;
else if(data8bit_en) //可写状态,计数串口发送数据
data_cnt <= data_cnt + 'd1;
else
data_cnt <= data_cnt;
end //2个8位串口合成一个16位数据
//step1:将接收的串口数据存储起来
reg [:]r1_data8bit;
always@(posedge clk50M or negedge rst_n)
begin
if(!rst_n)
begin
r1_data8bit <= 'd0;
end
else
begin
r1_data8bit <= data8bit;
end
end //step2:产生数据合成标志位,即将data8bit_en延时1个周期的信号
reg r1_data8bit_en;
always@(posedge clk50M or negedge rst_n)
begin
if(!rst_n)
begin
r1_data8bit_en <= 'b0;
end
else
begin
r1_data8bit_en <= data8bit_en;
end
end //step3:数据合成
reg [:] data16bit;
always@(posedge clk50M or negedge rst_n)
begin
if(!rst_n)
data16bit <= 'd0;
else if(r1_data8bit_en && data_cnt[]==)
data16bit <= {r1_data8bit,data8bit};
else
data16bit <= data16bit;
end //SRAM写入数据地址变化,每接收两个串口数据加1
always@(posedge clk50M or negedge rst_n)
begin
if(!rst_n)
writeaddr <= 'h3ffff;
else if(r1_data8bit_en && data_cnt[]==)
writeaddr <= writeaddr + 'd1;
else
writeaddr <= writeaddr;
end //一帧图片数据传输完成标志
always@(posedge clk50M or negedge rst_n)
begin
if(!rst_n)
rx_img_done <= 'b0;
else if(r1_data8bit_en && data_cnt == rx_data_cnt_max)
rx_img_done <= 'b1;
else
rx_img_done <= 'b0;
end //读写状态控制
always@(posedge clk50M or negedge rst_n)
begin
if(!rst_n)
ctrl_state <= 'b1;
else if(rx_img_done)
ctrl_state <= 'b0;
else
ctrl_state <= ctrl_state;
end //写数据控制位
always@(posedge clk50M or negedge rst_n)
begin
if(!rst_n)
write_n <= 'b1;
else if(rx_img_done)
write_n <= 'b1;
else if(data_cnt > && r1_data8bit_en)
write_n <= 'b0;
else
write_n <= write_n;
end //写数据
wire [:]writedata = data16bit; //读数据控制位
assign read_n = (~ctrl_state)?'b0:1'b1; //从SRAM读取数据地址,依据据TFT行和场扫描计数器变化
always@(posedge clk50M or negedge rst_n)
begin
if(!rst_n)
readaddr <= 'd0;
else if(tft_de&&(~read_n))
readaddr <= hcount + vcount * h_pixel;
else
readaddr <= 'd0;
end //SRAM地址
assign address = (~write_n)?writeaddr:(~read_n)?readaddr:'h0; endmodule

编写tb文件进行仿真验证,这里要借用之前的tft驱动模块提供vcount、hcount和tft_de信号,具体代码如下:

   `timescale 1ns/1ns
`define PERIOD_CLK50M
`define PERIOD_CLK9M module uart2sram_tb; reg clk50M;
reg clk9M;
reg rst_n;
reg [:]data8bit;
reg data8bit_en; wire [:]hcount;
wire [:]vcount;
wire tft_vs;
wire tft_de; wire [:]address;
wire write_n;
wire [:]writedata;
wire read_n;
wire rx_img_done; reg [:]v_cnt = ; //扫描帧数统计计数器 defparam uart2sram.rx_data_cnt_max = ; TFT_CTRL u1_TFT_CTRL(
.clk9M(clk9M),
.rst_n(rst_n),
.data_in(),
.hcount(hcount),
.vcount(vcount),
.tft_rgb(),
.tft_hs(),
.tft_vs(tft_vs),
.tft_clk(),
.tft_de(tft_de),
.tft_pwm()
); uart2sram uart2sram(
.clk50M(clk50M),
.rst_n(rst_n),
.data8bit(data8bit),
.data8bit_en(data8bit_en),
.hcount(hcount),
.vcount(vcount),
.tft_de(tft_de), .address(address),
.write_n(write_n),
.writedata(writedata),
.read_n(read_n),
.rx_img_done(rx_img_done)
); initial clk50M = 'b1;
always #(`PERIOD_CLK50M/) clk50M = ~clk50M; initial clk9M = 'b1;
always #(`PERIOD_CLK9M/) clk9M = ~clk9M; initial
begin
rst_n = 'b0;
data8bit_en = 'b0;
#(`PERIOD_CLK50M* + )
rst_n = 'b1;
#;
forever
begin
#;
data8bit_en = 'b1;
#;
data8bit_en = 'b0;
end #;
$stop;
end initial
begin
data8bit = 'd0;
forever
begin
@(posedge data8bit_en);
#`PERIOD_CLK50M;
data8bit = data8bit + ;
end
end initial
begin
wait(v_cnt == ); //等待扫描2帧后结束仿真
$stop;
end always@(posedge tft_vs) //统计总扫描帧数
v_cnt = v_cnt + 'b1;
endmodule

仿真结果如下:

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

可以看到数据的合成和写SRAM数据和地址与设计的是相符的。由于要看到读数据的地址,需要的时间较长,在编写tb时,将最大串口接收数据量改小进行仿真得到读取SRAM数据部分的仿真波形如下:

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

从上面的波形可以看出数据读取的地址波形与预期一致,我们还发现其地址改变的位置与屏的驱动时钟的上升沿并没有对齐,这个好像没有影响,看tft屏的驱动时序图发现屏的显示好像是下降沿对应的像素点数据,这样我们的设计也是符合这个的。或者为了与tft时钟上升沿同步,可以将tft时钟延迟相应的时钟周期。

各模块设计完成,接下来是顶层文件的设计,设计如下:

   module uart_tft_img(
clk50M,
rst_n,
Rs232_rx, sram_addr,
sram_dq,
sram_ce_n,
sram_oe_n,
sram_we_n,
sram_lb_n,
sram_ub_n, tft_rgb,
tft_hs,
tft_vs,
tft_clk,
tft_de,
tft_pwm, led
); input clk50M;
input rst_n;
input Rs232_rx; output [:]sram_addr; //操作RAM数据的地址
inout [:]sram_dq; //RAM的数据端口
output sram_ce_n; //SRAM片选信号,低电平有效
output sram_oe_n; //SRAM读数据控制信号,低电平有效
output sram_we_n; //SRAM写数据控制信号,低电平有效
output sram_lb_n; //数据低字节有效
output sram_ub_n; //数据高字节有效 output [:]tft_rgb;
output tft_hs;
output tft_vs;
output tft_clk;
output tft_de;
output tft_pwm;
output led; //用于指示图片数据是否已经接收完成 wire [:]Data_Byte;
wire Rx_Done; wire [:]data8bit;
wire data8bit_en;
wire [:]address;
wire write_n;
wire [:]writedata;
wire read_n;
wire rx_img_done;
wire [:]readdata; wire clk9M;
wire [:]data_in;
wire [:]hcount;
wire [:]vcount; //串口接收模块例化
uart_byte_rx u0_uart_byte_rx(
.clk50M(clk50M),
.rst_n(rst_n),
.Rs232_rx(Rs232_rx),
.baud_set('d4), //波特率设置为115200 .Data_Byte(Data_Byte),
.Rx_Done(Rx_Done)
); assign data8bit = Data_Byte;
assign data8bit_en = Rx_Done; //串口数据存入SRAM模块例化
uart2sram u1_uart2sram(
.clk50M(clk50M),
.rst_n(rst_n),
.data8bit(data8bit),
.data8bit_en(data8bit_en),
.hcount(hcount),
.vcount(vcount),
.tft_de(tft_de), .address(address),
.write_n(write_n),
.writedata(writedata),
.read_n(read_n),
.rx_img_done(rx_img_done)
); assign led = (!rst_n)?'b1:rx_img_done?1'b0:led; //SRAM控制模块例化
sram_ctrl u2_sram_ctrl(
.clk50M(clk50M),
.rst_n(rst_n),
.address(address),
.chipselect_n('b0),
.read_n(read_n),
.write_n(write_n),
.byteenable_n('b00),
.writedata(writedata),
.readdata(readdata), .sram_addr(sram_addr),
.sram_dq(sram_dq),
.sram_ce_n(sram_ce_n),
.sram_oe_n(sram_oe_n),
.sram_we_n(sram_we_n),
.sram_lb_n(sram_lb_n),
.sram_ub_n(sram_ub_n)
); //9Mhz时钟
pll u3_pll(
.areset(!rst_n),
.inclk0(clk50M),
.c0(clk9M)
); assign data_in = readdata; //TFT屏控制模块例化
TFT_CTRL u4_TFT_CTRL(
.clk9M(clk9M),
.rst_n(rst_n),
.data_in(data_in),
.hcount(hcount),
.vcount(vcount), .tft_rgb(tft_rgb),
.tft_hs(tft_hs),
.tft_vs(tft_vs),
.tft_clk(tft_clk),
.tft_de(tft_de),
.tft_pwm(tft_pwm)
); endmodule

以下为仿真顶层模块的设计

   `timescale 1ns/1ns
`define PERIOD_CLK module uart_tft_img_tb; reg clk50M;
reg rst_n;
reg send_en;
reg [:]Data_Byte; wire [:]sram_addr;
wire [:]sram_dq;
wire sram_ce_n;
wire sram_oe_n;
wire sram_we_n;
wire sram_lb_n;
wire sram_ub_n; wire [:]tft_rgb;
wire tft_hs;
wire tft_vs;
wire tft_clk;
wire tft_de;
wire tft_pwm;
wire led; wire Rs232_Tx;
wire Tx_Done; defparam u1_uart_tft_img.u1_uart2sram.rx_data_cnt_max = ; //例化串口发送模块
uart_byte_tx u0_uart_byte_tx(
.Clk(clk50M),
.Rst_n(rst_n),
.send_en(send_en),
.baud_set('d4),
.Data_Byte(Data_Byte), .Rs232_Tx(Rs232_Tx),
.Tx_Done(Tx_Done),
.uart_state()
); //例化顶层模块uart_tft_img
uart_tft_img u1_uart_tft_img(
.clk50M(clk50M),
.rst_n(rst_n),
.Rs232_rx(Rs232_Tx), .sram_addr(sram_addr),
.sram_dq(sram_dq),
.sram_ce_n(sram_ce_n),
.sram_oe_n(sram_oe_n),
.sram_we_n(sram_we_n),
.sram_lb_n(sram_lb_n),
.sram_ub_n(sram_ub_n), .tft_rgb(tft_rgb),
.tft_hs(tft_hs),
.tft_vs(tft_vs),
.tft_clk(tft_clk),
.tft_de(tft_de),
.tft_pwm(tft_pwm),
.led(led)
); initial clk50M <= 'b1;
always #(`PERIOD_CLK/) clk50M <= ~clk50M; initial begin
rst_n <= 'b0;
send_en <= 'b0;
Data_Byte <= 'b0000_0000;
#(`PERIOD_CLK* + )
rst_n <= 'b1;
#(`PERIOD_CLK*) Data_Byte <= 'h0;
send_en <= 'b1;
#(`PERIOD_CLK)
send_en <= 'b0; repeat()
begin
@(posedge Tx_Done) //数据传输完成
#(`PERIOD_CLK*);
Data_Byte <= Data_Byte + 'h3;
send_en <= 'b1;
#(`PERIOD_CLK)
send_en <= 'b0;
end @(posedge Tx_Done)//数据传输完成
#(`PERIOD_CLK*)
$stop;
end endmodule

由于按照实际的数据量来仿真需要的时间太长,为了缩短时间,将数据量更改为小一点的值,主要是更改上面代码的第30行。

仿真波形如下:

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

以上图片是串口传数据,然后将数据写入SRAM的波形,与预期设计效果一致。

有关读数据的仿真由于仿真过程没有实际SRAM读出的数据,只能看读地址的波形和地址的变化。这个地方没有想到好的仿真方法。

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

板级验证,引脚分配按照梅哥发的文档引脚一一对应分配好即可,分配表如下:

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

下载后进行板级验证,在此之前我们先配置一个SignalTap II Logic Analyzer的文件,这样可以方便我们验证SRAM写入和读取数据的对错,以及一张图片数据是否写完。具体的关于这个配置,小梅哥的视频上有讲,我的配置如下图所示:

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

创建好,保存后重新编译,下载,然后再打开SignalTap II Logic Analyzer,让其一直处于循环的抓捕状态。以下是刚复位后的状态,此时开发板的led0也处于灭的状态。

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

打开串口软件,我使用的是友善串口调试助手,这个因人而异,有的串口软件不好用可以换其他的,总有一款适合你,以下是我用的串口软件:

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

串口设置与我们设计的串口接收模块的设置保持一致,程序里波特率设置的位115200,图片数据输入是将图片数据复制在下面红色空中的,最开始是想着直接发送数据文件的,后来发现文件好像默认是把一位16进制数当成了两个字节,例如一字节的0xFF,在文件里就成了2个字节,如下图所示,实际261120字节大小的数据,放入文本文档中就变成了522240字节大小

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

这样将我们要发送的数据量变成了原有的两倍导致错误。我是直接复制数据粘贴在红框中发送的,反应有点慢,不影响最后的结果。在数据传输过程中我们可以在SignalTap II Logic Analyzer工具中看到写入和读取SRAM数据的过程,我截取了写数据和读数据过程中的两幅图如下:

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

板级验证结果如下:

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

在串口数据传输完成后LED0变亮,与设计的完全一致。

上述图片数据是首先在网上找的与tft屏大小一样的图片,然后利用软件Img2Lcd,和rom存储图片数据显示在tft屏的操作差不多,将图片转换成 .c的数据文件,该数据文件中数据是0x开头的,但是有的串口不能识别0x和逗号,我们可以利用Notepad++ 软件进行简单的处理,就是用Notepad++ 软件将数据文件打开,然后利用全部替换功能将0x和逗号之类的无用的字符去掉,这样剩下的都是有效的数据,然后复制粘贴到串口软件即可。

如有更多问题,欢迎加入芯航线 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