2020年1月28日 星期二

altera小实验——LCD1602显示

altera小实验——LCD1602显示

源自於https://blog.csdn.net/moon9999/article/details/62436955

所用板子为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数据存贮器地址地址输入才为有效。



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



用户接口标注如下:


[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时操作数据

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



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



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

指令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个字符如下



如要显示“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.程序结果





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
————————————————
原文链接:https://blog.csdn.net/moon9999/article/details/62436955

沒有留言:

張貼留言

Messaging API作為替代方案

  LINE超好用功能要沒了!LINE Notify明年3月底終止服務,有什麼替代方案? LINE Notify將於2025年3月31日結束服務,官方建議改用Messaging API作為替代方案。 //CHANNEL_ACCESS_TOKEN = 'Messaging ...