十五、串口發送圖片數據到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控制信號的真值表
接下來分析SRAM的讀寫時序圖,兩種類型的讀操作時序如圖1(a)和圖1(b)所示
(a)地址控制的讀周期時序圖(ce_n=0,we_n=1,oe_n=0)
(b)oe_n控制的讀周期時序圖
(c)部分時序參數的介紹
圖1 讀操作的時序圖和部分參數
本實驗數據用的是16位,所以lb_n和ub_n控制位我們一直給低電平即可。關於ce_n控制位在復位后一直給低電平即可。
芯片手冊上關於寫操作時序有四種類型,這里就簡單介紹其中一種,其他的類似,寫操作時序如圖2所示:
(a)寫操作時序圖
(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
仿真結果如下:
寫操作控制信號放大后波形如下:
讀操作控制信號放大后波形如下:
這里需要說明一下,就是讀操作讀出的數據沒有值,主要是沒有真正的接SRAM,還沒想到怎么去驗證讀數據,但是仿真結果可以看出,讀寫時序與按預期設計的一致。如果想進一步進行板級驗證,也是可以的,這就需要使用SignalTap II Logic Analyzer工具對寫入的數據和讀取的數據進行抓取和比較,從而判斷控制驅動設計的對錯,具體的操作后面會提到。關於SRAM的控制驅動就說這么多,其他的可以參考芯片手冊做更進一步的設計,本人經驗不足,還望前輩們批評指正。
接下來還是進入今天的主題,就是通過串口的傳圖片數據到SRAM,然后通過讀取SRAM的圖片數據在tft上顯示完整的圖片,主要是解決上次通過讀rom數據顯示圖片不能顯示整個tft屏的問題。主要的設計框圖如下:
框圖中除了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
這個代碼根據串口接收模塊的不同稍有差別,主要是是看你設計的串口接收模塊接收完成標志位,輸出數據的時序關系,大概有兩種不同的時序,如下圖所示:
本實驗串口接收模塊的時序是右邊的圖,如果是左邊的時序圖,上述代碼需要做相應的修改,主要是產生合成數據標志位有所變化,此時標志位就直接為data8bit,不用延時一時鍾周期,具體時序如下圖所示:
兩種不同的時序稍有差別,總的思路是一樣的,具體實現可根據實際的情況而定。
接下來就是向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
仿真結果如下:
可以看到數據的合成和寫SRAM數據和地址與設計的是相符的。由於要看到讀數據的地址,需要的時間較長,在編寫tb時,將最大串口接收數據量改小進行仿真得到讀取SRAM數據部分的仿真波形如下:
從上面的波形可以看出數據讀取的地址波形與預期一致,我們還發現其地址改變的位置與屏的驅動時鍾的上升沿並沒有對齊,這個好像沒有影響,看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行。
仿真波形如下:
以上圖片是串口傳數據,然后將數據寫入SRAM的波形,與預期設計效果一致。
有關讀數據的仿真由於仿真過程沒有實際SRAM讀出的數據,只能看讀地址的波形和地址的變化。這個地方沒有想到好的仿真方法。
板級驗證,引腳分配按照梅哥發的文檔引腳一一對應分配好即可,分配表如下:
下載后進行板級驗證,在此之前我們先配置一個SignalTap II Logic Analyzer的文件,這樣可以方便我們驗證SRAM寫入和讀取數據的對錯,以及一張圖片數據是否寫完。具體的關於這個配置,小梅哥的視頻上有講,我的配置如下圖所示:
創建好,保存后重新編譯,下載,然后再打開SignalTap II Logic Analyzer,讓其一直處於循環的抓捕狀態。以下是剛復位后的狀態,此時開發板的led0也處於滅的狀態。
打開串口軟件,我使用的是友善串口調試助手,這個因人而異,有的串口軟件不好用可以換其他的,總有一款適合你,以下是我用的串口軟件:
串口設置與我們設計的串口接收模塊的設置保持一致,程序里波特率設置的位115200,圖片數據輸入是將圖片數據復制在下面紅色空中的,最開始是想着直接發送數據文件的,后來發現文件好像默認是把一位16進制數當成了兩個字節,例如一字節的0xFF,在文件里就成了2個字節,如下圖所示,實際261120字節大小的數據,放入文本文檔中就變成了522240字節大小
這樣將我們要發送的數據量變成了原有的兩倍導致錯誤。我是直接復制數據粘貼在紅框中發送的,反應有點慢,不影響最后的結果。在數據傳輸過程中我們可以在SignalTap II Logic Analyzer工具中看到寫入和讀取SRAM數據的過程,我截取了寫數據和讀數據過程中的兩幅圖如下:
板級驗證結果如下:
在串口數據傳輸完成后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