当前位置: 首页 > backend >正文

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;
elseram_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;//行同步的信号最大值为1056output 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;
elsehcount_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)  beginif(vcount_r == vpixel_end)  vcount_r <= 11'd0;elsevcount_r <= vcount_r + 1'd1;end
elsevcount_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)beginTFT_DE_r[0] <= data_req;TFT_DE_r[3:1] <=  TFT_DE_r[2:0];endassign TFT_DE =  TFT_DE_r[2];         always@(posedge clk)beginhcount <= data_req ? (hcount_r - hdat_begin) :10'd0;vcount <= data_req ? (vcount_r - vdat_begin) :10'd0;
endalways@(posedge clk)beginTFT_DATA <= (data_req)? data_in: 16'h0000;end reg [3:0]TFT_HS_r;//同样打两拍
always@(posedge clk)beginTFT_HS_r[0] = (hcount_r > TFT_HS_end) ?1'b1:1'b0;TFT_HS_r[3:1] <=  TFT_HS_r[2:0];endassign TFT_HS =  TFT_HS_r[2];      reg [3:0]TFT_VS_r;//同样打两拍
always@(posedge clk)beginTFT_VS_r[0] = (vcount_r > TFT_VS_end) ?1'b1:1'b0;TFT_VS_r[3:1] <=  TFT_VS_r[2:0];endassign 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串口发送工具将图片文件发送即可使用!!!



 

http://www.xdnf.cn/news/3447.html

相关文章:

  • 【Android】四大组件之BroadcastReceiver
  • Lucene并不是只有倒排索引一种数据结构,支持多种数据结构
  • react学习笔记3——基于React脚手架
  • 杜邦分析法
  • Android12 Rom定制设置默认语言为中文
  • 如何拿奖蓝桥杯
  • 电机常用易混淆概念说明(伺服、舵机、多轮)
  • 【CV数据集】Visdrone2019无人机目标检测数据集(YOLO、VOC、COCO格式)
  • 2025五一数学建模竞赛B题完整分析论文(共42页)(含模型、可运行代码、数据)
  • python绘制全球ERA5再分析数据10m风速产品
  • Python 装饰器基础知识科普
  • 置换密码程序设计
  • GitHub 趋势日报 (2025年04月30日)
  • 算法-二分查找
  • archlinux wine 运行windows程序
  • css中盒模型有哪些
  • 前端八股 7
  • 如何让Steam下载速度解除封印?!
  • 渗透测试中的那些“水洞”:分析与防御
  • 【Game】Powerful——Abandoned Ruins(9)
  • node.js模块化步骤(各标准区别)CommonJS规范、AMD规范、UMD规范、ES Modules (ESM)
  • qemu(4) -- qemu-system-arm使用
  • 三生原理的离散生成逻辑如何与复分析结合?
  • 2025大模型微调视频课程全套(附下载)
  • WPF之Image控件详解
  • Sentry 异常捕获
  • 第 2.3 节: 基于 Python 的关节空间与任务空间控制
  • AUTOSAR图解==>AUTOSAR_RS_TimingExtensions
  • Rerank详解
  • C++初阶-string类3