• 【小梅哥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控制信号的真值表

    图片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

  • 相关阅读:
    财富平台项目日记1:spring boot + mybatis 实现分页查询
    Spring boot 跨域
    Mysql索引
    Java中list多对多拆分
    Redis持久化
    Windows下安装Redis
    idea 常用快捷键
    数据库事务
    Linux开启防火墙端口号
    nginx相关
  • 原文地址:https://www.cnblogs.com/xiaomeige/p/6478658.html
Copyright © 2020-2023  润新知