用FPGA驱动LCD1602显示自定义字符:从原理到Verilog代码实战(附完整工程)

FPGA驱动LCD1602显示自定义字符:从CGRAM原理到Verilog实战

在嵌入式显示领域,LCD1602因其经典可靠而广受欢迎。但大多数开发者仅停留在调用内置字符的层面,当需要显示温度符号、单位标志或简单图标时往往束手无策。本文将揭示如何通过FPGA操控LCD1602的CGRAM(Character Generator RAM)实现完全自定义字符显示,并以"°C"温度符号为例展示完整开发流程。

1. CGRAM工作机制深度解析

LCD1602内置的CGROM存储了160个预定义字符(包括字母、数字和日文假名),而8个可编程的CGRAM位置才是实现个性化的关键。每个CGRAM位置对应一个5x8点阵字符,其工作原理如下:

  • 地址映射:CGRAM地址范围0x00-0x3F,每8字节定义一个字符(实际使用前5位)
  • 数据格式:每个字节代表一行点阵,LSB对应最右侧像素。例如定义"°"符号:
    8'b00110, // 第1行 8'b01001, // 第2行 8'b01001, // 第3行 8'b00110, // 第4行 8'b00000, // 第5行 8'b00000, // 第6行 8'b00000, // 第7行 8'b00000 // 第8行
  • 调用机制:写入DDRAM时使用0x00-0x07地址即可调用对应CGRAM字符

注意:CGRAM内容会在断电后丢失,每次上电需重新写入。部分型号LCD在初始化时会清空CGRAM。

2. 硬件接口与时序优化

在原有驱动框架基础上扩展CGRAM功能,需特别注意时序控制:

信号线方向CGRAM操作时的状态
RSFPGA→LCD0-写指令;1-写数据
RWFPGA→LCD保持低电平(写模式)
EFPGA→LCD高脉冲宽度>450ns
DB0-DB7FPGA→LCD指令/数据总线

关键操作序列:

  1. 设置CGRAM地址指令(0x40|addr)
  2. 连续写入8字节点阵数据
  3. 返回DDRAM模式(0x80|addr)
// CGRAM写入状态机片段示例 parameter SET_CGRAM_ADDR = 4'd10; parameter WRITE_CGRAM = 4'd11; case(state) SET_CGRAM_ADDR: begin lcd_rs <= 0; lcd_data <= 8'h40; // 设置CGRAM地址基址 state <= WRITE_CGRAM; end WRITE_CGRAM: begin lcd_rs <= 1; lcd_data <= custom_char[char_line]; if(char_line == 3'd7) begin state <= NEXT_STATE; end end endcase

3. 完整温度符号实现方案

3.1 自定义字符设计

实现"°C"显示需要两个自定义字符:

  1. 度符号(°)
    • CGRAM地址:0x00
    • 点阵数据:
      [0x07, 0x05, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00] # 5x8点阵
  2. 大写字母C
    • CGRAM地址:0x08
    • 点阵数据需与内置字体风格保持一致

3.2 Verilog代码集成

在原有状态机中增加CGRAM初始化阶段:

// 状态定义扩展 parameter INIT_CGRAM = 4'd5; parameter DISP_TEMP = 4'd12; // 点阵数据存储 reg [7:0] degree_char [0:7]; reg [7:0] celsius_char [0:7]; initial begin // 初始化度符号 degree_char[0] = 8'b00110; degree_char[1] = 8'b01001; // ...完整数据省略 end // 状态机新增分支 INIT_CGRAM: begin if(sub_state == 0) begin lcd_data <= 8'h40; // 设置CGRAM地址 sub_state <= 1; end else begin lcd_data <= degree_char[char_ptr]; if(char_ptr == 7) begin state <= DISP_TEMP; end end end

3.3 显示控制技巧

  • 混合显示:DDRAM中交替使用CGROM和CGRAM字符
  • 地址计算:显示位置需考虑行偏移(第一行0x80+col,第二行0xC0+col)
  • 优化建议
    • 将常用符号固化在CGRAM的固定位置
    • 使用宏定义提高代码可读性:
      `define DEGREE_SYM 8'h00 // CGRAM位置0 `define CELSIUS_C 8'h01 // CGRAM位置1

4. 调试与性能优化

4.1 常见问题排查

  1. 字符显示错乱

    • 检查CGRAM地址是否越界
    • 验证点阵数据位序(LSB/MSB)
  2. 显示位置偏移

    • 确认DDRAM地址计算正确
    • 检查状态机是否意外修改了地址指针
  3. 时序问题

    • 用逻辑分析仪捕获E信号脉宽
    • 确保各状态间有足够延时

4.2 性能提升方案

  • 双缓冲技术:在FPGA中开辟CGRAM缓存区,实现动态更新
  • 预编译工具:使用Python生成点阵数据Verilog初始化代码:
    def generate_verilog(data, symbol_name): print(f"// {symbol_name} definition") for i, line in enumerate(data): print(f"8'b{line:08b}", end=', ' if i<7 else '')
  • 资源优化:共用相同字符的点阵数据,减少CGRAM占用

在完成上述实现后,当系统需要显示"25°C"时,DDRAM中只需写入:

0x32 0x35 0x00 0x01 // "2" "5" CGRAM0 CGRAM1