altera小实验——LCD1602显示

简介: altera小实验——LCD1602显示

所用板子为altera DE2板子,FPGA为Cyclone II:EP2C35F672C6,quartus版本为13.0

1.LCD规格与接口

DE2板子上的LCD为16*2,是最简单的LCD显示屏。

数据储存器地址为第一行00H~0fH,第二行40H~4fH。但是需要注意的是,在需要向数据存贮器赋值时,需要赋值为80H~8fH和c0H~cfH,因为只有第一位置1数据存贮器地址地址输入才为有效。

063429cb4f052e771f0b5c4f09800392_SouthEast.png

接下来看下LCD用户接口,在此截取DE2用户手册。

529e8e77b1ecb84fdc394698478f90d9_SouthEast.png

用户接口标注如下:

[7:0]Data    //8位数据总线
LCD_EN      //使能信号
LCD_RW     //读/写选择信号
LCD_RS      //数据/命令选择信号
LCD_BLON  //背光灯亮灭
LCD_ON      //总开关

其中,LCD_ON LCD_BLON可暂时不关注,使用时置为1即可。

LCD_EN LCD_RS LCD_RW Data在时序中需要格外关注。

2.LCD时序图

LCD的时序图极为简单,概括来讲就是:

LCD_EN=1时进行数据交互(因此可令LCD_EN = CLK_LCD,数据操作均发生在时钟高有效时段)

LCD_RW=0时写入数据,LCD_RW=1时读出数据(基本没有用到)

LCD_RS=0时操作指令,LCD_RS=1时操作数据

整体时序图如下(来自网络),里面的细节时序可暂时忽略。

e072470d8818585eea16124c4ffb052d_SouthEast.png

3.LCD基本操作指令

LCD的操作整体上分为两类:指令配置与数据读写显示。指令表如下(来源网络)。

e46534d24179081fb9f3448d6fdb3f6e_SouthEast.png

指令的解读(摘取自网络):

指令1:清屏,光标同时复位至00H位置(左上角);

指令2:光标复位,即光标复位至00H;

指令3:光标与显示移动设置。I/D:光标移动方向,高电平右移,低电平左移;S:屏幕上所有文字是否左移或右移,高电平表示有效,低电平表示无效;

指令4:显示开关控制。D:控制整体的显示开与关,高电平表示开显示,低电平表示关显示。C:控制光标的开与关,高电平表示有光标,低电平表示无光标 B:控制光标是否闪烁,高电平闪烁,低电平不闪烁;

指令5:光标或显示移位 S/C :高电平时显示移动的文字,低电平时移动光标;

指令6:功能设置命令 DL:高电平时为4位总线,低电平时为8位总线 N:低电平时为单行显示,高电平时为双行显示,F:低电平时显示5X7的点阵字符,高电平时显示5X10的显示字符;功能设置命令 DL:高电平时为4位总线,低电平时为8位总线 N:低电平时为单行显示,高电平时为双行显示,F:低电平时显示5X7的点阵字符,高电平时显示5X10的显示字符。

指令7:字符发生器RAM地址设置;

指令8:DDRAM地址设置;

指令9:读忙信号和光标地址 BF:忙标志位,高电平表示忙,此时模块不能接收命令或数据,如果为低电平表示不忙。

4.如何显示一个字符

字符产生器CGROM中内置了192个常用字符,自定义字符产生器CGRAM还允许用户自定义8个字符。内置的192个字符如下

eab66fac45eff04e1234bf08138b9ebd_SouthEast.png

如要显示“A”,则需要在指令10阶段时候写入数据为8‘b0100_0001。

本次将大小写字母以参数形式写在了character.v中。主程序中可以直接调用。

parameter space = 8'b0010_0000;
parameter CAPITAL_A = 8'b0100_0001;
parameter CAPITAL_B = 8'b0100_0010;
parameter CAPITAL_C = 8'b0100_0011;
parameter CAPITAL_D = 8'b0100_0100;
parameter CAPITAL_E = 8'b0100_0101;
parameter CAPITAL_F = 8'b0100_0110;
parameter CAPITAL_G = 8'b0100_0111;
parameter CAPITAL_H = 8'b0100_1000;
parameter CAPITAL_I = 8'b0100_1001;
parameter CAPITAL_J = 8'b0100_1010;
parameter CAPITAL_K = 8'b0100_1011;
parameter CAPITAL_L = 8'b0100_1100;
parameter CAPITAL_M = 8'b0100_1101;
parameter CAPITAL_N = 8'b0100_1110;
parameter CAPITAL_O = 8'b0100_1111;
parameter CAPITAL_P = 8'b0101_0001;
parameter CAPITAL_Q = 8'b0101_0001;
parameter CAPITAL_R = 8'b0101_0010;
parameter CAPITAL_S = 8'b0101_0011;
parameter CAPITAL_T = 8'b0101_0100;
parameter CAPITAL_U = 8'b0101_0101;
parameter CAPITAL_V = 8'b0101_0110;
parameter CAPITAL_W = 8'b0101_0111;
parameter CAPITAL_X = 8'b0101_1000;
parameter CAPITAL_Y = 8'b0101_1001;
parameter CAPITAL_Z = 8'b0101_1010;
parameter LOWERCASE_a = 8'b0110_0001;
parameter LOWERCASE_b = 8'b0110_0010;
parameter LOWERCASE_c = 8'b0110_0011;
parameter LOWERCASE_d = 8'b0110_0100;
parameter LOWERCASE_e = 8'b0110_0101;
parameter LOWERCASE_f = 8'b0110_0110;
parameter LOWERCASE_g = 8'b0110_0111;
parameter LOWERCASE_h = 8'b0110_1000;
parameter LOWERCASE_i = 8'b0110_1001;
parameter LOWERCASE_j = 8'b0110_1010;
parameter LOWERCASE_k = 8'b0110_1011;
parameter LOWERCASE_l = 8'b0110_1100;
parameter LOWERCASE_m = 8'b0110_1101;
parameter LOWERCASE_n = 8'b0110_1110;
parameter LOWERCASE_o = 8'b0110_1111;
parameter LOWERCASE_p = 8'b0111_0001;
parameter LOWERCASE_q = 8'b0111_0001;
parameter LOWERCASE_r = 8'b0111_0010;
parameter LOWERCASE_s = 8'b0111_0011;
parameter LOWERCASE_t = 8'b0111_0100;
parameter LOWERCASE_u = 8'b0111_0101;
parameter LOWERCASE_v = 8'b0111_0110;
parameter LOWERCASE_w = 8'b0111_0111;
parameter LOWERCASE_x = 8'b0111_1000;
parameter LOWERCASE_y = 8'b0111_1001;
parameter LOWERCASE_z = 8'b0111_1010;
5.程序分析

1)接口

module work(
  ClOCK_50,
  KEY,
  LCD_RW,
  LCD_EN,
  LCD_RS,
  LCD_DATA,
  LCD_ON,
  LCD_BLON
  );
input ClOCK_50;
input [3:0]KEY;
output LCD_RW, LCD_EN, LCD_RS, LCD_ON, LCD_BLON;
output [8:0]LCD_DATA;
assign LCD_RW = rw;
assign LCD_EN = CLK_500Hz;
assign LCD_RS = rs;
assign LCD_ON = 1;
assign LCD_BLON = 1;
assign LCD_DATA = data;
`include "character.v"

主时钟为50MHz,之后需要进行手动分频,LCD时钟不能过快。

!KEY[1]作为复位信号。其余信号均输出至LCD。

2)状态与参数设置

/*共14个状态:1个空闲状态,9个设置状态,两行读写包括地址与数据共计4个状态*/
parameter s_idle         = 4'd0;
parameter s_clear        = 4'd1;
parameter s_cursor       = 4'd2;
parameter s_inputmode    = 4'd3;
parameter s_switchmode   = 4'd4;
parameter s_shiftmode    = 4'd5;
parameter s_setfunction  = 4'd6;
parameter s_setgeneraddr = 4'd7;
parameter s_setdataaddr1 = 4'd8;
parameter s_readbasy     = 4'd9;
parameter s_writecgram1  = 4'd10;
parameter s_readram      = 4'd11;
parameter s_setdataaddr2 = 4'd12;
parameter s_writecgram2  = 4'd13;
parameter IDLE         = 8'bzzzz_zzzz;
parameter CLEAR        = 8'b0000_0001;  //清屏
parameter CURSOR       = 8'b0000_0010;  //光标返回
parameter INPUTMODE    = 8'b0000_0110;  //置输入模式 _01(I/D)S
parameter SWITCHMODE   = 8'b0000_1111;  //显示开关控制 _1DCB
parameter SHIFTMODE    = 8'b0001_1100;  //光标或字符移位 _(S/R)(R/L)**
parameter SETFUNCTION  = 8'b0011_1100;  //置功能 00_001(DL)_NF**
parameter SETGENERADDR = 8'b0100_0000;  //置字符发生存贮地址
parameter SETDATAADDR  = 8'b1000_0000;  //置数据存贮器地址 起始地址

3)状态机跳转,并非每个状态都是必须的,参考了其他程序后,状态机如下

reg [3:0]state;
reg rw, rs;
reg [7:0]data;
reg [7:0]addr;
always @(posedge clk or posedge rst) begin
  if (rst) begin
    // reset
    state <= s_idle;
    data  <= 8'b0;
    rw    <= 1'b0;
    rs    <= 1'b0;
    addr  <= 8'b1000_0000;
  end
  else begin
    case (state)
      s_idle        :begin
                        state <= s_clear;
                        data  <= IDLE;
                        rw    <= rw;
                        rs    <= rs;
                     end
      s_clear       :begin
                        state <= s_inputmode;
                        data  <= CLEAR;
                        rw    <= 1'b0;
                        rs    <= 1'b0;
                     end
      s_inputmode   :begin
                        state <= s_switchmode;
                        data  <= INPUTMODE;
                        rw    <= 1'b0;
                        rs    <= 1'b0;
                     end
      s_switchmode  :begin
                        state <= s_shiftmode;
                        data  <= SWITCHMODE;
                        rw    <= 1'b0;
                        rs    <= 1'b0;
                     end
      s_shiftmode   :begin
                        state <= s_setfunction;
                        data  <= SHIFTMODE;
                        rw    <= 1'b0;
                        rs    <= 1'b0;
                     end
      s_setfunction :begin
                        state <= s_setdataaddr1;
                        data  <= SETFUNCTION;
                        rw    <= 1'b0;
                        rs    <= 1'b0;
                     end
      s_setdataaddr1:begin
                        state <= s_writecgram1;
                        data  <= addr;
                        rw    <= 1'b0;
                        rs    <= 1'b0;
                     end
      s_writecgram1 :begin
                        if (addr == 8'b1000_1111) begin
                          state <= s_setdataaddr2;
                          data  <= lcd_data(addr);
                          rw    <= 1'b0;
                          rs    <= 1'b1;
                          addr  <= 8'b1100_0000;
                        end
                        else begin
                          state <= s_writecgram1;
                          data  <= lcd_data(addr);
                          rw    <= 1'b0;
                          rs    <= 1'b1;
                          addr  <= addr + 1'b1;
                        end                        
                     end
      s_setdataaddr2:begin
                        state <= s_writecgram2;
                        data  <= addr;
                        rw    <= 1'b0;
                        rs    <= 1'b0;
                     end
      s_writecgram2 :begin
                        if (addr == 8'b1100_1111) begin
                          state <= s_setdataaddr1;
                          data  <= lcd_data(addr);
                          rw    <= 1'b0;
                          rs    <= 1'b1;
                          addr  <= 8'b1000_0000;
                        end
                        else begin
                          state <= s_writecgram2;
                          data  <= lcd_data(addr);
                          rw    <= 1'b0;
                          rs    <= 1'b1;
                          addr  <= addr + 1'b1;
                        end                        
                     end
      default       :begin
                        state <= state;
                        data  <= data;
                        rw    <= rw;
                        rs    <= rs;
                     end
    endcase
  end
end

4)函数:设置每一个地址所显示数据(此处可DIY)

function [7:0]lcd_data;
  input [7:0]lcd_addr;
  begin
    case(lcd_addr)
      8'h81  :lcd_data = CAPITAL_I;
      8'h83  :lcd_data = LOWERCASE_a;
      8'h84  :lcd_data = LOWERCASE_m;
      8'h86  :lcd_data = CAPITAL_G;
      8'h87  :lcd_data = CAPITAL_J;
      8'h88  :lcd_data = CAPITAL_M;
      8'hc1  :lcd_data = CAPITAL_W;
      8'hc2  :lcd_data = LOWERCASE_h;
      8'hc3  :lcd_data = LOWERCASE_o;
      8'hc5  :lcd_data = LOWERCASE_a;
      8'hc6  :lcd_data = LOWERCASE_r;
      8'hc7  :lcd_data = LOWERCASE_e;
      8'hc9  :lcd_data = LOWERCASE_y;
      8'hca  :lcd_data = LOWERCASE_o;
      8'hcb  :lcd_data = LOWERCASE_u;
      default:lcd_data = space;
    endcase
  end
endfunction

5)时钟分频

parameter CLK_LCD_times = 19'd030000;
reg [22:0]cnt;
reg CLK_LCD;
always@(posedge ClOCK_50 or posedge rst) begin
  if(rst) begin
    cnt <= 19'd0;
    CLK_500Hz <= 1'b0;
  end
  else if(cnt == CLK_LCD_times) begin
    cnt <= 19'd0;
    CLK_LCD <= ~CLK_LCD;
  end
  else begin 
    cnt <= cnt+1'b1;
  end 
end

至此程序完成。文末附上完整代码。

6.程序结果

202d428b6097f24ae80c195c5246b411_SouthEast.jpg


ps.完整代码如下

module work(
  ClOCK_50,
  KEY,
  LCD_RW,
  LCD_EN,
  LCD_RS,
  LCD_DATA,
  LCD_ON,
  LCD_BLON
  );
input ClOCK_50;
input [3:0]KEY;
output LCD_RW, LCD_EN, LCD_RS, LCD_ON, LCD_BLON;
output [8:0]LCD_DATA;
assign LCD_RW = rw;
assign LCD_EN = CLK_LCD;
assign LCD_RS = rs;
assign LCD_ON = 1;
assign LCD_BLON = 1;
assign LCD_DATA = data;
`include "character.v" 
/*共14个状态:1个空闲状态,9个设置状态,两行读写包括地址与数据共计4个状态*/
parameter s_idle         = 4'd0;
parameter s_clear        = 4'd1;
parameter s_cursor       = 4'd2;
parameter s_inputmode    = 4'd3;
parameter s_switchmode   = 4'd4;
parameter s_shiftmode    = 4'd5;
parameter s_setfunction  = 4'd6;
parameter s_setgeneraddr = 4'd7;
parameter s_setdataaddr1 = 4'd8;
parameter s_readbasy     = 4'd9;
parameter s_writecgram1  = 4'd10;
parameter s_readram      = 4'd11;
parameter s_setdataaddr2 = 4'd12;
parameter s_writecgram2  = 4'd13;
parameter IDLE         = 8'bzzzz_zzzz;
parameter CLEAR        = 8'b0000_0001;  //清屏
parameter CURSOR       = 8'b0000_0010;  //光标返回
parameter INPUTMODE    = 8'b0000_0110;  //置输入模式 _01(I/D)S
parameter SWITCHMODE   = 8'b0000_1111;  //显示开关控制 _1DCB
parameter SHIFTMODE    = 8'b0001_1100;  //光标或字符移位 _(S/R)(R/L)**
parameter SETFUNCTION  = 8'b0011_1100;  //置功能 00_001(DL)_NF**
parameter SETGENERADDR = 8'b0100_0000;  //置字符发生存贮地址
parameter SETDATAADDR  = 8'b1000_0000;  //置数据存贮器地址 起始地址
wire clk, rst;
assign clk = CLK_LCD;
assign rst = !KEY[1];
reg [3:0]state;
reg rw, rs;
reg [7:0]data;
reg [7:0]addr;
always @(posedge clk or posedge rst) begin
  if (rst) begin
    // reset
    state <= s_idle;
    data  <= 8'b0;
    rw    <= 1'b0;
    rs    <= 1'b0;
    addr  <= 8'b1000_0000;
  end
  else begin
    case (state)
      s_idle        :begin
                        state <= s_clear;
                        data  <= IDLE;
                        rw    <= rw;
                        rs    <= rs;
                     end
      s_clear       :begin
                        state <= s_inputmode;
                        data  <= CLEAR;
                        rw    <= 1'b0;
                        rs    <= 1'b0;
                     end
      s_inputmode   :begin
                        state <= s_switchmode;
                        data  <= INPUTMODE;
                        rw    <= 1'b0;
                        rs    <= 1'b0;
                     end
      s_switchmode  :begin
                        state <= s_shiftmode;
                        data  <= SWITCHMODE;
                        rw    <= 1'b0;
                        rs    <= 1'b0;
                     end
      s_shiftmode   :begin
                        state <= s_setfunction;
                        data  <= SHIFTMODE;
                        rw    <= 1'b0;
                        rs    <= 1'b0;
                     end
      s_setfunction :begin
                        state <= s_setdataaddr1;
                        data  <= SETFUNCTION;
                        rw    <= 1'b0;
                        rs    <= 1'b0;
                     end
      s_setdataaddr1:begin
                        state <= s_writecgram1;
                        data  <= addr;
                        rw    <= 1'b0;
                        rs    <= 1'b0;
                     end
      s_writecgram1 :begin
                        if (addr == 8'b1000_1111) begin
                          state <= s_setdataaddr2;
                          data  <= lcd_data(addr);
                          rw    <= 1'b0;
                          rs    <= 1'b1;
                          addr  <= 8'b1100_0000;
                        end
                        else begin
                          state <= s_writecgram1;
                          data  <= lcd_data(addr);
                          rw    <= 1'b0;
                          rs    <= 1'b1;
                          addr  <= addr + 1'b1;
                        end                        
                     end
      s_setdataaddr2:begin
                        state <= s_writecgram2;
                        data  <= addr;
                        rw    <= 1'b0;
                        rs    <= 1'b0;
                     end
      s_writecgram2 :begin
                        if (addr == 8'b1100_1111) begin
                          state <= s_setdataaddr1;
                          data  <= lcd_data(addr);
                          rw    <= 1'b0;
                          rs    <= 1'b1;
                          addr  <= 8'b1000_0000;
                        end
                        else begin
                          state <= s_writecgram2;
                          data  <= lcd_data(addr);
                          rw    <= 1'b0;
                          rs    <= 1'b1;
                          addr  <= addr + 1'b1;
                        end                        
                     end
      default       :begin
                        state <= state;
                        data  <= data;
                        rw    <= rw;
                        rs    <= rs;
                     end
    endcase
  end
end
function [7:0]lcd_data;
  input [7:0]lcd_addr;
  begin
    case(lcd_addr)
      8'h81  :lcd_data = CAPITAL_I;
      8'h83  :lcd_data = LOWERCASE_a;
      8'h84  :lcd_data = LOWERCASE_m;
      8'h86  :lcd_data = CAPITAL_G;
      8'h87  :lcd_data = CAPITAL_J;
      8'h88  :lcd_data = CAPITAL_M;
      8'hc1  :lcd_data = CAPITAL_W;
      8'hc2  :lcd_data = LOWERCASE_h;
      8'hc3  :lcd_data = LOWERCASE_o;
      8'hc5  :lcd_data = LOWERCASE_a;
      8'hc6  :lcd_data = LOWERCASE_r;
      8'hc7  :lcd_data = LOWERCASE_e;
      8'hc9  :lcd_data = LOWERCASE_y;
      8'hca  :lcd_data = LOWERCASE_o;
      8'hcb  :lcd_data = LOWERCASE_u;
      default:lcd_data = space;
    endcase
  end
endfunction
parameter CLK_LCD_times = 19'd030000;
reg [22:0]cnt;
reg CLK_LCD;
always@(posedge ClOCK_50 or posedge rst) begin
  if(rst) begin
    cnt <= 19'd0;
    CLK_500Hz <= 1'b0;
  end
  else if(cnt == CLK_LCD_times) begin
    cnt <= 19'd0;
    CLK_LCD <= ~CLK_LCD;
  end
  else begin 
    cnt <= cnt+1'b1;
  end 
end
endmodule


相关文章
|
存储 运维 Unix
MFS详解(一)——MFS介绍
MFS详解(一)——MFS介绍
2425 0
|
27天前
|
人工智能 前端开发 JavaScript
GLM-4.7实战指南:三个梯度测试,解锁国产大模型的代码生成与审美上限
国产大模型 GLM-4.7 在前端代码生成与智能体编程(Agentic Coding)上实现突破,凭借强大的逻辑推理、UI 审美与交互设计能力,可媲美 Claude 3.5 Sonnet。实测显示,其在 SVG 生成、网页游戏开发及高级页面设计中表现惊艳,支持长上下文、高性价比 API,成为开发者高效落地 AI 编程的优选工具。
GLM-4.7实战指南:三个梯度测试,解锁国产大模型的代码生成与审美上限
|
前端开发 关系型数据库 MySQL
开源云真机平台-Sonic应用实践
Sonic,一站式开源分布式集群云真机测试平台,致力服务于中小企业的客户端UI测试。 Sonic当前的愿景是能帮助中小型企业解决在客户端自动化或远控方面缺少工具和测试手段的问题。
开源云真机平台-Sonic应用实践
|
10月前
|
移动开发 JavaScript 前端开发
精通服务器推送事件(SSE)与 Python 和 Go 实现实时数据流 🚀
服务器推送事件(SSE)是HTML5规范的一部分,允许服务器通过HTTP向客户端实时推送更新。相比WebSocket,SSE更轻量、简单,适合单向通信场景,如实时股票更新或聊天消息。它基于HTTP协议,使用`EventSource` API实现客户端监听,支持自动重连和事件追踪。虽然存在单向通信与连接数限制,但其高效性使其成为许多轻量级实时应用的理想选择。文中提供了Python和Go语言的服务器实现示例,以及HTML/JavaScript的客户端代码,帮助开发者快速集成SSE功能,提升用户体验。
|
人工智能 测试技术 API
ChatGLM3-6B:新一代开源双语对话语言模型,流畅对话与低部署门槛再升级
ChatGLM3-6B:新一代开源双语对话语言模型,流畅对话与低部署门槛再升级
ChatGLM3-6B:新一代开源双语对话语言模型,流畅对话与低部署门槛再升级
|
10月前
|
SQL 分布式计算 关系型数据库
基于云服务器的数仓搭建-hive/spark安装
本文介绍了在本地安装和配置MySQL、Hive及Spark的过程。主要内容包括: - **MySQL本地安装**:详细描述了内存占用情况及安装步骤,涉及安装脚本的编写与执行,以及连接MySQL的方法。 - **Hive安装**:涵盖了从上传压缩包到配置环境变量的全过程,并解释了如何将Hive元数据存储配置到MySQL中。 - **Hive与Spark集成**:说明了如何安装Spark并将其与Hive集成,确保Hive任务由Spark执行,同时解决了依赖冲突问题。 - **常见问题及解决方法**:列举了安装过程中可能遇到的问题及其解决方案,如内存配置不足、节点间通信问题等。
基于云服务器的数仓搭建-hive/spark安装
|
机器学习/深度学习 算法 PyTorch
深度强化学习中SAC算法:数学原理、网络架构及其PyTorch实现
软演员-评论家算法(Soft Actor-Critic, SAC)是深度强化学习领域的重要进展,基于最大熵框架优化策略,在探索与利用之间实现动态平衡。SAC通过双Q网络设计和自适应温度参数,提升了训练稳定性和样本效率。本文详细解析了SAC的数学原理、网络架构及PyTorch实现,涵盖演员网络的动作采样与对数概率计算、评论家网络的Q值估计及其损失函数,并介绍了完整的SAC智能体实现流程。SAC在连续动作空间中表现出色,具有高样本效率和稳定的训练过程,适合实际应用场景。
4248 7
深度强化学习中SAC算法:数学原理、网络架构及其PyTorch实现
|
机器学习/深度学习 人工智能 资源调度
基于AI的运维资源调度:效率与智能的双重提升
基于AI的运维资源调度:效率与智能的双重提升
1743 16
基于AI的运维资源调度:效率与智能的双重提升
|
存储 机器人
ROS2教程 07 参数Param
本文是关于ROS2(机器人操作系统2)中参数(Parameter)机制的教程,介绍了ROS2中参数的使用和管理,包括列出、获取、设置、删除参数,以及如何通过命令行和launch文件在启动节点时配置参数,并通过示例代码展示了如何在ROS2节点中声明和使用参数。
1003 1
|
存储 API C语言
C/C++爱心代码
C/C++爱心代码
3778 2