【小梅哥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控制信号的真值表
接下来分析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控制器的设计如下:
1 module sram_ctrl( 2 clk50M, 3 rst_n, 4 address, 5 chipselect_n, 6 read_n, 7 write_n, 8 byteenable_n, 9 writedata, 10 readdata, 11 12 sram_addr, 13 sram_dq, 14 sram_ce_n, 15 sram_oe_n, 16 sram_we_n, 17 sram_lb_n, 18 sram_ub_n 19 ); 20 21 input clk50M; //系统时钟,默认50M 22 input rst_n; //异步复位,低电平有效 23 24 input [17:0] address; //数据传输地址 25 input chipselect_n; //SRAM片选信号,低电平有效 26 input read_n; //数据读控制信号,低电平有效 27 input write_n; //数据写控制信号,低电平有效 28 input [1:0]byteenable_n;//数据高低字节使能,低电平有效 29 input [15:0]writedata; //待写入RAM的数据 30 output [15:0]readdata; //读RAM的数据 31 32 output [17:0]sram_addr; //操作RAM数据的地址 33 inout [15:0]sram_dq; //RAM的数据端口 34 output sram_ce_n; //SRAM片选信号,低电平有效 35 output sram_oe_n; //SRAM读数据控制信号,低电平有效 36 output sram_we_n; //SRAM写数据控制信号,低电平有效 37 output sram_lb_n; //数据低字节有效 38 output sram_ub_n; //数据高字节有效 39 40 //signal declaration 41 reg [17:0]addr_reg; 42 reg [15:0]rdata_reg, wdata_reg; 43 reg ce_n_reg, lb_n_reg, ub_n_reg, oe_n_reg, we_n_reg; 44 45 //body 46 //registers 47 always@(posedge clk50M or negedge rst_n) 48 begin 49 if(!rst_n) 50 begin 51 addr_reg <= 18'd0; 52 rdata_reg <= 16'd0; 53 wdata_reg <= 16'd0; 54 ce_n_reg <= 1'b1; 55 oe_n_reg <= 1'b1; 56 we_n_reg <= 1'b1; 57 lb_n_reg <= 1'b1; 58 ub_n_reg <= 1'b1; 59 end 60 else 61 begin 62 addr_reg <= address; 63 rdata_reg <= sram_dq; 64 wdata_reg <= writedata; 65 ce_n_reg <= chipselect_n; 66 oe_n_reg <= read_n; 67 we_n_reg <= write_n; 68 lb_n_reg <= byteenable_n[0]; 69 ub_n_reg <= byteenable_n[1]; 70 end 71 end 72 73 //to fpga interface 74 assign readdata = rdata_reg; 75 76 //to SRAM 77 assign sram_addr = addr_reg; 78 assign sram_ce_n = ce_n_reg; 79 assign sram_oe_n = oe_n_reg; 80 assign sram_we_n = we_n_reg; 81 assign sram_ub_n = ub_n_reg; 82 assign sram_lb_n = lb_n_reg; 83 //SRAM tristate data bus 84 assign sram_dq = (~we_n_reg)?wdata_reg:16'bz; 85 86 endmodule
SRAM的数据线是输出输入数据共用的,要将其设计成三态门形式,具体如代码84行所示。接下就是编写tb文件来验证驱动程序,代码如下:
1 `timescale 1ns/1ns 2 `define PERIOD_CLK 20 3 4 module sram_tb; 5 reg clk50M; 6 reg rst_n; 7 8 reg [17:0]address; 9 reg read_n; 10 reg write_n; 11 12 reg [15:0]writedata; 13 wire [15:0]readdata; 14 15 wire [17:0]sram_addr; 16 wire [15:0]sram_dq; 17 wire sram_ce_n; 18 wire sram_oe_n; 19 wire sram_we_n; 20 wire sram_lb_n; 21 wire sram_ub_n; 22 23 integer i; 24 25 sram_ctrl sram_ctrl_u0( 26 .clk50M(clk50M), 27 .rst_n(rst_n), 28 .address(address), 29 .chipselect_n(1'b0), 30 .read_n(read_n), 31 .write_n(write_n), 32 .byteenable_n(2'b00), 33 .writedata(writedata), 34 .readdata(readdata), 35 36 .sram_addr(sram_addr), 37 .sram_dq(sram_dq), 38 .sram_ce_n(sram_ce_n), 39 .sram_oe_n(sram_oe_n), 40 .sram_we_n(sram_we_n), 41 .sram_lb_n(sram_lb_n), 42 .sram_ub_n(sram_ub_n) 43 ); 44 45 initial clk50M = 1'b1; 46 always #(`PERIOD_CLK/2) clk50M = ~clk50M; 47 48 initial 49 begin 50 rst_n = 1'b0; 51 read_n = 1'b1; 52 address = 0; 53 write_n = 1'b1; 54 writedata = 16'h0; 55 #(`PERIOD_CLK*200 + 1) 56 rst_n = 1'b1; 57 58 write_n = 1'b0; 59 for(i=0; i<1000; i=i+1) 60 begin 61 #(`PERIOD_CLK); 62 address = address + 1; 63 writedata = writedata + 1; 64 end 65 write_n = 1'b1; 66 #(`PERIOD_CLK*2000); 67 68 #2000; 69 address = 0; 70 read_n = 1'b0; 71 for(i=0; i<1000; i=i+1) 72 begin 73 #(`PERIOD_CLK); 74 address = address + 1; 75 end 76 read_n = 1'b1; 77 #(`PERIOD_CLK*2000); 78 79 #2000; 80 $stop; 81 end 82 83 endmodule
仿真结果如下:
写操作控制信号放大后波形如下:
读操作控制信号放大后波形如下:
这里需要说明一下,就是读操作读出的数据没有值,主要是没有真正的接SRAM,还没想到怎么去验证读数据,但是仿真结果可以看出,读写时序与按预期设计的一致。如果想进一步进行板级验证,也是可以的,这就需要使用SignalTap II Logic Analyzer工具对写入的数据和读取的数据进行抓取和比较,从而判断控制驱动设计的对错,具体的操作后面会提到。关于SRAM的控制驱动就说这么多,其他的可以参考芯片手册做更进一步的设计,本人经验不足,还望前辈们批评指正。
接下来还是进入今天的主题,就是通过串口的传图片数据到SRAM,然后通过读取SRAM的图片数据在tft上显示完整的图片,主要是解决上次通过读rom数据显示图片不能显示整个tft屏的问题。主要的设计框图如下:
框图中除了UART2SRAM模块是没有设计的,其余模块都已经进行了设计和验证,串口接收模块和tft屏的驱动参考的小梅哥教程里的。UART2SRAM模块主要有两个功能一个是将串口接收来的8位的数据每两个合成一个16位的数据传给writedata,还有一个是向SARM里写入数据和读取数据。数据的合成首先对串口接收模块的输出数据进行一个计数,然后通过计数器的数将每两个8位合成一个16位的数据,也就是个数为偶数时进行一次合成。具体代码如下:
//串口数据个数计数器 reg [17:0]data_cnt; always@(posedge clk50M or negedge rst_n) begin if(!rst_n) data_cnt <= 18'd0; else if(ctrl_state) data_cnt <= 18'd0; else if(data8bit_en) data_cnt <= data_cnt + 18'd1; else data_cnt <= data_cnt; end //2个8位串口合成一个16位数据 //step1:将接收的串口数据存储起来 reg [7:0]r1_data8bit; //reg [7:0]r2_data8bit; always@(posedge clk50M or negedge rst_n) begin if(!rst_n) begin r1_data8bit <= 8'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 <= 1'b0; end else begin r1_data8bit_en <= data8bit_en; end end //step3:数据合成 reg [15:0] data16bit; always@(posedge clk50M or negedge rst_n) begin if(!rst_n) data16bit <= 16'd0; else if(r1_data8bit_en && data_cnt[0]==0) 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 <= 1'b0; else if(r1_data8bit_en && data_cnt == rx_data_cnt_max) rx_img_done <= 1'b1; else rx_img_done <= 1'b0; end //写数据控制 always@(posedge clk50M or negedge rst_n) begin if(!rst_n) write_n <= 1'b1; else if(rx_img_done) write_n <= 1'b1; else if(data_cnt > 0 && r1_data8bit_en) write_n <= 1'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 <= 18'h3ffff; else if(r1_data8bit_en && data_cnt[0]==0) wirteaddr <= wirteaddr + 18'd1; else wirteaddr <= wirteaddr; end
上面判断data_cnt[0]==0是判断计数器奇偶的。
数据的读取,和rom读取数据类似了,这里只多了一个读取控制,本实验将该控制信号在数据写完后就将其变成有效,便可进行数据的读取,数据读取的地址主要是依据tft驱动模块的行扫描和场扫描计数器来计算的。具体代码如下:
//读数据控制位 assign read_n = (~ctrl_state)?1'b0:1'b1; //从SRAM读取数据地址,依据据TFT行和场扫描计数器变化 always@(posedge clk50M or negedge rst_n) begin if(!rst_n) readaddr <= 18'd0; else if(tft_de&&(~read_n)) readaddr <= hcount + vcount * h_pixel; else readaddr <= 18'd0; end
这样就完成了UART2SRAM模块的设计,整个设计的代码如下:
1 module uart2sram( 2 clk50M, 3 rst_n, 4 data8bit, 5 data8bit_en, 6 7 vcount, 8 hcount, 9 tft_de, 10 11 address, 12 write_n, 13 writedata, 14 read_n, 15 rx_img_done 16 ); 17 18 input clk50M; //系统时钟 19 input rst_n; //系统异步复位 20 input [7:0]data8bit; //串口接收的8位数据 21 input data8bit_en; //串口接收完成标志位 22 23 input [9:0]hcount; //TFT行扫描计数器 24 input [9:0]vcount; //TFT场扫描计数器 25 input tft_de; //TFT数据使能 26 27 output [17:0]address; //写入或读取数据的SRAM地址 28 output reg write_n; //写数据控制位 29 output [15:0]writedata; //写入数据到SRAM数据 30 output read_n; //读数据控制位 31 32 output reg rx_img_done; //一张图片数据传送完成标志位 33 34 reg [17:0]writeaddr; //写入数据到SRAM地址 35 reg [17:0]readaddr; //从SRAM读取数据的地址 36 37 reg ctrl_state; //读写控制状态,1代表可写状态,0代表可读状态 38 39 localparam h_pixel = 480, //屏的行像素点 40 v_pixel = 272; //屏的场像素点 41 42 parameter rx_data_cnt_max = h_pixel*v_pixel; //最大串口接收数据量,根据屏的大小而定 43 44 //串口数据个数计数器 45 reg [17:0]data_cnt; 46 always@(posedge clk50M or negedge rst_n) 47 begin 48 if(!rst_n) 49 data_cnt <= 18'd0; 50 else if(ctrl_state == 1'b0) //可读状态,串口传数据无效 51 data_cnt <= 18'd0; 52 else if(data8bit_en) //可写状态,计数串口发送数据 53 data_cnt <= data_cnt + 18'd1; 54 else 55 data_cnt <= data_cnt; 56 end 57 58 //2个8位串口合成一个16位数据 59 //step1:将接收的串口数据存储起来 60 reg [7:0]r1_data8bit; 61 always@(posedge clk50M or negedge rst_n) 62 begin 63 if(!rst_n) 64 begin 65 r1_data8bit <= 8'd0; 66 end 67 else 68 begin 69 r1_data8bit <= data8bit; 70 end 71 end 72 73 //step2:产生数据合成标志位,即将data8bit_en延时1个周期的信号 74 reg r1_data8bit_en; 75 always@(posedge clk50M or negedge rst_n) 76 begin 77 if(!rst_n) 78 begin 79 r1_data8bit_en <= 1'b0; 80 end 81 else 82 begin 83 r1_data8bit_en <= data8bit_en; 84 end 85 end 86 87 //step3:数据合成 88 reg [15:0] data16bit; 89 always@(posedge clk50M or negedge rst_n) 90 begin 91 if(!rst_n) 92 data16bit <= 16'd0; 93 else if(r1_data8bit_en && data_cnt[0]==0) 94 data16bit <= {r1_data8bit,data8bit}; 95 else 96 data16bit <= data16bit; 97 end 98 99 //SRAM写入数据地址变化,每接收两个串口数据加1 100 always@(posedge clk50M or negedge rst_n) 101 begin 102 if(!rst_n) 103 writeaddr <= 18'h3ffff; 104 else if(r1_data8bit_en && data_cnt[0]==0) 105 writeaddr <= writeaddr + 18'd1; 106 else 107 writeaddr <= writeaddr; 108 end 109 110 //一帧图片数据传输完成标志 111 always@(posedge clk50M or negedge rst_n) 112 begin 113 if(!rst_n) 114 rx_img_done <= 1'b0; 115 else if(r1_data8bit_en && data_cnt == rx_data_cnt_max) 116 rx_img_done <= 1'b1; 117 else 118 rx_img_done <= 1'b0; 119 end 120 121 //读写状态控制 122 always@(posedge clk50M or negedge rst_n) 123 begin 124 if(!rst_n) 125 ctrl_state <= 1'b1; 126 else if(rx_img_done) 127 ctrl_state <= 1'b0; 128 else 129 ctrl_state <= ctrl_state; 130 end 131 132 //写数据控制位 133 always@(posedge clk50M or negedge rst_n) 134 begin 135 if(!rst_n) 136 write_n <= 1'b1; 137 else if(rx_img_done) 138 write_n <= 1'b1; 139 else if(data_cnt > 0 && r1_data8bit_en) 140 write_n <= 1'b0; 141 else 142 write_n <= write_n; 143 end 144 145 //写数据 146 wire [15:0]writedata = data16bit; 147 148 //读数据控制位 149 assign read_n = (~ctrl_state)?1'b0:1'b1; 150 151 //从SRAM读取数据地址,依据据TFT行和场扫描计数器变化 152 always@(posedge clk50M or negedge rst_n) 153 begin 154 if(!rst_n) 155 readaddr <= 18'd0; 156 else if(tft_de&&(~read_n)) 157 readaddr <= hcount + vcount * h_pixel; 158 else 159 readaddr <= 18'd0; 160 end 161 162 //SRAM地址 163 assign address = (~write_n)?writeaddr:(~read_n)?readaddr:18'h0; 164 165 endmodule
编写tb文件进行仿真验证,这里要借用之前的tft驱动模块提供vcount、hcount和tft_de信号,具体代码如下:
1 `timescale 1ns/1ns 2 `define PERIOD_CLK50M 20 3 `define PERIOD_CLK9M 120 4 5 module uart2sram_tb; 6 7 reg clk50M; 8 reg clk9M; 9 reg rst_n; 10 reg [7:0]data8bit; 11 reg data8bit_en; 12 13 wire [9:0]hcount; 14 wire [9:0]vcount; 15 wire tft_vs; 16 wire tft_de; 17 18 wire [17:0]address; 19 wire write_n; 20 wire [15:0]writedata; 21 wire read_n; 22 wire rx_img_done; 23 24 reg [7:0]v_cnt = 0; //扫描帧数统计计数器 25 26 defparam uart2sram.rx_data_cnt_max = 1000; 27 28 TFT_CTRL u1_TFT_CTRL( 29 .clk9M(clk9M), 30 .rst_n(rst_n), 31 .data_in(), 32 .hcount(hcount), 33 .vcount(vcount), 34 .tft_rgb(), 35 .tft_hs(), 36 .tft_vs(tft_vs), 37 .tft_clk(), 38 .tft_de(tft_de), 39 .tft_pwm() 40 ); 41 42 uart2sram uart2sram( 43 .clk50M(clk50M), 44 .rst_n(rst_n), 45 .data8bit(data8bit), 46 .data8bit_en(data8bit_en), 47 .hcount(hcount), 48 .vcount(vcount), 49 .tft_de(tft_de), 50 51 .address(address), 52 .write_n(write_n), 53 .writedata(writedata), 54 .read_n(read_n), 55 .rx_img_done(rx_img_done) 56 ); 57 58 initial clk50M = 1'b1; 59 always #(`PERIOD_CLK50M/2) clk50M = ~clk50M; 60 61 initial clk9M = 1'b1; 62 always #(`PERIOD_CLK9M/2) clk9M = ~clk9M; 63 64 initial 65 begin 66 rst_n = 1'b0; 67 data8bit_en = 1'b0; 68 #(`PERIOD_CLK50M*200 + 1) 69 rst_n = 1'b1; 70 #2000; 71 forever 72 begin 73 #6000; 74 data8bit_en = 1'b1; 75 #20; 76 data8bit_en = 1'b0; 77 end 78 79 #2000; 80 $stop; 81 end 82 83 initial 84 begin 85 data8bit = 8'd0; 86 forever 87 begin 88 @(posedge data8bit_en); 89 #`PERIOD_CLK50M; 90 data8bit = data8bit + 1; 91 end 92 end 93 94 initial 95 begin 96 wait(v_cnt == 3); //等待扫描2帧后结束仿真 97 $stop; 98 end 99 100 always@(posedge tft_vs) //统计总扫描帧数 101 v_cnt = v_cnt + 1'b1; 102 endmodule
仿真结果如下:
可以看到数据的合成和写SRAM数据和地址与设计的是相符的。由于要看到读数据的地址,需要的时间较长,在编写tb时,将最大串口接收数据量改小进行仿真得到读取SRAM数据部分的仿真波形如下:
从上面的波形可以看出数据读取的地址波形与预期一致,我们还发现其地址改变的位置与屏的驱动时钟的上升沿并没有对齐,这个好像没有影响,看tft屏的驱动时序图发现屏的显示好像是下降沿对应的像素点数据,这样我们的设计也是符合这个的。或者为了与tft时钟上升沿同步,可以将tft时钟延迟相应的时钟周期。
各模块设计完成,接下来是顶层文件的设计,设计如下:
1 module uart_tft_img( 2 clk50M, 3 rst_n, 4 Rs232_rx, 5 6 sram_addr, 7 sram_dq, 8 sram_ce_n, 9 sram_oe_n, 10 sram_we_n, 11 sram_lb_n, 12 sram_ub_n, 13 14 tft_rgb, 15 tft_hs, 16 tft_vs, 17 tft_clk, 18 tft_de, 19 tft_pwm, 20 21 led 22 ); 23 24 input clk50M; 25 input rst_n; 26 input Rs232_rx; 27 28 output [17:0]sram_addr; //操作RAM数据的地址 29 inout [15:0]sram_dq; //RAM的数据端口 30 output sram_ce_n; //SRAM片选信号,低电平有效 31 output sram_oe_n; //SRAM读数据控制信号,低电平有效 32 output sram_we_n; //SRAM写数据控制信号,低电平有效 33 output sram_lb_n; //数据低字节有效 34 output sram_ub_n; //数据高字节有效 35 36 output [15:0]tft_rgb; 37 output tft_hs; 38 output tft_vs; 39 output tft_clk; 40 output tft_de; 41 output tft_pwm; 42 output led; //用于指示图片数据是否已经接收完成 43 44 wire [7:0]Data_Byte; 45 wire Rx_Done; 46 47 wire [7:0]data8bit; 48 wire data8bit_en; 49 wire [17:0]address; 50 wire write_n; 51 wire [15:0]writedata; 52 wire read_n; 53 wire rx_img_done; 54 wire [15:0]readdata; 55 56 wire clk9M; 57 wire [15:0]data_in; 58 wire [9:0]hcount; 59 wire [9:0]vcount; 60 61 //串口接收模块例化 62 uart_byte_rx u0_uart_byte_rx( 63 .clk50M(clk50M), 64 .rst_n(rst_n), 65 .Rs232_rx(Rs232_rx), 66 .baud_set(3'd4), //波特率设置为115200 67 68 .Data_Byte(Data_Byte), 69 .Rx_Done(Rx_Done) 70 ); 71 72 assign data8bit = Data_Byte; 73 assign data8bit_en = Rx_Done; 74 75 //串口数据存入SRAM模块例化 76 uart2sram u1_uart2sram( 77 .clk50M(clk50M), 78 .rst_n(rst_n), 79 .data8bit(data8bit), 80 .data8bit_en(data8bit_en), 81 .hcount(hcount), 82 .vcount(vcount), 83 .tft_de(tft_de), 84 85 .address(address), 86 .write_n(write_n), 87 .writedata(writedata), 88 .read_n(read_n), 89 .rx_img_done(rx_img_done) 90 ); 91 92 assign led = (!rst_n)?1'b1:rx_img_done?1'b0:led; 93 94 //SRAM控制模块例化 95 sram_ctrl u2_sram_ctrl( 96 .clk50M(clk50M), 97 .rst_n(rst_n), 98 .address(address), 99 .chipselect_n(1'b0), 100 .read_n(read_n), 101 .write_n(write_n), 102 .byteenable_n(2'b00), 103 .writedata(writedata), 104 .readdata(readdata), 105 106 .sram_addr(sram_addr), 107 .sram_dq(sram_dq), 108 .sram_ce_n(sram_ce_n), 109 .sram_oe_n(sram_oe_n), 110 .sram_we_n(sram_we_n), 111 .sram_lb_n(sram_lb_n), 112 .sram_ub_n(sram_ub_n) 113 ); 114 115 //9Mhz时钟 116 pll u3_pll( 117 .areset(!rst_n), 118 .inclk0(clk50M), 119 .c0(clk9M) 120 ); 121 122 assign data_in = readdata; 123 124 //TFT屏控制模块例化 125 TFT_CTRL u4_TFT_CTRL( 126 .clk9M(clk9M), 127 .rst_n(rst_n), 128 .data_in(data_in), 129 .hcount(hcount), 130 .vcount(vcount), 131 132 .tft_rgb(tft_rgb), 133 .tft_hs(tft_hs), 134 .tft_vs(tft_vs), 135 .tft_clk(tft_clk), 136 .tft_de(tft_de), 137 .tft_pwm(tft_pwm) 138 ); 139 140 endmodule
以下为仿真顶层模块的设计
1 `timescale 1ns/1ns 2 `define PERIOD_CLK 20 3 4 module uart_tft_img_tb; 5 6 reg clk50M; 7 reg rst_n; 8 reg send_en; 9 reg [7:0]Data_Byte; 10 11 wire [17:0]sram_addr; 12 wire [15:0]sram_dq; 13 wire sram_ce_n; 14 wire sram_oe_n; 15 wire sram_we_n; 16 wire sram_lb_n; 17 wire sram_ub_n; 18 19 wire [15:0]tft_rgb; 20 wire tft_hs; 21 wire tft_vs; 22 wire tft_clk; 23 wire tft_de; 24 wire tft_pwm; 25 wire led; 26 27 wire Rs232_Tx; 28 wire Tx_Done; 29 30 defparam u1_uart_tft_img.u1_uart2sram.rx_data_cnt_max = 10; 31 32 //例化串口发送模块 33 uart_byte_tx u0_uart_byte_tx( 34 .Clk(clk50M), 35 .Rst_n(rst_n), 36 .send_en(send_en), 37 .baud_set(3'd4), 38 .Data_Byte(Data_Byte), 39 40 .Rs232_Tx(Rs232_Tx), 41 .Tx_Done(Tx_Done), 42 .uart_state() 43 ); 44 45 //例化顶层模块uart_tft_img 46 uart_tft_img u1_uart_tft_img( 47 .clk50M(clk50M), 48 .rst_n(rst_n), 49 .Rs232_rx(Rs232_Tx), 50 51 .sram_addr(sram_addr), 52 .sram_dq(sram_dq), 53 .sram_ce_n(sram_ce_n), 54 .sram_oe_n(sram_oe_n), 55 .sram_we_n(sram_we_n), 56 .sram_lb_n(sram_lb_n), 57 .sram_ub_n(sram_ub_n), 58 59 .tft_rgb(tft_rgb), 60 .tft_hs(tft_hs), 61 .tft_vs(tft_vs), 62 .tft_clk(tft_clk), 63 .tft_de(tft_de), 64 .tft_pwm(tft_pwm), 65 .led(led) 66 ); 67 68 initial clk50M <= 1'b1; 69 always #(`PERIOD_CLK/2) clk50M <= ~clk50M; 70 71 initial begin 72 rst_n <= 1'b0; 73 send_en <= 1'b0; 74 Data_Byte <= 8'b0000_0000; 75 #(`PERIOD_CLK*20 + 1) 76 rst_n <= 1'b1; 77 #(`PERIOD_CLK*50) 78 79 Data_Byte <= 8'h0; 80 send_en <= 1'b1; 81 #(`PERIOD_CLK) 82 send_en <= 1'b0; 83 84 repeat(2000) 85 begin 86 @(posedge Tx_Done) //数据传输完成 87 #(`PERIOD_CLK*5000); 88 Data_Byte <= Data_Byte + 8'h3; 89 send_en <= 1'b1; 90 #(`PERIOD_CLK) 91 send_en <= 1'b0; 92 end 93 94 @(posedge Tx_Done)//数据传输完成 95 #(`PERIOD_CLK*500) 96 $stop; 97 end 98 99 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