FPGA的串口 ram TFT传图实验

串口接收图片数据,传到RAM里面,然后通过读取RAM传到TFT上显示

下面一张是我画的框图,一张是网上截图的差不多的,都是一个意思。


顶层文件

module uart_ram_tft(
    clk,
    reset_n,
    uart_rx,
    tft_data_out,  //实际输出的信号值        
    tft_hs,     //行使能                
    tft_vs,     //列使能                
    tft_black,    //背光led高电平         
    tft_de,                          
    tft_clk,
    led                          
    );
    
    input   clk;
    input   reset_n;
    input   uart_rx;
    
    output [15:0] tft_data_out;  //实际输出的信号值         
    output  tft_hs;     //行使能                 
    output  tft_vs;     //列使能                 
    output  tft_black;    //背光led高电平          
    output  tft_de;                           
    output  tft_clk;                          
    output  led;    
    
    wire    rx_done;
    wire  [7:0]  rx_data; 
    
    wire    ram_wren  ;
    wire  [15:0]  ram_wraddr;
    wire  [15:0]  ram_wrdata;
    reg  [15:0]  ram_rdaddr;   //读地址
    
    wire    clk_106m;
    wire    [15:0]  ram_rddata;
    wire    [11:0]  hcount,vcount;
    
    wire    ram_data_en;
    wire    [15:0]  tft_data_in;
    wire    locked;

    clk_wiz_0 instance_name(
    // Clock out ports
    .clk_out1(clk_106m),     // output clk_out1
    // Status and control signals
    .reset(!reset_n), // input reset
    .locked(locked),       // output locked
   // Clock in ports
    .clk_in1(clk)
    );      // input clk_in1  
    
    uart_byte_rx uart_byte_rx_inst(
    .clk(clk),
    .reset_n(reset_n),
    
    .uart_rx(uart_rx),
    .rx_done(rx_done),
    .rx_data(rx_data)

    );   
    
    img_rx_wr  img_rx_wr_inst (
    .clk         (clk       ),
    .reset_n     (reset_n   ),
    .rx_data     (rx_data   ),
    .rx_done     (rx_done   ),
    .ram_wren    (ram_wren  ),
    .ram_wraddr  (ram_wraddr),
    .ram_wrdata  (ram_wrdata),
    .led         (led       )
    );    
    
    blk_mem_gen_0 your_instance_name (
    .clka(clk),    // input wire clka
    .ena(1),      // input wire ena
    .wea(ram_wren),      // input wire [0 : 0] wea
    .addra(ram_wraddr),  // input wire [15 : 0] addra
    .dina(ram_wrdata),    // input wire [15 : 0] dina
    .clkb(clk_106m),    // input wire clkb
    .enb(1),      // input wire enb
    .addrb(ram_rdaddr),  // input wire [15 : 0] addrb
    .doutb(ram_rddata)  // output wire [15 : 0] doutb
); 

    tft_ctrl    tft_ctrl_inst(
    .clk_106m(clk_106m),   //1904*932*60hz = 106,471,680HZ = 106.5Mhz 时钟IP核只能到这个点
    .reset_n(reset_n),
    
    //vga
    .data_req(data_req),
    .tft_data_in(tft_data_in),   //想要输入的信号值
    .tft_data_out(tft_data_out),  //实际输出的信号值
    .hs_count(hcount),   //行计数
    .vs_count(vcount),   //列计数
    .tft_hs(tft_hs),     //行使能
    .tft_vs(tft_vs),     //列使能
    .tft_de(tft_de),     //数据使能信号
    .tft_black(tft_black),    //背光高电平led
    .tft_clk(tft_clk)
);   
    wire    data_req;
    //ram当中存储的图像是256*256的像素矩阵
    always @(posedge clk_106m or negedge reset_n)
        if(!reset_n)
            ram_rdaddr <= 0;
        else if(ram_data_en)
            ram_rdaddr <= ram_rdaddr + 1'd1;
                          
    assign  ram_data_en = data_req && (hcount >= 272 && hcount < 528) && (vcount >= 112 && vcount < 368);
    assign  tft_data_in = ram_data_en? ram_rddata:0;
    
endmodule

顶层模块例化中,时钟IP核需要产生33.3M的时钟,RAM简单双端口,A端口写入,B端口读出。

在例化RAM的时候,我就在想这个B端口的读地址,按道理应该是input,因为是外界TFT读出RAM里面的数据地址和数据,而对于RAM来说,就是input

所以上面我的框图中的B端口的地址方向应该箭头朝向RAM

而在TFT_CTRL的端口里面,没有对读地址的例化,所以在顶层文件里面对读地址进行赋值分析。

    //ram当中存储的图像是256*256的像素矩阵
    always @(posedge clk_106m or negedge reset_n)
        if(!reset_n)
            ram_rdaddr <= 0;
        else if(ram_data_en)
            ram_rdaddr <= ram_rdaddr + 1'd1;
                          
    assign  ram_data_en = data_req && (hcount >= 272 && hcount < 528) && (vcount >= 112 && vcount < 368);
    assign  tft_data_in = ram_data_en? ram_rddata:0;

这边是通过ram_data_en数据,满足TFT请求,且在TFT的中央选取256*256的区域显示图像。

这是实际的显示图像的区域,在这个区域使能,然后开始读地址,累加地址。

且通过实际图像显示使能,开始将RAM的数据赋值给tft的预输入。

通过这几步完成读取RAM数据并显示。

 

tft控制(修改了时序)
module  tft_ctrl(
    clk_106m,   //1904*932*60hz = 106,471,680HZ = 106.5Mhz 时钟IP核只能到这个点
    //这边其实是33M,上面的106M是我给1440*900的VGA显示器适配的
    //时钟IP核输出33.3M
    reset_n,
    
    //vga
    data_req,   //读数据请求,这个是VGA这边的,请求RAM那边,时序不一样,需要打拍
    
    tft_data_in,   //想要输入的信号值
    tft_data_out,  //实际输出的信号值
    hs_count,   //行计数
    vs_count,   //列计数
    tft_hs,     //行使能
    tft_vs,     //列使能
    tft_de,     //数据使能信号
    tft_black,    //背光高电平led
    tft_clk
);

    input   clk_106m;
    input   reset_n;
    
    input   [15:0]   tft_data_in;  //8位 R G B
    
    output reg data_req;
    
    output reg  [15:0]   tft_data_out;
    output reg  [11:0] hs_count;  //实际图像区域的像素计数
    output reg  [11:0] vs_count;
    output  tft_hs;
    output  tft_vs;
    output  tft_de;
    output  tft_clk;
    output  tft_black;
    
//    hs_whole = 1904;    
//    hs_front_porch = 80;
//    hs_sync_pulse = 152;
//    hs_real_data = 1440  
//    hs_back_porch = 232                 
    
    
//    parameter  hs_whole = 1904;
//    parameter  hs_data_begin = 384;  //hs_sync_porch + hs_back_porch
//    parameter  hs_sync_pulse = 152;
//    parameter  hs_data_end = 1824; //hs_data_begin + hs_real_data

//    parameter  vs_whole = 932;
//    parameter  vs_data_begin = 31;  //hs_sync_porch + hs_back_porch
//    parameter  vs_sync_pulse = 3;
//    parameter  vs_data_end = 931; //hs_data_begin + hs_real_data
    
    parameter  hs_whole = 1056;    
    parameter  hs_data_begin = 216;
    parameter  hs_sync_pulse = 128;
    parameter  hs_data_end = 1016; 
                                   
    parameter  vs_whole = 525;     
    parameter  vs_data_begin = 27; 
    parameter  vs_sync_pulse = 2;  
    parameter  vs_data_end = 515; 

    reg [11:0]  hs_count_r;  //行列像素计数
    reg [11:0]  vs_count_r;
    
    //行计数到最大值清零
    always @(posedge clk_106m or negedge reset_n)
        if(!reset_n)
            hs_count_r <= 0;
        else if(hs_count_r == hs_whole - 1)
            hs_count_r <= 0;
        else
            hs_count_r <= hs_count_r + 1'd1;
            
        //列计数到行计数一行结束,到最大值清零
    always @(posedge clk_106m or negedge reset_n)
        if(!reset_n)
            vs_count_r <= 0;
        else if(hs_count_r == hs_whole - 1) begin
            if(vs_count_r == vs_whole - 1)
                vs_count_r <= 0;
            else
                vs_count_r <= vs_count_r + 1'd1;
        end
        else
            vs_count_r <= vs_count_r;
            
       //读数据请求
       always @(posedge clk_106m)
            data_req <= ((hs_count_r >= hs_data_begin) && (hs_count_r < hs_data_end ) 
                                && (vs_count_r >= vs_data_begin) && (vs_count_r < vs_data_end)) ? 1'b1:1'b0;
       reg  tft_de_s0;
       reg  tft_de_s1;         
       always @(posedge clk_106m) begin
            tft_de_s0 <= data_req;
            tft_de_s1 <= tft_de_s0;
       end
       assign   tft_de = tft_de_s1;
       
       always @(posedge clk_106m) begin
            hs_count = (data_req)?(hs_count_r - hs_data_begin):12'd0;
            vs_count = (data_req)?(vs_count_r - vs_data_begin):vs_count; 
            //列的实际图像的计数,保持,不然仿真会记一个数,变0一次
       end
       
       always @(posedge clk_106m) begin
            tft_data_out = (data_req)?tft_data_in:16'h0000;   
       end
       
       reg  tft_hs_s0;
       reg  tft_hs_s1;
       
       always @(posedge clk_106m) begin
            tft_hs_s0 <=   (hs_count_r <= hs_sync_pulse)?1'b0:1'b1;
            tft_hs_s1 <= tft_hs_s0;
       end
       assign      tft_hs  = tft_hs_s1;
       
       reg  tft_vs_s0;
       reg  tft_vs_s1;
       
       always @(posedge clk_106m) begin
            tft_vs_s0 <=  (vs_count_r <= vs_sync_pulse)?1'b0:1'b1;
            tft_vs_s1 <= tft_vs_s0;
       end
       assign      tft_vs  = tft_vs_s1;
//       assign   tft_hs = (hs_count_r <= hs_sync_pulse)?1'b0:1'b1;
//       assign   tft_vs = (vs_count_r <= vs_sync_pulse)?1'b0:1'b1;
//       assign   tft_data_out = (tft_de)?tft_data_in:24'h000000;
//       assign   hs_count = (tft_de)?(hs_count_r - hs_data_begin):12'd0; //实际的显示图像计数
//       assign   vs_count = (tft_de)?(vs_count_r - vs_data_begin):12'd0;
       assign   tft_clk=clk_106m;
       assign   tft_black = 1;
       
  
 endmodule

这边tft_ctrl的代码修改了一点时序,将一些阻塞赋值的换成了非阻塞赋值,因为TFT这边的数据请求,用的TFT这边的时钟是33M的,而RAM这边用的50M时钟,两边是异步信号

所以需要打拍,打两拍,这个已经很熟悉了。

always @(posedge clk_106m)
            data_req <= ((hs_count_r >= hs_data_begin) && (hs_count_r < hs_data_end ) 
                                && (vs_count_r >= vs_data_begin) && (vs_count_r < vs_data_end)) ? 1'b1:1'b0;

这个data_req就是请求,在TFT显示的图像实际输出位置,发起请求。

tft_hs和tft_vs的行列使能,也需要打拍。

 

 

 

posted @ 2024-03-22 17:17  祈愿树下  阅读(10)  评论(0编辑  收藏  举报
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css