十五、串口发送图片数据到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控制信号的真值表

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

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

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

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

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

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

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

(a)写操作时序图

(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

仿真结果如下:

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

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

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

接下来还是进入今天的主题,就是通过串口的传图片数据到SRAM,然后通过读取SRAM的图片数据在tft上显示完整的图片,主要是解决上次通过读rom数据显示图片不能显示整个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

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

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

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

接下来就是向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

仿真结果如下:

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

从上面的波形可以看出数据读取的地址波形与预期一致,我们还发现其地址改变的位置与屏的驱动时钟的上升沿并没有对齐,这个好像没有影响,看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行。

仿真波形如下:

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

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

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

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

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

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

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

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

板级验证结果如下:

在串口数据传输完成后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

【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示的更多相关文章

  1. 【小梅哥FPGA进阶教程】第十四章 TFT屏显示图片

    十四.TFT屏显示图片 本文由杭电网友曾凯峰贡献,特此感谢 学习了小梅哥的TFT显示屏驱动设计后,想着在此基础上通过TFT屏显示一张图片,有了这个想法就开始动工了.首先想到是利用FPGA内部ROM存储 ...

  2. 【小梅哥FPGA进阶教程】第九章 基于串口猎人软件的串口示波器

    九.基于串口猎人软件的串口示波器 1.实验介绍 本实验,为芯航线开发板的综合实验,该实验利用芯航线开发板上的ADC.独立按键.UART等外设,搭建了一个具备丰富功能的数据采集卡,芯航线开发板负责进行数 ...

  3. 【小梅哥FPGA进阶教程】第十一章 四通道幅频相可调DDS信号发生器

    十一.四通道幅频相可调DDS信号发生器 本文由山东大学研友袁卓贡献,特此感谢 实验目标 实现多通道可调信号发生器 实验平台 芯航线FPGA核心板.ADDA模块 实验现象 实现基于FPGA的多通道可调信 ...

  4. 【小梅哥FPGA进阶教程】MC8051软核在FPGA上的使用

    十.MC8051软核在FPGA上的使用 本教程内容力求以详细的步骤和讲解让读者以最快的方式学会 MC8051 IP core 的应用以及相关设计软件的使用,并激起读者对 SOPC 技术的兴趣.本实验重 ...

  5. 【小梅哥FPGA进阶教程】第十三章 四通道数字电压表

    十三.四通道数字电压表 本文由山东大学研友袁卓贡献,特此感谢 实验目的 设计一个四通道的数字电压表 实验平台 芯航线FPGA核心板.AD/DA模块 实验现象 实现一个四通道的数字电压表,其中可以用按键 ...

  6. 【小梅哥FPGA进阶教程】第十二章 数字密码锁设计

    十二.数字密码锁设计 本文由山东大学研友袁卓贡献,特此感谢 实验目的 实现数字密码锁设计,要求矩阵按键输出且数码管显示输入密码,密码输入正确与否均会有相应标志信号产生. 实验平台 芯航线FPGA核心板 ...

  7. 【小梅哥FPGA进阶学习之旅】基于Altera FPGA 的DDR2+千兆以太网电路设计

    DDR2电路设计 在高速大数据的应用中,高速大容量缓存是必不可少的硬件.当前在FPGA系统中使用较为广泛的高速大容量存储器有经典速度较低的单数据速率的SDRAM存储器,以及速度较高的双速率DDR.DD ...

  8. 小梅哥FPGA数字逻辑设计教程——基于线性序列机的TLC5620型DAC驱动设计

    基于线性序列机的TLC5620型DAC驱动设计 目录 TLC5620型DAC芯片概述:    2 TLC5620型DAC芯片引脚说明:    2 TLC5620型DAC芯片详细介绍:    3 TLC ...

  9. AVR单片机教程——串口发送

    本文隶属于AVR单片机教程系列.   到目前为止,我们的开发板只能处理很小量的数据:读取几个引脚电平,输出几个LED,顶多用数码管显示一个两位数字.至于输入一个指令.输出一条调试信息,甚至用scanf ...

随机推荐

  1. Julia - 字符串判断函数

    isascii() 判断是否是 ascii 码,返回 Bool 值 julia> isascii('a') true julia> isascii('α') false julia> ...

  2. Python压缩及解压文件

    Zip压缩 #-*- coding:utf-8 -*- __author__ = "MuT6 Sch01aR" import zipfile #加载模块 # 压缩 z = zipf ...

  3. mac 使用svn记录

    checkout  project : svn checkout svn://127.0.0.1/repository --username=username --password=password ...

  4. 第一章IP:网际协议

    I P是T C P / I P协议族中最为核心的协议.所有的 T C P.U D P.I C M P及I G M P数据都以I P数据报格式传输(见图 1 - 4).许多刚开始接触 T C P / I ...

  5. Spring Test 整合 JUnit 4 使用总结

    转自:https://blog.csdn.net/hgffhh/article/details/83712924 这两天做Web开发,发现通过spring进行对象管理之后,做测试变得复杂了.因为所有的 ...

  6. KindEditor 和 xss过滤

    KindEditor   1.进入官网 2.下载 官网下载:http://kindeditor.net/down.php 本地下载:http://files.cnblogs.com/files/wup ...

  7. 深入理解Javascript中构造函数和原型对象的区别(转存)

    Object是构造函数,而Object.prototype是构造函数的原型对象.构造函数自身的属性和方法无法被共享,而原型对象的属性和方法可以被所有实例对象所共享. 首先,我们知道,构造函数是生成对象 ...

  8. Python面向对象相关知识1

    1. python是动态的语言,这样在使用类的时候,类的属性就可以随意的添加,但是这样在实际开发中有一定的缺陷,所以,可以在类中定义一个特殊的__init__()方法,当创建实例时,__init__( ...

  9. 浅谈scheduler

  10. Python基础语法习题一

    Part 1 习题 1.简述编译型与解释型语言的区别,且分别列出你知道的哪些语言属于编译型,哪些属于解释型 2.执行 Python 脚本的两种方式是什么 3.Pyhton 单行注释和多行注释分别用什么 ...