이글의 전부 또는 일부, 사진, 소스프로그램 등은 저작자의 동의 없이는 상업적인 사용을 금지합니다. 또한, 비상업적인 목적이라하더라도 출처를 밝히지 않고 게시하는 것은 금지합니다.

 


(본 글은 2017.09.17.에 필자의 다른 티스토리 http://avrlab.tistory.com에 적었던 것을 옮겨왔습니다.)

 

 

 

이번 글에서는 문자형 LCD를 제어하는 명령들을 익히고 이를 이용하여 LCD를 초기화시키는 함수를 비롯하여 몇몇의 유용한 함수들을 만들어 보겠습니다.

 

LCD 제어 명령들에 대한 설명때문에 글이 다소 지루하고 길어 질 수 있습니다.

 

 

 

 

 

(1) LCD 제어 명령

 

문자형 LCD 컨트롤러로 사용되는 대표적인 칩으로 KS0066과 HD44780을 들 수 있습니다. 이 두 칩은 완전히 호환됩니다. 거의 대부분의 문자형 LCD는 이 두 칩 중 하나, 또는 호환되는 컨트롤러를 사용합니다.

 

이러한 이유로 문자형 LCD의 pin 기능이 대동소이하며, 제어 명령은 거의 같습니다. 특히 16핀을 가진 문자형 LCD는 거의 완전 표준화 되어 있어서, 특별한 제품이 아니라면 그냥 바꿔 끼워도 상관이 없을 정도입니다. (그래도 사용 전압이 3.3V, 5V로 다를 수 있고, 행여 간혹 변종이 있을 수도 있으니 반드시 확인은 해야 합니다.)

 

다음은 대표적인 문자형 LCD 제어 명령표입니다.

 

 

각각의 명령들을 자세히 살펴 보도록 하겠습니다.

 

위 명령들 중에서 맨 아래의 세 개는 이미 이전 글에서 함수로 작성하였습니다. 즉, Read Busy Flag and address는 LCD_READ_BF_ADDRESS 함수, Write Data to RAM은 LCD_WRITE_DATA 함수, Read Data From RAM은 LCD_READ_DATA 함수로 구현한 내용이므로 이글에서는 생략하겠습니다.

 

1) Clear Display

 

 Instruction

 RS

 RW

 DB7

 DB6

 DB5

 DB4

 DB3

 DB2

 DB1

 DB0

 Description

 time

 Clear

Display

 0

 0

 0

 0

 0

 Write "20H" to DDRAM and set DDRAM address to "00H" from AC

1.52mS 

 

문자형 LCD 내부의 저장장소인 DDRAM에 16진수 0x20(ASCII 코드의 space)를 채우고 앞으로 10)에서 설명할 DDRAM의 주소를 0번지로 설정합니다. 0번지는 화면상으로는 1행의 맨 첫째 글자 위치에 해당합니다. 이 명령을 실행하면 화면을 깨끗이 지우고, 첫행의 첫 글자를 쓸 준비가 됩니다.

레지스터 AL에 1을 기록한 다음에 앞의 글에서 만든 함수 LCD_WRITE_COMMAND 함수를 호출하면 위의 화면 지우기 작업을 합니다. 화면을 지우기 위해서는 명령값으로 1을 보낸다는 것을 기억하는 것이 귀찮은 일이므로, 아래와 같이 매크로로 정의하여 사용하는 것이 편리합니다.

 

이 명령을 실행에 1.57mS가 소요되므로 명령 뒤에는 대기 시간을 주어야 합니다. 실제로 측정해보 LCM4004A의 경우 2mS 대기해도 불안정하게 동작해서 3mS 정도 대기하도록 했습니다. Clear Display 명령을 실행할 때마다 3mS 대기 시간을 넣어야 하기 때문에 아예 LCD_CLEAR_DISPLAY라는 이름의 함수를 따로 만들었습니다.

위 설명의 Description에 있는 AC는 Address Counter로 LCD 내부에 있는 DDRAM의 주소값입니다. DDRAM의 주소값은 화면 상의 위치와 연결되어 있습니다. 다음 글인 4편에서 자세히 다룰 예정입니다.

 

.EQU LCD_CLEARDISPLAY    = 1

LCD_CLEAR_DISPLAY:
    LDI     AL,LCD_CLEARDISPLAY
    RCALL   LCD_WRITE_COMMAND
    LDI     3
    RJMP    DELAY_MS
사용 예)
    RCALL   LCD_CLEAR_DISPLAY

 

맨 마지막 행 RJMP DELAY_MS는 아래와 같이 작성해도 됩니다만, 불필요한 코드 하나라도 줄이고 조금이라도 더 빨리 실행하라고 위와 같이 작성하였습니다. 아래와 같이 코딩하면 깔끔하고 나중에 분석하기도 편하다는 장점이 있습니다. 어떤 방법을 선택하는지는 프로그래머의 기호 및 취향이라고 생각합니다.

 

    RCALL DELAY_MS
    RET

 

 

2) Return Home

 

 Instruction

 RS

 RW

 DB7

 DB6

 DB5

 DB4

 DB3

 DB2

 DB1

 DB0

 Description

 time

Return

Home

0

0

0

0

0

0

0

0

1

X

Set DDRAM address "00H" from AC and return cursor position to its original position if shifted. The contents fo DDRAM are not changed.

1.52mS

 

 

이 명령은 DDRAM 주소를 0으로 위치하는 것은 Clear Display와 같지만, 다음의 두가지 내용이 Clear Display와 다릅니다. 첫째, Clear Display는 DDRAM을 모두 0x20으로 채우지만 Return Home은 DDRAM의 내용은 지우지 않습니다. 둘째, 아래의 명령 5) Cursor or Display shift에서 이동된 내용을 모두 취소하고 원래의 위치 즉, 0번지가 첫 행의 첫 글자가 되도록 원위치 시킵니다.

이 명령도 실행에 1.52mS 소요되므로 명령 실행 후에 3mS 대기 시켜야 하고 호출할 때마다 3mS 대기해야 하므로, 별도로 LCD_RETURN_HOME이라는 함수를 만들었습니다.

DB0의 X 표시는 어느 값이 와도 상관없다는 의미입니다. 즉 이진수 0b00000010을 보내든 0b00000011을 보내든 결과는 같습니다.

 

.EQU LCD_RETURNHOME    = 2

LCD_RETURN_HOME:
    LDI     AL,LCD_RETURNHOME
    RCALL   LCD_WRITE_COMMAND
    LDI     AL,3
    RJMP    DELAY_MS
사용 예)
    RCALL   LCD_RETURN_HOME

 

 

3) Entry Mode Set

 

 Instruction

 RS

 RW

 DB7

 DB6

 DB5

 DB4

 DB3

 DB2

 DB1

 DB0

 Description

 time

Entry

Mode Set

0

0

0

0

0

0

0

1

I/D

SH

Assign cursor moving direction and make shift of entire display moving enable

37uS

 

 

이 명령은 LCD에 문자를 출력한 후에 커서를 이동시킬 방향과 화면을 이동시킬지 여부를 결정하는 명령입니다.

SH의 값을 0으로 설정하면 화면은 이동하지 않고 커서가 이동하며, 이 값을 1로 설정하면 화면이 이동하고 커서는 이동하지 않습니다. 커서와 화면의 이동 방향은 I/D의 값에 따라 결정됩니다.

 

I/D 값과 SH 값에 따라 다음과 같이 네 가지 경우의 수가 나옵니다.


 

 SH = 0

 SH = 1

 I/D = 0

 커서가 한 칸 왼쪽으로 이동

 커서가 한 칸 오른쪽으로 이동

 I/D = 1

 화면이 오른쪽으로 한 칸 이동

 화면이 왼쪽으로 한 칸 이동


 

이 모든 값을 기억하는 것은 매우 번거로운 일이므로 다음과 같이 매크로를 정의해 놓고 사용하는 것이 편리합니다.

 

.EQU LCD_ENTRYMODESET     = 0x04

.EQU LCD_ENTRY_LEFT       = 0x00
.EQU LCD_ENTRY_RIGHT      = 0x02
.EQU LCD_ENTRY_NOSHIFT    = 0x00
.EQU LCD_ENTRY_RIGHTSHIFT = 0x01
.EQU LCD_ENTRY_LEFTSHIFT  = 0x03

 

사용 예)
    LDI     AL,LCD_ENTRYMODESET | LCD_ENTRY_RIGHT
    RCALL   LCD_WRITE_COMMAND

 

사용 예에서는 LCD에 문자를 출력할 때에 화면은 움직이지 않고 커서가 오른쪽으로 이동하도록 설정하고 있습니다. 명령 실행 후에 특별한 조치가 필요 없기 때문에 Clear Display나 Return Home 처럼 별도의 함수를 만들지 않고 직접 명령을 호출하도록 했습니다.

 

 

4) Display ON/OFF Control

 

 Instruction

 RS

 RW

 DB7

 DB6

 DB5

 DB4

 DB3

 DB2

 DB1

 DB0

 Description

 time

 Display ON/

OFF Control

 0

 0

 0

 0

 0

 0

 D

C

B

Set display(D), cursor(C) and blinking of cursor(B) on/off control

37uS

 

화면을 표시할 것인지 여부(D)와 커서 표시 여부(C), 커서를 깜빡거리게 할 것인지 여부(B)를 지정합니다.

D의 값를 0으로 설정하면 화면을 보여주지 않고, 이 값을 1로 설정하면 화면을 보여 줍니다. D 값의 설정은 화면 표시 여부만 결정합니다. 따라서 D를 0으로 설정하여도 화면에 표시만 하지 않을 뿐, 표시할 내용은 그대로 DDRAM에 간직하고 있습니다. D 값이 0이어서 화면을 표시하지 않을 때에도 DDRAM에 정상적으로 출력할 내용을 기록할 수도 있고, 저장된 값을 읽어 올 수도 있습니다. 물론 이 때에 기록한 내용도 D를 1로 설정하면 화면에 표시됩니다. 이 성질을 이용하여 주기적으로 D 값에 0과 1을 입력하면 화면 전체가 깜빡이는 효과를 낼 수 있습니다.

 

C의 값을 0으로 설정하면 커서는 보이지 않으며, 이 값을 1로 설정하면 커서가 보입니다. C 값이 1일 때에 B 값이 1이면 커서가 깜빡이며, B 값이 0이면 커서가 깜빡이지 않습니다.

 

Display ON/OFF Control과 관련된 값은 다음과 같이 매크로로 정의하여 사용합니다.

 

.EQU LCD_DISPLAYCONTROL  = 0x08

.EQU LCD_DISPLAYON       = 0x04
.EQU LCD_DISPLAYOFF      = 0x00
.EQU LCD_CURSORON        = 0x02
.EQU LCD_CURSOROFF       = 0x00
.EQU LCD_BLINKON         = 0x01
.EQU LCD_BLINKOFF        = 0x00

 

사용 예)
    LDI     AL,LCD_DISPLAYCONTROL | LCD_DISPLAYON | LCD_CURSORON | LCD_BLINKON
    RCALL   LCD_WRITE_COMMAND

 

사용 예에서는 화면을 표시하면서 깜빡이는 커서가 보이도록설정하고 있습니다.

 

 

5) Cursor or Display Shift

 

 Instruction

 RS

 RW

 DB7

 DB6

 DB5

 DB4

 DB3

 DB2

 DB1

 DB0

 Description

 time

Cursor or Display Shift

0

0

0

0

0

1

S/C

R/L

X

X

Set cursor moving and display shift control bit, and the direction, without changing DDRAM data

37uS

 

LCD 화면에 표시할 데이터를 보내지 않고 커서를 움직이게 하거나 화면을 좌우로 이동하게 합니다.

S의 값을 0으로 설정하면 R/L의 값에 따라 커서가 한 칸 이동하며 DDRAM의 어드레스 카운터도 이에 맞추어 1만큼 증가 또는 감소합니다. R/L의 값을 0으로 설정하면 커서가 왼쪽으로 한 칸 움직이면서 어드레스 카운터의 값이 1만큼 감소합니다. R/L의 값을 1로 설정하면 커서가 오른쪽으로 한 칸 움직이면서 어드레스 카운터는 1만큼 증가합니다.

S의 값을 1로 설정하면 화면이 이동합니다. 커서는 화면을 따라 이동합니다. 화면이 이동하는 방향은 R/L의 값에 따라 결정됩니다. R/L의 값을 0을 설정하면 화면은 왼쪽으로 이동하고, 이 값을 1로 설정하면 화면은 오른쪽으로 이동합니다. 어드레스 카운터의 값은 변하지 않습니다.

 

 

S = 0 

 S = 1

R/L = 0 

 커서 왼쪽으로 한 칸 이동

어드레스 카운터 1 감소

 커서 오른쪽으로 한 칸 이동

어드레스 카운터 1 증가

R/L = 1

 화면 왼쪽으로 한 칸 이동

어드레스 카운터 변화 없음

 화면 오른쪽으로 한 칸 이동

어드레스 카운터 변화 없음

 

Cursor or Display Shift와 관련 된 값은 다음과 같이 매크로로 정의하여 사용합니다.

 

.EQU LCD_CURSORSHIFT   = 0x10

.EQU LCD_DISPLAYMOVE   = 0x08
.EQU LCD_CURSORMOVE    = 0x00
.EQU LCD_MOVERIGHT     = 0x04
.EQU LCD_MOVELEFT      = 0x00

 

사용 예)
    LDI     AL,LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_LCDMOVERIGHT
    RCALL   LCD_WRITE_COMMAND

 

위의 사용 예에서는 화면을 오른쪽으로 한 칸 이동시키고 있습니다.

 

 

6) Function Set

 

 Instruction

 RS

 RW

 DB7

 DB6

 DB5

 DB4

 DB3

 DB2

 DB1

 DB0

 Description

 time

Function Set

0

0

0

0

1

DL

N

F

X

X

Set interface data length(DL:4bit-8bit), numbers of display line(N:1-line,2-lines), display font type(F:5x8 dots/5x11 dots)

37uS

 

 

이 명령을 이용하여 LCD를 4비트로 제어할지 8비트로 제어할지, LCD의 제어칩이 제어할 행수가 1행인지 2행인지, LCD가 출력할 문자 폰트가 5 x 8 도트인지 5 x 11 도트인지 등을 설정할 수 있습니다.

DL을 0으로 설정하면 명령이나 데이터를 8비트로 전송합니다. DL을 1로 설정하면 명령이나 데이터를 4비트로 나누어 전송해야 합니다. 문자형 LCD는 기본적으로 8비트로 제어하도록 설정되어 있어서, 전원을 투입하거나 리셋한 후에는 8비트로 제어하도록 설정됩니다.

 

N을 0으로 설정하면 LCD 제어칩이 1개의 행만 제어하며, N을 1로 설정하면, 2개의 행을 제어합니다.

 

문자형 LCD는 기본적으로 5 x 7의 문자 폰트와 5 x 11의 문자 폰트를 가지고 있습니다. F를 0으로 설정하면 5 x 7의 문자 폰트를 사용하고, F를 1로 설정하면 5x11의 문자 폰트를 사용합니다.

 

이 명령에 사용할 매크로들을 다음과 같이 정의합니다.

 

.EQU LCD_FUNCTIONSET = 0x20

.EQU LCD_8BITMODE    = 0x10
.EQU LCD_4BITMODE    = 0x00
.EQU LCD_2LINE       = 0x08
.EQU LCD_1LINE       = 0x00
.EQU LCD_5x11DOTS    = 0x04
.EQU LCD_5x8DOTS     = 0x00

 

사용 예)
    LDI     AL,LCD_FUNCTIONSET | LCD_8BITMODE | LCD_2LINE
    RCALL   LCD_WRITE_COMMAND

 

위의 사용 예에서는 8비트로 LCD를 제어하고, 제어칩이 다루어야할 행은 2행, 사용할 폰트는 5 x 8로 지정하고 있습니다. LCD_5x8DOTS의 값이 0이므로 LDI의 값에서 생략하였습니다. LCD_2LINE 뒤에 | LCD_5x8DOTS를 추가해도 코드는 똑 같이 생성되므로 프로그램의 내용을 명확히 하는 것을 좋아하는 프로그래머들은 명시하는 경우도 있습니다. 어떤 형태의 코드를 좋아하는지는 프로그래머의 취향에 따라 자유롭게 선택하는 것이라고 생각합니다.

 

 

7) Set CGRAM Address

 

 Instruction

 RS

 RW

 DB7

 DB6

 DB5

 DB4

 DB3

 DB2

 DB1

 DB0

 Description

 time

Set CGRAM

Address

0

0

0

1

AC5

AC4

AC3

AC2

AC1

AC0

Set CGRAM address in address counter

37uS

 

CGRAM 어드레스 카운터의 주소값을 설정합니다. CGRAM은 LCD 내의 폰트 제어와 관련된 주소로서 데이터 저장장소인 DDRAM과는 다릅니다. 따라서 CGRAM 주소값 설정은 DDRAM 주소값 설정과 아무런 관련이 없습니다.

CGRAM 어드레스 카운터는 Return Home 명령을 실행하면 CGRAM 어드레스 카운터는 자동으로 0의 값을 갖습니다. CGRAM 어드레스 카운터를 이용하여 사용자 정의 문자를 저장하여 사용할 수 있습니다. 사용자 문자를 정의하고 사용하는 방법은 나중에 별도의 글로 다룰 예정입니다.

 

 

8) Set DDRAM Address

 

 Instruction

 RS

 RW

 DB7

 DB6

 DB5

 DB4

 DB3

 DB2

 DB1

 DB0

 Description

 time

Set DDRAM

Address

0

0

1

AC6

AC5

AC4

AC3

AC2

AC1

AC0

Set CGRAM address in address counter

37uS

 

LCD 화면에 문자를 출력하기 위해 데이터를 보내면 이 데이터는 LCD 내부의 DDRAM에 저장됩니다. Set DDRAM Address는 데이터를 저장할 DDRAM의 주소를 지정하는 명령입니다. 데이터를 저장할 DDRAM의 주소를 지정하는 것은 데이터를 출력할 LCD 화면 상의 위치를 지정하는 것과 같습니다.

 

다음은 문자형 LCD 규격별 DDRAM 주소 값과 화면 출력 위치 관계입니다.(숫자는 16진수 입니다.)

 

ㄱ) 16x1 규격

 

 1행

0x00 

 0x01

0x02 

 0x03

 0x04

 0x05

0x06 

0x07 

0x40 

 0x41

0x42 

 0x43

0x44 

0x45 

0x46 

0x47 

 

 

ㄴ) 16x2 규격

 

 1행

0x00

0x01

0x02

0x03

0x04

0x05

0x06

0x07

0x08

0x09

0x0A

0x0B

0x0C

0x0D

0x0E

0x0F

 2행

0x40

0x41

0x42

0x43

0x44

 0x45

0x46

0x47

0x48

0x49

0x4A

0x4B

0x4C

0x4D

0x4E

0x4F

 

 

ㄷ) 20x2 규격

 

1행

0x00

0x01

0x02

0x03

0x04

0x05

0x06

...

0x0C

0x0D

0x0E

 0x0F

0x10

0x11

0x12

0x13

2행

0x40

0x41

0x42

0x43

0x44

0x45

0x46

...

0x4C

0x4D

0x4E

0x4F

0x50

0x51

0x52 

0x53

 

 

ㄹ) 40x2 규격

 

1행

0x00

0x01

0x02

0x03

0x04

0x05

0x06

...

0x20

0x21

0x22

0x23

0x24

0x25

0x26

0x27

2행

0x40

0x41

0x42

0x43

0x44

0x45

0x46

...

0x60

0x61

0x62

0x63

0x64

0x65

0x66

0x67

 

 

 

ㅁ) 16x4 규격

 

 1행

0x00

0x01

0x02

0x03

0x04

0x05

0x06

0x07

0x08

0x09

0x0A

0x0B

0x0C

0x0D

0x0E

0x0F

2행

0x40

0x41

0x42

0x43

0x44

0x45

0x46

0x47

0x48

0x49

0x4A

0x4B

0x4C

0x4D

0x4E

0x4F

3행

0x10

0x11

0x12

0x13

0x14

0x15

0x16

0x17

0x18

0x19

0x1A

0x1B

0x1C

0x1D

0x1E

0x1F

4행

0x50

0x51

0x52

0x53

0x54

0x55

0x56

0x57

0x58

0x59

0x5A

0x5B

0x5C

0x5D

0x5E

0x5F

 

 

ㅂ) 20x4 규격

 

 1행

0x00

0x01

0x02

0x03

0x04

0x05

0x06

...

0x0C

0x0D

0x0E

0x0F

0x10

0x11

0x13

0x13

2행

0x40

0x41

0x42

0x43

0x44

0x45

0x46

...

0x4C

0x4D

0x4E

0x4F

0x50

0x51

0x52

0x53

3행

0x14

0x15

0x16

0x17

0x18

0x19

0x1A

...

0x20

0x21

0x22

0x23

0x24

0x25

0x26

0x27

4행

0x54

0x55

0x56

0x57

0x58

0x59

0x5A

...

0x60

0x61

0x62

0x63

0x64

0x65

0x66

0x67

 

 

ㅅ) 40x4 규격

 

40x2 규격을 2 개 붙여 놓은 것

 

 

LCM4004A는 40x4 규격의 LCD입니다. 이 규격은 40x2 규격의 LCD 제어칩을 두 개 가지고 있습니다. LCM4004A에 글자를 출력하려면 다음과 같은 절차를 거칩니다.

 

a) 적절한 제어칩을 선택합니다.
    앞 글의 LCD_CURRENT_EN에 적절한 값을 넣으면 됩니다.
    1행이나 2행에 출력하려면 제어칩1을 선택하기 위해서 LCD_CURRENT_EN에 (1 << LCD_E1_BIT)의 값을 넣습니다.
    2행이나 3행에 출력하려면 제어칩2를 선택하기 위해서 LCD_CURRENT_EN에 (1 << LCD_E2_BIT)의 값을 넣습니다.

b) 화면에 출력할 위치를 지정하기 위해서 Set DDRAM Address 명령을 이용하여 DDRAM 어드레스 카운터를 적절한 값을 기록합니다.

LDI AL,LCD_SETDDRAMADDR | 어드레스 값 RCALL LCD_WRITE_COMMAND c) 데이터를 전송합니다. LDI AL,데이터 값 RCALL LCD_WRITE_DATA

 

다음은 LCM4004A의 4행 11열에 영문자 'A'를 출력하는 예입니다.

 

.EQU LCD_SETDDRAMADDR  = 0x80

    LDI     AL,1 << LCD_E2_BIT              // 제어칩2를 선택
    STS     LCD_CURRENT_EN,AL
    LDI     AL,LCD_SETDDRAMADDR | 0x4A      // 4행 11열의 주소값은 0x4A
    RCALL   LCD_WRITE_COMMAND
    LDI     AL,'A'                          // 영문자 A 출력

RCALL LCD_WRITE_DATA

 

 

 

(2) LCD 초기화

 

 

LCD에 전원을 투입하면 다음과 같이 보입니다.

 


 

 

1행이나 2행 규격의 LCD는 1행에, 4행 규격의 LCD는 1행과 3행에 위 사진과 같이 네모를 채운 모습을 보여 줍니다. 이는 LCD가 초기화되지 않아 동작하지 않는다는 사실을 사용자에게 알려 주는 의미가 아닐까 생각합니다.

 

LCD를 초기화 하는 과정은 다음과 같습니다.

 

1) 전원 투입 후 전원이 안정될 때까지 대략 30mS 내지 50mS 정도 기다린다.
2) Function Set 명령을 보내고 4.1mS 이상 기다린다.
3) 다시 Function Set 명령을 보내고 100uS 이상을 기다린다.
4) Function Set 명령을 한 번 더 보낸다.
5) Function Set 명령을 또 보낸다.
6) Return Home 명령을 보낸다.
7) Clear Display 명령을 보낸다.
8) Entry Mode Set 명령을 보낸다.
9) DDRAM 어드레스 카운터를 설정한다.
10) Display ON/OFF Control 명령을 보낸다.

 

위 절차 중 2)~4) 과정은 일부의 문자형 LCD 제어칩의 결함때문에 해야하는 작업으로 다른 칩에서는 필요 없을 수도 있습니다. 실제로 LCM4004A를 8비트로 초기화 할 때에는 2)~4) 과정을 생략하였습니다.

 

다음은 LCD 명령을 초기화하는 함수 LCD_INIT의 예입니다.

 

//////////////////////////////////////////////////
// LCD_INIT:
// PARAM NONE
// RETURN NONE
// CHANGED AL
//////////////////////////////////////////////////
LCD_INIT:
    LDI     AL,50                                      // 1) 전원 안정화를 위해 50mS 대기
    RCALL   DELAY_MS
    LDI     AL,LCD_DATA_DDR_VALUE                      // LCD_DATA_PORT를 출력으로 설정
    OUT     LCD_DATA_DDR,AL
    LDI     AL,LCD_CTRL_DDR_VALUE                      // LCD_CTRL_PORT의 RS, RW, E1, E2 설정
    OUT     LCD_CTRL_DDR,AL
    LDI     AL,0                                       // LCD_DATA_PORT, LCD_CTRL_PORT 초기화
    OUT     LCD_CTRL_PORT,AL
    OUT     LCD_DATA_PORT,AL
    LDS     AL,LCD_CURRENT_EN                         // CURRENT_EN을 스택에 저장
    PUSH    AL
    LDI     AL,(1 << LCD_E1_BIT) | (1 << LCD_E2_BIT)  // E1, E2 모두 초기화
    RCALL   LCD_WRITE_CURRENT_EN
    LDI     AL,LCD_FUNCTIONSET | LCD_8BITMODE | LCD_2LINE | LCD_5x8DOTS     // 5) Function Set
    RCALL   LCD_WRITE_COMMAND
    RCALL   LCD_RETURN_HOME                           // 6) Return Home
    RCALL   LCD_CLEAR_DISPLAY                         // 7) Clear Display
    LDI     AL,LCD_ENTRYMODESET | LCD_ENTRY_RIGHT | LCD_ENTRY_NOSHIFT
    RCALL   LCD_WRITE_COMMAND                         // 8) Entry Mode Set 커서 오른쪽 이동
    LDI     AL,LCD_SETDDRAMADDR                       // 9) DDRAM 어드레스 카운터 0
    RCALL   LCD_WRITE_COMMAND
    LDI     AL,LCD_DISPLAYCONTROL | LCD_DISPLAYON     // 10) Display ON/OFF Control
    RCALL   LCD_WRITE_COMMAND
    POP     AL                                        // CURRENT_EN 값 복구
    RJMP    LCD_WRITE_CURRENT_EN

 

위의 LCD_INIT 함수를 호출하면 위 사진의 LCD 화면에 있는 네모들이 모두 없어지고 깨끗한 빈 화면이 나옵니다.

 

 

 

(3) 유용한 LCD 제어 함수

 

 

1) LCD_SET_CURSOR 함수

 

레지스터 AL에 지정하고자 하는 커서 상태를 담아서 호출하면 이 값을 LCD_CURSOR_STATE 변수에 저장하고, AL의 값에 따라 커서를 설정합니다.

 

//////////////////////////////////////////////////
// LCD_SET_CURSOR:
// PARAM AL:(on:LCD_CURSORON | LCD_BLINKON, off:0)
// RETURN NONE
// CHANGED AL
//////////////////////////////////////////////////
LCD_SET_CURSOR:
    STS     LCD_CURSOR_STATE,AL
    ORI     AL,LCD_DISPLAYCONTROL | LCD_DISPLAYON
    RJMP    LCD_WRITE_COMMAND

 

 

2) LCD_GOTOXY 함수

 

LCD 화면에 출력할 위치를 결정하는 함수를 만들어 두면 매우 편리하게 사용할 수 있습니다. 화면에 출력할 위치를 바꿀 때마다 위 8) Set DDRAM Address 명령에서 다른 메모리를 참조하는 것은 매우 번거로운 일입니다. 이 작업을 처리할 함수를 만들어 두고 이 함수를 호출하는 편이 훨씬 간편할 것입니다.

다음의 LCD_GOTOXY 함수는 AL에 x좌표를, AH에 y 좌표를 지정한 다음에 호출하면 DDRAM 어드레스 카운터를 설정하여 주는 기능을 합니다.

 

.DEF BL    = R18
.DEF BH    = R19

//////////////////////////////////////////////////
// LCD_GOTOXY:
// PARAM AL:x, AH:y
// RETURN NONE
// CHANGED AL
//////////////////////////////////////////////////
LCD_GOTOXY:
    PUSH    BL
    PUSH    BH
    MOV     BL,AL
    LDS     BH,LCD_CURSOR_STATE        // ㉠
    LDI     AL,LCD_CURSOROFF           // 현재의 제어칩 커서 감추기
    RCALL   LCD_SET_CURSOR
    STS     LCD_CURSOR_STATE,BH        // store cursor state
    LDI     AL,1 << LCD_E1_BIT         // ㉡ 0행 OR 1행이면 LCD_E1_BIT, 아니면 LCD_E2_BIT
    CPI     AH,2
    BRLO    LCD_GOTOXY_CURRENT_CHIP
    LDI     AL,1 << LCD_E2_BIT
LCD_GOTOXY_CURRENT_CHIP:
    RCALL   LCD_WRITE_CURRENT_EN
    MOV     AL,BH                      // ㉢ 새 제어칩의 커서 관리
    RCALL   LCD_SET_CURSOR
    MOV     AL,BL
    POP     BH
    POP     BL
    SBRC    AH,0                       // ㉣ add 0x40 if y = 1 or 3
    ORI     AL,0x40
    ORI     AL,LCD_SETDDRAMADDR
    RJMP    LCD_WRITE_COMMAND

 

위 LCD_GOTOXY 함수는 x값과 y값 모두 0을 기준으로 동작합니다. 즉, 화면 가장 왼쪽 맨 위 칸의 좌표값은 (0,0)입니다. LCD_GOTOXY 함수는 y값에 따라 제어할 컨트롤러를 결정합니다. y 값이 2보다 작으면 제어 신호로 LCD_E1_BIT를, 그렇지 않으면 LCD_E2_BIT를 선택하여 어드레스 카운터를 지정합니다.

 

이 과정에서 커서를 관리해야 합니다. 제어칩이 1개일 때에는 커서가 자동으로 이동하여 특별히 신경 쓸일이 없지만, 제어칩이 2 개일 경우는 이야기가 다릅니다. 만약 커서를 보여 주고 있던 상태에서 제어칩1에서 제어칩2로 바뀌는 경우 커서가제어칩1(0행이나 1행)에도 나타나는 현상이 발생합니다. 이런 상태를 방지하기 위해서 위의 LCD_GOTOXY 함수에서는 일단 현재 활성화된 제어칩의 커서를 안보임 상태로 만든 후에, 새로 제어할 제어칩에서 이전의 커서 상태를 고려하여 커서의 표시 여부를 결정합니다.

 

㉠ 행에서 현재의 커서 상태(LCD_CURSOR_STATE 변수)를 가져와서 레지스터 BH에 저장합니다. 현재 선택된 제어칩의 커서를 감추기 위하여 레지스터 AL에 LCD_CURSOROFF를 담고 LCD_SET_CURSOR 함수를 호출합니다. LCD_SET_CURSOR 함수에서 LCD_CURSOR_STATE 변수를 변경하므로 레지스터 BH에 저장하고 있던 원래의 LCD_CURSOR_STATE 값을 복구합니다.

㉡ 행 이하에서 y값인 레지스터 AH 값이 2보다 크면 제어칩2를 그렇지 않으면 제어칩1을 선택합니다.

㉢ 행 이하에서 새로 선택된 제어칩에서 LCD_GOTOXY 호출 이전의 커서 상태를 복구합니다.

㉣ 행에서 출력할 행이 짝수인지를 검사하여 짝수행이면 어드레스 카운터 값에 0x40을 더 합니다. LCM4004A 같은 40x4 문자형 LCD의 경우 짝수 행은 0x40 번지부터 시작하기 때문입니다. 레지스터 AL에 Set DDRAM Address 명령을 더해서 LCD_WRITE_COMMAND 함수를 실행하여 데이터를 출력할 위치를 지정합니다.

 

 

3) LCD_SHIFT 함수

 

제어칩이 하나만 있는 LCD의 경우 화면을 좌우로 이동시키는 것은 LCD_CURSORSHIFT 명령을 보내면 됩니다. 그러나 LCM4004A와 같이 제어칩이 두 개인 경우는 LCD_CURSORSHIFT 명령을 보내면 현재 선택된 제어칩이 관리하는 화면만 이동합니다. 즉 화면의 반만 이동하는 결과가 됩니다. 두 개의 칩을 모두 이동시키기 위해서 다음과 같이 LCD_SHIFT 함수를 만들어야 합니다.

 

//////////////////////////////////////////////////
// LCD_SHIFT:
// PARAM AL:LCD_MOVELEFT,LCD_MOVERIGHT
// RETURN NONE
// CHANGED AL
//////////////////////////////////////////////////
LCD_SHIFT:
    PUSH    AH
    LDS     AH,LCD_CURRENT_EN
    PUSH    AL
    LDI     AL,((1 << LCD_E1_BIT) | (1 << LCD_E2_BIT))
    RCALL   LCD_WRITE_CURRENT_EN
    POP     AL
    ORI     AL,LCD_CURSORSHIFT | LCD_DISPLAYMOVE
    RCALL   LCD_WRITE_COMMAND
    MOV     AL,AH
    RCALL   LCD_WRITE_CURRENT_EN
    POP     AH
    RET

 

레지스터 AL에 이동할 방향인 LCD_MOVERIGHT 혹은 LCD_MOVELEFT를 담아 호출하면 LCD의 전 화면이 이동합니다.

 

 

4) LCD_PUT_STRING 함수

 

레지스터 Z에 AVR의 SRAM 주소를 담은 후에 LCD_PUT_STRING 함수를 호출하면 null(0x00)을 만날 때까지의 내용을 LCD 화면에 출력합니다.

 

//////////////////////////////////////////////////
// LCD_PUT_STRING:
// PARAM Z:POINTER
// RETURN NONE
// CHANGED AL,Z
//////////////////////////////////////////////////
LCD_PUT_STRING:
    LD      AL,Z+
    TST     AL
    BREQ    LCD_PUT_STRING_QUIT
    RCALL   LCD_WRITE_DATA
    RJMP   LCD_PUT_STRING
LCD_PUT_STRING_QUIT:
    RET

 

2편과 3편에서 설명한 모든 루틴이 들어 있는 파일 LCM4004A.asm 파일을 첨부합니다. 어셈블리 소스프로그램에서 INCLUDE 문으로 포함시켜 사용하면 됩니다.

 

LCM4004A_8Bit.asm

 

다음은 위 파일을 이용하는 예제 프로그램과 부속 프로그램입니다.

 

LCDM32_8Bit.asm Delay_16mHz.asm


 

 

이 예제에서 실행하는 내용은 다음과 같습니다.

 

LCD_INIT 함수를 호출하여 LCD를 초기화한 다음에 LCD의 커서를 보이지 않게 숨깁니다.

INITIAL_ANIMATION 함수에서 다음과 같은 작업을 합니다.
    1행과 2행, 4행에 메시지를 표시합니다.
    3행과 4행의 내용을 오른쪽으로 이동시켰다가 다시 왼쪽으로 이동시킵니다.
    3행과 4행을 4회 점멸시킵니다.
    화면 0행의 15열로 이동한 후에
        ㉠ 현재의 어드레스 카운터를 스택에 넣고,
        ㉡ 화면의 내용을 읽어서 스택에 넣습니다.
        ㉢ 다시 한 번 화면의 값을 읽어 스택에 넣습니다.
            초기화 프로그램에서 Cursor or Shift Diplay 명령에 특별한 값을 지정하지 않았으므로
            화면에 접근한 후에는 자동으로 어드레스 카운터를 1증가시키도록 설정되어 있습니다.
            이로 인하여 0행의 16열 값을 읽어 옵니다.
        ㉣ 현재의 어드레스 카운터를 스택에 넣습니다.
    2행의 0열로 이동한 후에 위 ㉠~㉣ 값을 하나씩 스택에서 꺼내어 16진수로 출력합니다.
        ㉠의 값은 0행 15열의 어드레서 카운터는 16진수로 0x0F이고 이 위치의 화면 값 ㉡은 'C'(0x43)입니다.
        ㉢의 값은 'D'(0x44)이고 ㉣의 값은 0x11( 0x0F +1 + 1)이어야 합니다.
        ㉠~㉣의 값을 스택에서 하나씩 꺼내어 출력하므로
        화면에 출력되는 값은 ㉣, ㉢, ㉡, ㉠ 순으로 출력되어서 1144430F이 출력됩니다.
    전 화면을 오른쪽으로 한 번 이동 시켰다가 1초 후에 다시 왼쪽으로 한 번 이동시킵니다.

 

 

이상으로 LCM4004A를 비롯한 40x4 문자형 LCD를 8비트로 제어하는 방법에 대한 설명을 모두 마칩니다. 다음 편에서는 40x4 문자형 LCD를 4비트로 동작시키는 과정에 관하여 다루겠습니다.


블로그 이미지

엠쿠스

Microprocessor(STM32, AVR)로 무엇인가를 만들어 보고자 학습 중입니다.

,