新闻中心

EEPW首页>嵌入式系统>设计应用> FPGA:图形 LCD 面板- 文本

FPGA:图形 LCD 面板- 文本

作者: 时间:2024-01-15 来源:EEPW编译 收藏

图形 LCD 面板 4 - 文本

让我们尝试在面板上显示字符。 这样,面板就可以用作文本终端。
我们的 480x320 示例面板可用作 80 列 x 40 行控制台(使用 6x8 字符字体)或 60 列 x 40 行控制台(使用 8x8 字符字体)。 我们将使用“字符生成器”技术。

本文引用地址: //m.amcfsurvey.com/article/202401/454790.htm

字符生成器

让我们假设“你好”这个词在屏幕上的某个地方。在 ASCII 中,它使用 5 个字节(0x48、0x65、0x6C、0x6C、0x6F)。 我们的简单字符生成器使用一个 RAM 来保存要显示的字符,并使用一个 ROM 来保存字体。

“Font ROM”包含每个可能字符的表示形式。 以下是示例集:

更改“Character RAM”的内容会使字符出现在面板上。

8x8 字体实现

在面板上,6x8 字体看起来比 8x8 字体好一些,但 8x8 更容易实现,所以我们首先尝试了。

通过使用 2KB RAM 作为“字符 RAM”,我们可以有 32 行 64 个字符......所以 5 位来计算行数,6 位来计算列数。
通过将所有数字保持为 <> 的幂,实现尽可能简单。

以下是我们得到的:

我们使用 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];

一些细节:

  • 因为我的 LCD 面板每个时钟需要 4 个像素,所以我们需要 2 个时钟来表示 1 个字符宽度(1 个字符宽度 = 8 像素)。这就是为什么“角色RAM”使用“CounterX[6:1]”而不是上面的“CounterX[5:0]”的原因。

  • 中,“字体ROM”实际上是一个RAM是有道理的。这样,可以在运行时加载或更改字体。

  • 上面未显示写入 RAM 的机制。

这是带有 8x8 字体的 LCD 的照片:

6x8 字体实现

6x8 字体允许显示更多字符(并且在面板上看起来也更好! 我的特定面板宽度为 480 像素,可以方便地转换为 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

带有模数 3 计数器(计数 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”的字符计数器

// 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 的照片:

硬件光标

让我们实现一个闪烁的光标,它可以放置在面板上的任何字符上。

首先,我们使用视频帧计数器来“计时”闪烁。 使用 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

现在,我们假设游标地址位置在“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 上得意忘形时会发生什么......

轮到你来实验了!



关键词:FPGA图形LCD面板

评论


相关推荐

技术专区

关闭