반도체 Study/디지털 설계

Verilog - CHARACTER LCD에 문자 입력하기

잇(IT) 2024. 6. 18. 14:33
728x90

FPGA를 이용한 LCD 문자 출력 결과 화면


LCD는 CLK 신호를 기반으로 RS, RW, E, DB0~DB7 핀을 통해 신호를 전달받아 데이터를 출력한다.

 

RS는 Register Select의 약자로

RS가 0일 경우 명령 레지스터를 선택한다. 즉, LCD에 보내는 데이터가 명령어 또는 제어 명령어임을 나타낸다.

RS가 1일 경우 데이터 레지스터를 선택한다. 즉, LCD에 보내는 데이터가 화면에 표시될 문자 데이터임을 나타낸다.

 

RW는 Read Write의 약자로

RW가 0 일 때는 쓰기 모드로 MCU -> LCD로 데이터를 전송한다. 이 모드는 주로 LCD에 문자를 표시하거나 명령을 전송할 때 사용된다.

RW가 1일 때는 LCD -> MCU로 데이터를 전송한다. 이 모드는 주로 LCD의 상태를 읽거나, Busy Flag를 확인할 때 사용된다.

 

E는 Enable의 약자로

Enable 신호의 변화를 통해 LCD에게 데이터 전송이 준비되었음을 알려준다.

일반적으로, 데이터 전송 전에 Enable 핀을 High로 설정하고, 그 다음에 다시 Low로 설정한다.

데이터는 Enable 핀이 High에서 Low 전환될 때 LCD에 의해 캡처된다.

 

데이터 전송 시퀀스

RS 핀 설정: 데이터인지 명령인지 설정 (RS = 0: 명령, RS = 1: 데이터)
RW 핀 설정: 읽기/쓰기 설정 (RW = 0: 쓰기, RW = 1: 읽기)
데이터 설정: 데이터 핀에 전송할 명령 또는 데이터를 설정
Enable 핀을 High로 설정: LCD가 데이터를 준비할 시간을 줌
Enable 핀을 Low로 설정: 이 순간에 데이터가 LCD로 전송됨
Enable 핀을 다시 High로 설정: 다음 데이터 전송을 준비

 

위 과정을 통해 LCD에 값을 출력하게 된다.


동작 명령 RS R/W DB7 ~ DB0
DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
화면 클리어 0 0 0 0 0 0 0 0 0 1
커서 초기 위치 0 0 0 0 0 0 0 0 1 .
문자 입력 모드 0 0 0 0 0 0 0 1 I/D SH
디스플레이 On/Off 제어 0 0 0 0 0 0 1 D C B
커서 디스플레이 전이 0 0 0 0 0 1 S/C R/L . .
인터페이스/디스플레이설정 0 0 0 0 1 DL N F . .
CGRAM 주소 설정 0 0 0 1 CGRAM 주소 (AC5 ~ AC0)
DDRAM 주소 설정 0 0 1 DDRAM 주소 (AC6 ~ AC0)
BF와 AC 읽기 0 1 DF 주소 카운터(AC6 ~ AC0)
RAM 데이터 읽기 1 0 데이터 (DB7 ~ DB0)
RAM 데이터 쓰기 1 1 데이터 (DB7 ~ DB0)
동작 명령 비트 동작
화면 클리어 - 스페이스에 해당되는 ASCII 문자 0x20 인가, 커서 위치를 제 1행 1열에 움직임
커서 초기 위치 - 커서 위치를 제 1행 1열로 움직임
문자 입력 모드 (I/D, SH) (1,0) 커서를 오른쪽으로 이동하면서 문자 입력, (0,0) 커서를 왼쪽으로 이동하면서 문자 입력, (-,1) 커서 이동 없이 같은 자리에서 문자 입력
디스플레이 On/Off 제어 D 1 : 디스플레이 ON / 0 : 디스플레이 OFF
C 1 : 커서 ON / 0 : 커서 OFF
B 1 : 커서 깜빡임 ON / 0 : 커서 깜빡임 OFF
커서디스플레이 천이 (S/C, R/L) (1,0) 화면 전체 커서와 함께 왼쪽으로 스크롤, (1,1) 화면 전체 커서와 함께 오른쪽으로 스크롤, (0,0) 커서만 좌로 이동, (0,1) 커서만 우로 이동
인터페이스 / 디스플레이 설정 DL 1 : 8비트 인터페이스 / 0 : 4비트 인터페이스
N 1 : 두 줄 표시 / 0 : 한 줄 표시
F 1 : 문자 5x10 / 0 : 문자 5x7 도트
CGRAM 주소 설정 AC5 ~ AC0 CGRAM 주소 카운터(AC) AC5 ~ AC0값 설정
DDRAM 주소 설정 AC6 ~ AC0 DDRAM 커서 위치 설정 AC6 ~ AC0값 설정, 0x00 ~ 0x0F : 제 1행의 0열부터 15열까지 지정, 0x40 ~ 0x4F : 제 2행의 0열부터 15열까지 지정
BF와 AC 읽기 BF, AC6 ~ AC0 내부 동작 여부에 따른 Busy Flag와 주소 카운터값을 읽음, BF : 1 내부 동작 진행 중, BF : 0 내부 동작 완료, 다음 동작 명령 수행 가능

 

위 표는 LCD를 동작 시키기 위한 각 비트에 대한 의미들을 나타낸 표에 해당한다.


module lcd(
    input clk,
    input reset,
    output [7:0] data,
    output lcd_e,
    output lcd_rs
    );

parameter A = 8'h41, B = 8'h42, C = 8'h43, D = 8'h44, E = 8'h45, F = 8'h46, G = 8'h47, H = 8'h48, I = 8'h49, J = 8'h4a, K = 8'h4b, L = 8'h4c, M = 8'h4d, N = 8'h4e, O = 8'h4f, P = 8'h50, Q = 8'h51, R = 8'h52, S = 8'h53, T = 8'h54, U = 8'h55, V = 8'h56, W = 8'h57, X = 8'h58, Y = 8'h59, Z = 8'h5a;
parameter ZERO = 8'h30, ONE = 8'h31, TWO = 8'h32, THREE = 8'h33, FOUR = 8'h34, FIVE = 8'h35, SIX = 8'h36, SEVEN = 8'h37, EIGHT = 8'h38, NINE = 8'h39;
parameter SPACE = 8'h20;
parameter EX = 8'h21; 

    reg [7:0] data;
    reg lcd_e;
    reg lcd_rs;

    reg [7:0] dspdata [0:37];
    integer i = 0;
    integer j = 0;

    always @(posedge clk or posedge reset) begin
        if (reset) begin
            dspdata[0] <= 8'h38;    // function set 8bit, 2line, 5x7 dot
            dspdata[1] <= 8'h0f;    // display on/off , display on, cursor off, cursor blink off
            dspdata[2] <= 8'h06;    // entry mode set increment cursor position, no display shift
            dspdata[3] <= 8'h01;    // clear display
            dspdata[4] <= 8'h80;    // set cg ram address 1000 0000   1번라인 첫번째부터
            dspdata[5] <= H;    // 1
            dspdata[6] <= E;    // 2
            dspdata[7] <= L;
            dspdata[8] <= L;
            dspdata[9] <= O;
            dspdata[10] <= SPACE;
            dspdata[11] <= W;
            dspdata[12] <= O;
            dspdata[13] <= R;
            dspdata[14] <= L;
            dspdata[15] <= D;
            dspdata[16] <= EX;
            dspdata[17] <= EX;
            dspdata[18] <= EX;
            dspdata[19] <= EX;
            dspdata[20] <= EX;
            dspdata[21] <= 8'hC0;   // set cg ram address 1100 0000   2번라인 첫번째부터
            dspdata[22] <= B;   // A
            dspdata[23] <= A;
            dspdata[24] <= E;
            dspdata[25] <= K;
            dspdata[26] <= SPACE;
            dspdata[27] <= I;
            dspdata[28] <= N;
            dspdata[29] <= SPACE;
            dspdata[30] <= S;
            dspdata[31] <= O;
            dspdata[32] <= O;
            dspdata[33] <= SPACE;
            dspdata[34] <= SPACE;
            dspdata[35] <= SPACE;
            dspdata[36] <= SPACE;
            dspdata[37] <= SPACE;

            i <= 0;
            j <= 0;
            lcd_e <= 0;
            lcd_rs <= 0;
            data <= 8'b0;
        end else begin
        //i 1000000 이하면 i + 1을 시킨다.
            if(i <= 1000000) begin
                i <= i + 1;
                //lcd_e는 1로 선언한다.
                lcd_e <= 1;
                data <= dspdata[j];
            end else if ((i > 1000000) && (i < 2000000)) begin
                i <= i + 1;
                lcd_e <= 0;
            end else if (i == 2000000) begin
                j <= j + 1;
                i <= 1'b0;
            end

            if(j <= 4) lcd_rs <= 0;
            else if ((j > 4) && (j < 21 )) lcd_rs <= 1;
            else if (j == 21) lcd_rs <= 0;
            else if ((j > 21 ) && (j < 38)) lcd_rs <= 1;

            if(j == 38) j <= 4;
        end
    end
endmodule

LCD에 문자를 띄우기 위한 전체 코드는 위에 해당한다.

 

module lcd(
    input clk,
    input reset,
    output [7:0] data,
    output lcd_e,
    output lcd_rs
    );

2개의 input과 10개의 output을 사용한다.

 

parameter A = 8'h41, B = 8'h42, C = 8'h43, D = 8'h44, E = 8'h45, F = 8'h46, G = 8'h47, H = 8'h48, I = 8'h49, J = 8'h4a, K = 8'h4b, L = 8'h4c, M = 8'h4d, N = 8'h4e, O = 8'h4f, P = 8'h50, Q = 8'h51, R = 8'h52, S = 8'h53, T = 8'h54, U = 8'h55, V = 8'h56, W = 8'h57, X = 8'h58, Y = 8'h59, Z = 8'h5a;
parameter ZERO = 8'h30, ONE = 8'h31, TWO = 8'h32, THREE = 8'h33, FOUR = 8'h34, FIVE = 8'h35, SIX = 8'h36, SEVEN = 8'h37, EIGHT = 8'h38, NINE = 8'h39;
parameter SPACE = 8'h20;
parameter EX = 8'h21; 

    reg [7:0] data;
    reg lcd_e;
    reg lcd_rs;

    reg [7:0] dspdata [0:37];
    integer i = 0;
    integer j = 0;

ASCII 코드를 참고하여 각 문자에 해당하는 값을 Parameter로 표현하였다.

 

그 외 register로 사용해야 하는 변수들 지정 및 동작에 필요한 변수 선언 및 레지스터를 생성하였다.

 

always @(posedge clk or posedge reset) begin
        if (reset) begin
            dspdata[0] <= 8'h38;    
            dspdata[1] <= 8'h0f;    
            dspdata[2] <= 8'h06;    
            dspdata[3] <= 8'h01;    
            dspdata[4] <= 8'h80;   
            dspdata[5] <= H;    
            dspdata[6] <= E;   
            dspdata[7] <= L;
            dspdata[8] <= L;
            dspdata[9] <= O;
            dspdata[10] <= SPACE;
            dspdata[11] <= W;
            dspdata[12] <= O;
            dspdata[13] <= R;
            dspdata[14] <= L;
            dspdata[15] <= D;
            dspdata[16] <= EX;
            dspdata[17] <= EX;
            dspdata[18] <= EX;
            dspdata[19] <= EX;
            dspdata[20] <= EX;
            dspdata[21] <= 8'hC0;   
            dspdata[22] <= B;   
            dspdata[23] <= A;
            dspdata[24] <= E;
            dspdata[25] <= K;
            dspdata[26] <= SPACE;
            dspdata[27] <= I;
            dspdata[28] <= N;
            dspdata[29] <= SPACE;
            dspdata[30] <= S;
            dspdata[31] <= O;
            dspdata[32] <= O;
            dspdata[33] <= SPACE;
            dspdata[34] <= SPACE;
            dspdata[35] <= SPACE;
            dspdata[36] <= SPACE;
            dspdata[37] <= SPACE;

            i <= 0;
            j <= 0;
            lcd_e <= 0;
            lcd_rs <= 0;
            data <= 8'b0;
        end else begin
        //i 1000000 이하면 i + 1을 시킨다.
            if(i <= 1000000) begin
                i <= i + 1;
                //lcd_e는 1로 선언한다.
                lcd_e <= 1;
                data <= dspdata[j];
            end else if ((i > 1000000) && (i < 2000000)) begin
                i <= i + 1;
                lcd_e <= 0;
            end else if (i == 2000000) begin
                j <= j + 1;
                i <= 1'b0;
            end

            if(j <= 4) lcd_rs <= 0;
            else if ((j > 4) && (j < 21 )) lcd_rs <= 1;
            else if (j == 21) lcd_rs <= 0;
            else if ((j > 21 ) && (j < 38)) lcd_rs <= 1;

            if(j == 38) j <= 4;
        end
    end
endmodule

 

위 코드는 LCD에 문자를 출력하는 코드에 해당하며

 

LCD는 16x2 문자를 표현할 수 있기 때문에 32개의 레지스터 + LCD에 명령을 보내기 위한 6개의 명령 레지스터 총 38개의 레지스터를 사용한다.

 

 i <= 0;
            j <= 0;
            lcd_e <= 0;
            lcd_rs <= 0;
            data <= 8'b0;
        end else begin
        //i 1000000 이하면 i + 1을 시킨다.
            if(i <= 1000000) begin
                i <= i + 1;
                //lcd_e는 1로 선언한다.
                lcd_e <= 1;
                data <= dspdata[j];
            end else if ((i > 1000000) && (i < 2000000)) begin
                i <= i + 1;
                lcd_e <= 0;
            end else if (i == 2000000) begin
                j <= j + 1;
                i <= 1'b0;
            end

            if(j <= 4) lcd_rs <= 0;
            else if ((j > 4) && (j < 21 )) lcd_rs <= 1;
            else if (j == 21) lcd_rs <= 0;
            else if ((j > 21 ) && (j < 38)) lcd_rs <= 1;

            if(j == 38) j <= 4;

위 코드를 먼저 보게 되면 LCD에게 명령 혹은 데이터를 전달하며, RS 값 설정 및 E 값을 조절하는 부분에 해당한다.

 

LCD의 경우 최초에 데이터를 보내기 위해 일정 시간은 딜레이가 필요하여 RS 값 및 Enable 값을 통해 데이터를 전달한다.

현재 FPGA 보드에는 50MHz의 클럭이 사용되고 있으므로 대략 0.02초의 간격으로 데이터를 전송하게 된다. 

(LCD의 경우 데이터 전달 속도가 너무 빠를 경우 데이터 누락이 발생할 수 있고, 너무 느릴 경우 동기화 문제가 발생할 수 있기 때문이다.)

 

dspdata 레지스터의 값을 하나씩 전달하게 되며, 또 데이터 전달 타이밍은 Enable이 High에서 Low 갈 때 발생하고, Enable High 유지 신호가 짧으면 Data를 받아오기 전에 데이터를 전달 할 수 있으므로 충분한 간격을 유지한다.

 

현재 코드는 문자 출력을 우선 목적으로하기 때문에 코드가 하드 코딩 되어 있다.

for문을 통해 명령줄까지는 RS값을 0으로 설정하고, 데이터 전달 부분은 RS를 1로 설정하여 데이터를 전달하는 반복문을 작성하여 Command와 Data를 전달하고 있다.

 

dspdata[0] <= 8'h38;    
            dspdata[1] <= 8'h0f;    
            dspdata[2] <= 8'h06;    
            dspdata[3] <= 8'h01;    
            dspdata[4] <= 8'h80;

dspdata[0] ~ dspdata[4]는 명령을 위한 데이터에 해당한다.

 

예를 들어 위에 작성된 DataSheet를 보게되면 dspdata[0]은 8bit를 사용하며, 2 line을 전부 사용하고, 5x7 dot을 사용하는 것을 알 수 있다.

 

기본적인 LCD에 문자를 출력하는 코드는 위와 같고 추후에 기회가 있으면 하드코딩이 아닌 조금 더 유연하게 LCD를 통해 문자를 출력하는 코드를 작성해 볼 것이다.

 

 

728x90