首页 > 技术文章 > 【小梅哥FPGA进阶教程】串口发送图片数据到SRAM在TFT屏上显示

xiaomeige 2017-02-28 14:17 原文

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

 

图片1图片2

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

 

图片3

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

 

图片4

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

图片5

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

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

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

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

 

图片6

(a)写操作时序图

图片7

(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 

仿真结果如下:

 

图片10

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

 

图片11

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

 

图片12

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

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

 

图片13

框图中除了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

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

图片15

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

图片16

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

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

仿真结果如下:

 

图片22

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

 

图片23

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

仿真波形如下:

 

图片26

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

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

 

图片27

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

 

图片28

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

 

图片29

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

 

图片30

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

 

图片31

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

 

图片32

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

 

图片33

 

图片34

板级验证结果如下:

 

图片35

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

推荐阅读