FPGA:圖形 LCD 面板- 文本
圖形 LCD 面板 4 - 文本
讓我們嘗試在面板上顯示字符。 這樣,面板就可以用作文本終端。
我們的 480x320 示例面板可用作 80 列 x 40 行控制臺(使用 6x8 字符字體)或 60 列 x 40 行控制臺(使用 8x8 字符字體)。 我們將使用“字符生成器”技術(shù)。
字符生成器
讓我們假設“你好”這個詞在屏幕上的某個地方。在 ASCII 中,它使用 5 個字節(jié)(0x48、0x65、0x6C、0x6C、0x6F)。 我們的簡單字符生成器使用一個 RAM 來保存要顯示的字符,并使用一個 ROM 來保存字體。
“Font ROM”包含每個可能字符的表示形式。 以下是示例集:
更改“Character RAM”的內(nèi)容會使字符出現(xiàn)在面板上。
8x8 字體實現(xiàn)
在面板上,6x8 字體看起來比 8x8 字體好一些,但 8x8 更容易實現(xiàn),所以我們首先嘗試了。
通過使用 2KB RAM 作為“字符 RAM”,我們可以有 32 行 64 個字符......所以 5 位來計算行數(shù),6 位來計算列數(shù)。
通過將所有數(shù)字保持為 <> 的冪,實現(xiàn)盡可能簡單。
以下是我們得到的:
我們使用 CounterX 的 6 位和 CounterY 的 8 位(“字符 RAM”為 5 位,加上“字體 ROM”為 3 位)。 “字體ROM”總共使用11位。
設計如下:
wire [7:0] CharacterRAM_dout; ram8x2048 CharacterRAM( .clk(clk), .rd_adr({CounterY[7:3],CounterX[6:1]}), .data_out(CharacterRAM_dout) ); wire [7:0] raster8; rom8x2048 FontROM( .clk(clk), .rd_adr({CharacterRAM_dout, CounterY[2:0]}), .data_out(raster8) ); wire [3:0] LCDdata = CounterX[0] ? raster8[7:4] : raster8[3:0];
一些細節(jié):
因為我的 LCD 面板每個時鐘需要 4 個像素,所以我們需要 2 個時鐘來表示 1 個字符寬度(1 個字符寬度 = 8 像素)。這就是為什么“角色RAM”使用“CounterX[6:1]”而不是上面的“CounterX[5:0]”的原因。
在FPGA中,“字體ROM”實際上是一個RAM是有道理的。這樣,可以在運行時加載或更改字體。
上面未顯示寫入 RAM 的機制。
這是帶有 8x8 字體的 LCD 的照片:
6x8 字體實現(xiàn)
6x8 字體允許顯示更多字符(并且在面板上看起來也更好! 我的特定面板寬度為 480 像素,可以方便地轉(zhuǎn)換為 80 列。
6x8 字體比 8x8 字體處理起來更復雜,因為字體寬度 (6) 不是 2 的冪。
這意味著我們不再在每個時鐘周期中顯示字符的相同部分。
當 CounterX=0 時,我們顯示 CHAR[4] 的前 0 個像素
當 CounterX=1 時,我們顯示 CHAR[2] 的最后 0 個像素和 CHAR[2] 的前 1 個像素
當 CounterX=2 時,我們顯示 CHAR[4] 的最后 1 個像素
當 CounterX=3 時,我們顯示 CHAR[4] 的前 2 個像素
當 CounterX=4 時,我們顯示 CHAR[2] 的最后 2 個像素和 CHAR[2] 的前 3 個像素
...
這是使用簡單的 case 語句完成的
wire [3:0] charfont0, charfont1; always @(posedge clk)begin case(cnt_mod3) 2'b00: LCDdata <= charfont0; 2'b01: LCDdata <= {charfont0[3:2], charfont1[3:2]}; 2'b10: LCDdata <= {charfont1[3:2], charfont0[1:0]}; endcase end
帶有模數(shù) 3 計數(shù)器(計數(shù) 0、1、2、0、1、2、0、1 等)
reg [1:0] cnt_mod3; always @(posedge clk) if(cnt_mod3==2) cnt_mod3 <= 0; else cnt_mod3 <= cnt_mod3 + 1;
但我們還需要一個用于“字符RAM”的字符計數(shù)器
// character-counter (increments only twice every 3 clocks) reg [6:0] cnt_charbuf; always @(posedge clk) if(cnt_mod3!=1) cnt_charbuf <= cnt_charbuf + 1; wire [11:0] CharacterRAM_rdaddr = CounterY[8:3]*80 + cnt_charbuf; wire [7:0] CharacterRAM_dout;ram8x2048 CharacterRAM( .clk(clk), .rd_adr(CharacterRAM_rdaddr), .data_out(CharacterRAM_dout) );
和兩個“字體ROM”。
// remember the previous character displayed reg [7:0] RAM_charbuf_dout_last; always @(posedge clk) CharacterRAM_dout_last <= CharacterRAM_dout; // because we need it when we display 2 half characters at once wire [10:0] readaddr0 = {CharacterRAM_dout, CounterY[2:0]}; wire [10:0] readaddr1 = {CharacterRAM_dout_last, CounterY[2:0]}; // The font ROMs are split in two blockrams, holding 4 pixels each // (half of the second ROM is not used, since we need only 6 pixels) rom4x2048 FontROM0(.clk(clk), .rd_adr(readaddr0), .data_out(charfont0)); rom4x2048 FontROM1(.clk(clk), .rd_adr(readaddr1), .data_out(charfont1));
這是帶有 6x8 字體的 LCD 的照片:
硬件光標
讓我們實現(xiàn)一個閃爍的光標,它可以放置在面板上的任何字符上。
首先,我們使用視頻幀計數(shù)器來“計時”閃爍。 使用 6 位,光標每 64 幀閃爍一次。
reg [5:0] cnt_cursorblink; always @(posedge clk) if(vsync & hsync) cnt_cursorblink <= cnt_cursorblink + 1; wire cursorblinkstate = cnt_cursorblink[5]; // cursor on for 32 frames, off for 32 frames
現(xiàn)在,我們假設游標地址位置在“CursorAddress”寄存器中可用。
// Do we have a cursor-character match? wire cursorblink_adrmatch = cursorblinkstate & (CharacterRAM_rdaddr==CursorAddress); // When we have a match, "invert" the character // First for charfont0 wire [3:0] charfont0_cursor = charfont0 ^ {4{cursorblink_adrmatch}}; // Next for charfont1 reg cursorblink_adrmatch_last; always @(posedge clk) cursorblink_adrmatch_last <= cursorblink_adrmatch; wire [3:0] charfont1_cursor = charfont1 ^ {4{cursorblink_adrmatch_last}};
哇,我們可以顯示圖形和文本!
所有這些都適用于單色和彩色 LCD。
當我們在彩色 LCD 上得意忘形時會發(fā)生什么......
輪到你來實驗了!
評論