FPGA中级项目8———UART-RAM-TFT

FPGA中级项目8---------UART-RAM-TFT

UART串口我们学过,RAM IP核学过,TFT同样也学过。那如何将它们联合起来呢?
言简意赅:实现从串口写入图像到RAM并且由TFT显示屏输出!

首先第一步,便是要将UART_RX与RAM之间架起桥梁,也就是我们要写一个控制器,其需要的接口如下

需要串口端的数据导入,一个字节的数据导入完成标志;RAM端的写使能信号,写入地址,写入数据。

问题一

特别需要注意的是,在本节内容中,选用的RAM核为16位宽的,同时有16根地址线。

RAM 存储容量 :一个 16 位宽的 RAM,意味着它每次读写操作的数据宽度是 16 位(也就是 2 字节)。如果这个 RAM 的地址线有 16 根(因为 (2^16=65536) ),那么它的存储深度就是 65536 个存储单元。每个存储单元可以存放 16 位(2 字节)的数据。所以这个 RAM 的总存储容量是131072 字节 = 128KB 。

像素点与数据关系 :一个像素点用 16 位(2 字节)来表示,那么这个 128KB 容量的 RAM 正好可以存放 65536 个像素点的数据。

所以需要设立rx_down信号,来进行计数直到一幅图像数据 65536 完成。

问题二

同时定义rx_data_tmp寄存器是很有必要的,不能直接用ram_wrdata,原因有下:
1. 数据组装需求

UART 串口每次传输一个字节(8 位)的数据,而 RAM 需要以 16 位(两个字节)为单位写入数据,也就是一个像素点的数据。这就需要将两个连续接收到的字节组合成一个 16 位的数据。

rx_data_tmp寄存器起到了临时存储和组装数据的作用。每次接收到一个新的字节rx_data时,它会把之前存储的低 8 位数据和新的rx_data组合起来,形成一个完整的 16 位数据。

2. 信号类型和赋值规则

ram_wrdata是一个wire类型的信号,它是通过assign语句进行赋值的,不能在always块中直接修改其值。assign语句通常用于组合逻辑的赋值,而rx_data_tmp的更新是时序逻辑,需要在always块中完成。如果直接使用ram_wrdata,就无法在always块中对其进行更新,也就无法实现数据的正确组装。

3. 写使能和数据同步

写使能信号ram_wren是在接收到第二个字节时才有效,这意味着只有在两个字节都接收完成后,才会将组合好的 16 位数据写入 RAM。rx_data_tmp在接收到每个字节时都会更新,但只有在写使能有效时,ram_wrdata才会将组合好的数据输出到 RAM。

控制器代码如下所示:

代码展示

复制代码
//输入输出模块
module img_rx_wr(
        clk,
        reset_n,
        rx_data,//串口的导入数据
        rx_down,//串口接受到一个字节的标志信号
        ram_wren,//写使能
        ram_wraddr,//地址
        ram_wrdata,//输出给RAM的数据
        led      
    );
        input clk;
        input reset_n;
        input [7:0]rx_data;
        input rx_down;
        output reg ram_wren;
        output reg [15:0]ram_wraddr;
        output wire [15:0]ram_wrdata;
        output reg led;
        
        
        
//统计串口接受的计数器计数,看是否达到了128k个数据传输     
reg [16:0]data_cnt ;   
always@(posedge clk or negedge reset_n)
if(!reset_n)
        data_cnt <= 0;
else if(rx_down)
        data_cnt <= data_cnt +1'd1;
    
    
//定义16位寄存器,用于存放一个像素点也就是两个字节
reg [15:0]rx_data_tmp;
always@(posedge clk or negedge reset_n)
if(!reset_n)
        rx_data_tmp <= 0;
else if(rx_down) 
        rx_data_tmp <= {rx_data_tmp[7:0],rx_data};

//写使能逻辑,利用相与
always@(posedge clk or negedge reset_n)
if(!reset_n)
        ram_wren <= 0;
else if(rx_down && data_cnt[0])//利用奇数末尾为1,相与为1,表明写使能有效
        ram_wren <= 1'd1;
else
        ram_wren <= 0;

//将数据位置写入地址,2个数据一个地址,采用舍弃低位方式
always@(posedge clk or negedge reset_n)
if(!reset_n)
        ram_wraddr <= 0;
else if(rx_down && data_cnt[0])//也就是写使能
        ram_wraddr <= data_cnt[16:1];


assign ram_wrdata = rx_data_tmp;


//led灯翻转逻辑,也就是串口传输完完整数据后翻转
always@(posedge clk or negedge reset_n)
if(!reset_n)
        led <= 0;
else if(data_cnt == 131071 && rx_down)
        led <= ~led;
    
    
    
endmodule

实现了以上控制器逻辑之后,我们便需要来编写顶层模块了,也就是将之前学过的相关模块例化并连接。

问题一

要创建一个MMCN IP核创建33M频率的时钟clk_TFT,来实现各模块的时钟同步。

问题二

要创建RAM IP核实现上述要求

问题三

要对原先的TFT模块进行微微修改,也就是要创建一个TFT数据请求输出data_req,用来输出TFT的数据时间段,也就是将原先的TFT_DE的打拍操作,同时将TFT-TS和TFT-VS打两拍,从而消除亚稳态。

TFT代码展示

复制代码
//定义输入输出端口
module TFT(
        clk,
        reset_n,
        data_in,//用户输入数据
        data_req,
        TFT_HS,//行同步信号
        TFT_VS,//场同步信号
        hcount,//行扫描位置
        vcount,//场扫描位置
        TFT_DE,//数据输出时间段
        TFT_CLK,
        TFT_DATA,
        TFT_BL
    );
        input clk;
        input reset_n;
        input [15:0]data_in;
        output reg data_req;
        output TFT_HS;
        output TFT_VS;
        output reg [11:0]hcount;//行同步的信号最大值为1056
        output reg [11:0]vcount;
        output TFT_DE;
        output TFT_CLK;
        output reg [15:0]TFT_DATA;
        output TFT_BL;



// 定义不同的分辨率
//`define resolution_480x272 1 // 时钟为9MHz
//`define resolution_640x480 1 // 时钟为25MHz
`define resolution_800x480 1 // 时钟为33MHz
//`define resolution_800x600 1 // 时钟为40MHz
//`define resolution_1024x600 1 // 时钟为51MHz
//`define resolution_1024x768 1 // 时钟为65MHz
//`define resolution_1280x720 1 // 时钟为74.25MHz
//`define resolution_1920x1080 1 // 时钟为148.5MHz

`ifdef resolution_480x272
    `define h_right_border 0
    `define h_front_porch 2
    `define h_sync_time 41
    `define h_back_porch 2
    `define h_left_border 0
    `define h_data_time 480
    `define h_total_time 525

    `define v_bottom_border 0
    `define v_front_porch 2
    `define v_sync_time 10
    `define v_back_porch 2
    `define v_top_border 0
    `define v_data_time 272
    `define v_total_time 286

`elsif resolution_640x480
    `define h_right_border 0
    `define h_front_porch 16
    `define h_sync_time 96
    `define h_back_porch 48
    `define h_left_border 0
    `define h_data_time 640
    `define h_total_time 800

    `define v_bottom_border 0
    `define v_front_porch 10
    `define v_sync_time 2
    `define v_back_porch 33
    `define v_top_border 0
    `define v_data_time 480
    `define v_total_time 525

`elsif resolution_800x480
    `define h_right_border 0
    `define h_front_porch 40
    `define h_sync_time 128
    `define h_back_porch 88
    `define h_left_border 0
    `define h_data_time 800
    `define h_total_time 1056

    `define v_bottom_border 8
    `define v_front_porch 2
    `define v_sync_time 2
    `define v_back_porch 25
    `define v_top_border 8
    `define v_data_time 480
    `define v_total_time 525

`elsif resolution_800x600
    `define h_right_border 0
    `define h_front_porch 40
    `define h_sync_time 128
    `define h_back_porch 88
    `define h_left_border 0
    `define h_data_time 800
    `define h_total_time 1056

    `define v_bottom_border 0
    `define v_front_porch 1
    `define v_sync_time 4
    `define v_back_porch 23
    `define v_top_border 0
    `define v_data_time 600
    `define v_total_time 628

`elsif resolution_1024x600
    `define h_right_border 0
    `define h_front_porch 24
    `define h_sync_time 136
    `define h_back_porch 160
    `define h_left_border 0
    `define h_data_time 1024
    `define h_total_time 1344

    `define v_bottom_border 0
    `define v_front_porch 1
    `define v_sync_time 3
    `define v_back_porch 28
    `define v_top_border 0
    `define v_data_time 600
    `define v_total_time 632

`elsif resolution_1024x768
    `define h_right_border 0
    `define h_front_porch 24
    `define h_sync_time 136
    `define h_back_porch 160
    `define h_left_border 0
    `define h_data_time 1024
    `define h_total_time 1344

    `define v_bottom_border 0
    `define v_front_porch 3
    `define v_sync_time 6
    `define v_back_porch 29
    `define v_top_border 0
    `define v_data_time 768
    `define v_total_time 806

`elsif resolution_1280x720
    `define h_right_border 0
    `define h_front_porch 110
    `define h_sync_time 40
    `define h_back_porch 220
    `define h_left_border 0
    `define h_data_time 1280
    `define h_total_time 1650

    `define v_bottom_border 0
    `define v_front_porch 5
    `define v_sync_time 5
    `define v_back_porch 36
    `define v_top_border 0
    `define v_data_time 720
    `define v_total_time 750

`elsif resolution_1920x1080
    `define h_right_border 0
    `define h_front_porch 88
    `define h_sync_time 44
    `define h_back_porch 148
    `define h_left_border 0
    `define h_data_time 1920
    `define h_total_time 2200

    `define v_bottom_border 0
    `define v_front_porch 4
    `define v_sync_time 5
    `define v_back_porch 36
    `define v_top_border 0
    `define v_data_time 1080
    `define v_total_time 1125

`endif    

 
 //定义时序中相关信号       
        //parameter VGA_HS_end =  11'd127;
        //parameter hdat_begin = 11'd216;//行数据开始输出位置
        //parameter hdat_end = 11'd1016;//行数据停止输出位置
        //parameter hpixel_end = 11'd1055;//行扫描的最大位置处
        
        //parameter VGA_VS_end =  11'd1;
        //parameter vdat_begin = 11'd35;
        //parameter vdat_end = 11'd515;
        //parameter vpixel_end = 11'd524; 
                   
//将上述的parameter定义改为参数定义,便于适配
        parameter  TFT_HS_end = `h_sync_time - 1,
                    hdat_begin = `h_sync_time + `h_back_porch + `h_left_border,
                    hdat_end = `h_sync_time + `h_back_porch + `h_left_border + `h_data_time,
                    hpixel_end = `h_total_time - 1,
                    TFT_VS_end = `v_sync_time - 1,
                    vdat_begin = `v_sync_time + `v_back_porch + `v_top_border,
                    vdat_end = `v_sync_time + `v_back_porch + `v_top_border + `v_data_time,
                    vpixel_end = `v_total_time - 1;
                  
                  
 
 //定义计数器,开始行扫描信号,场扫描信号计数       
        reg [11:0]hcount_r;
        reg [11:0]vcount_r;
always@(posedge clk or negedge reset_n)
if(!reset_n)
        hcount_r <= 11'd0;
else if(hcount_r  == hpixel_end)       
        hcount_r <= 11'd0;
else
        hcount_r <= hcount_r + 1'd1;
    
    
always@(posedge clk or negedge reset_n)
if(!reset_n) 
        vcount_r <= 11'd0;
else if(hcount_r  == hpixel_end)  begin
        if(vcount_r == vpixel_end)  
        vcount_r <= 11'd0;
        else
        vcount_r <= vcount_r + 1'd1;
        end
else
        vcount_r <= vcount_r;
                
assign  TFT_CLK = ~clk;  
                
                
always@(posedge clk)
 data_req <=  ((hcount_r >= hdat_begin) &&(hcount_r < hdat_end ) &&(vcount_r >= vdat_begin )&&(vcount_r < vdat_end))?1'b1:1'b0;             
 
 
reg [3:0]TFT_DE_r;//将data_req打两拍
always@(posedge clk)begin
       TFT_DE_r[0] <= data_req;
       TFT_DE_r[3:1] <=  TFT_DE_r[2:0];
       end
              
assign TFT_DE =  TFT_DE_r[2];         


always@(posedge clk)begin
        hcount <= data_req ? (hcount_r - hdat_begin) :10'd0;
        vcount <= data_req ? (vcount_r - vdat_begin) :10'd0;
end

always@(posedge clk)begin
        TFT_DATA <= (data_req)? data_in: 16'h0000;
        end 



reg [3:0]TFT_HS_r;//同样打两拍
always@(posedge clk)begin
       TFT_HS_r[0] = (hcount_r > TFT_HS_end) ?1'b1:1'b0;
       TFT_HS_r[3:1] <=  TFT_HS_r[2:0];
       end
              
assign TFT_HS =  TFT_HS_r[2];      


reg [3:0]TFT_VS_r;//同样打两拍
always@(posedge clk)begin
       TFT_VS_r[0] = (vcount_r > TFT_VS_end) ?1'b1:1'b0;
       TFT_VS_r[3:1] <=  TFT_VS_r[2:0];
       end
              
assign TFT_VS =  TFT_VS_r[2];  
              
              
//定义相关信号    
//assign  TFT_HS = (hcount_r > TFT_HS_end) ?1'b1:1'b0;
//assign  TFT_VS = (vcount_r > TFT_VS_end) ?1'b1:1'b0;  
//assign  TFT_DE =((hcount_r >= hdat_begin) &&(hcount_r < hdat_end ) &&(vcount_r >= vdat_begin )&&(vcount_r < vdat_end))?1'b1:1'b0;
//assign  hcount = TFT_DE ? (hcount_r - hdat_begin) :10'd0;
//assign  vcount = TFT_DE ? (vcount_r - vdat_begin) :10'd0;
//assign  data_out = (TFT_DE) ? data_in : 24'h000000;
//assign  TFT_CLK = ~clk; 
assign  TFT_BL = 1;   
    

    
endmodule

顶层模块代码展示

复制代码
//输入输出模块
module UART_RAM_TFT(
        clk,
        reset_n,
        uart_rx,
        TFT_RGB,//tft数据输出
        TFT_HS,//TFT行同步信号
        TFT_VS,//TFT场同步信号
        TFT_DE,//TFT数据有效信号
        TFT_CLK,
        TFT_BL,//TFT背光
        led
    );
        input clk;
        input reset_n;
        input uart_rx;
        output [15:0]TFT_RGB;
        output TFT_HS;
        output TFT_VS;
        output TFT_DE;
        output TFT_CLK;
        output TFT_BL;
        output led;
 
 
//定义相关变量
        wire [7:0]rx_data;
        wire rx_down;
        
        wire ram_wren;//RAM 的写使能信号,高电平有效时允许向 RAM 写入数据。
        wire [15:0]ram_wraddr;//RAM 的写地址信号,用于指定写入数据的存储位置。
        wire [15:0]ram_wrdata;//要写入 RAM 的数据
       
        reg [15:0]ram_rdaddr;//用于存储从 RAM 中读取数据的地址,是一个寄存器类型,因为它需要在always块中被赋值和更新。
        wire clk_TFT;//TFT 显示屏的时钟信号,由 MMCM 模块生成。
        wire [15:0]ram_rddata;//从 RAM 中读取出来的 16 位数据。
        
        wire [11:0]hcount,vcount;
        wire ram_data_en;//用于控制是否从 RAM 中读取数据的使能信号
        wire [15:0]disp_data;//最终要显示在 TFT 显示屏上的数据,根据ram_data_en信号从 RAM 中读取或赋值为 0。
        wire locked; 
 
 
 //例化相关模块      
        MMCM MMCM
   (
    // Clock out ports
    .clk_out1(clk_TFT),     // output clk_out1
    // Status and control signals
    .reset(!reset_n), // input reset
    .locked(locked),       // output locked
   // Clock in ports
    .clk_in1(clk));    
    
   
   uart_rx1 uart_rx1(
       .clk(clk),
       .reset_n(reset_n),
       .uart_rx(uart_rx),
       .rx_data(rx_data),
       .rx_down(rx_down)       
    );
   
    
    img_rx_wr img_rx_wr(
        .clk(clk_TFT),
        .reset_n(reset_n),
        .rx_data(rx_data),//串口的导入数据
        .rx_down(rx_down),//串口接受到一个字节的标准信号
        .ram_wren(ram_wren),//写使能
        .ram_wraddr(ram_wraddr),//地址
        .ram_wrdata(ram_wrdata),//输出给RAM的数据
        .led(led)      
    );
    
    
    RAM RAM (
  .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_TFT),    // input wire clkb
  .enb(1),      // input wire enb
  .addrb(ram_rdaddr),  // input wire [15 : 0] addrb
  .doutb(ram_rddata)  // output wire [15 : 0] doutb
);
 
 
 //RAM中存储的图像是256*256像素矩阵,取完一个数据地址加一
         wire data_req;
always@(posedge clk_TFT 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  disp_data =  ram_data_en ? ram_rddata : 0;  
    
    
 TFT TFT(
        .clk(clk_TFT),
        .reset_n(reset_n),
        .data_in(disp_data),//用户输入数据
        .data_req(data_req),
        .TFT_HS(TFT_HS),//行同步信号
        .TFT_VS(TFT_VS),//场同步信号
        .hcount(hcount),//行扫描位置
        .vcount(vcount),//场扫描位置
        .TFT_DE(TFT_DE),//数据输出时间段
        .TFT_CLK(TFT_CLK),
        .TFT_DATA(TFT_RGB),
        .TFT_BL(TFT_BL)
    );   
    
    
    
endmodule

同时需要必须的一些小文件来进行图像输入操作,例如网友创建的COE文件程序"BMP2MIF"可将图像BMP文件转换为COE格式输入到RAM中,从而在TFT屏幕显示。

最后利用UART串口发送工具将图片文件发送即可使用!!!


相关推荐
计算机毕设定制辅导-无忧学长1 小时前
ActiveMQ 性能优化与网络配置实战(二)
网络·性能优化·activemq
hgdlip1 小时前
怎么查自己手机连接的ip归属地:完整指南
网络·tcp/ip·web安全·手机·ip归属地
同聘云2 小时前
网络安全防火墙技术有哪些?网络防火墙的主要作用
网络
孞㐑¥2 小时前
C++之特殊类设计及类型转换
开发语言·c++·经验分享·笔记
ONETHING_CLOUD_22 小时前
高性价比手机如何挑选?
经验分享·科技·智能手机·数码
美好的事情总会发生3 小时前
晶振PCB设计核心要点与规范
嵌入式硬件·硬件工程·智能硬件
whaosoft-1433 小时前
51c嵌入式~单片机~合集9
嵌入式硬件
2401_835261384 小时前
网络原理初识
网络·智能路由器
caolib4 小时前
1.计算机网络概述
网络·计算机网络
夜月yeyue5 小时前
CCM/TCM在STM32中的含义和用途
stm32·单片机·嵌入式硬件