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

 


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

 

 

 

이번 글인 2편부터 5편까지 총 4회에 걸쳐서 문자형 LCD를 구동시키는 내용을 다루겠습니다.

 

인터넷 상에서 LCD 구동 관련 이론적인 설명과 프로그램 루틴을 어렵지 않게 찾을 수 있습니다. 별로 특이한 내용도 아니면서 한 꼭지 올리는 것이 좀 그렇습니다만, 어셈블리어로 작성한다는 점과 그냥 개인적인 경험을 기록으로 남긴다는 점 등등에 의미를 두면서 글을 씁니다. 게다가 LCM4004A는 데이터시트와 조금 다른 것도 있습니다.

 

 

이번 글에서는 LCM4004A에 명령과 데이터를 전달하고 읽어오는 함수들을 작성해 봅니다.

 

대부분의 문자형 LCD는 8비트로 제어할 수도 있고, 4비트로 제어할 수도 있습니다. 8비트로 제어하면 4비트로 제어하는 것보다 시간적인 면에서는 빠르다는 장점이 있습니다. 반면에 8비트를 연결해야 하므로 배선 수가 많아서 회로가 복잡해질 뿐만 아니라, 마이크로프로세서의 입출력 포트가 적은 경우에는 8비트를 모두 LCD에 할당하는 것이 어려울 수도 있습니다. 4비트로 제어하면 배선 수와 마이크로프로세서의 입출력 포트를 절약할 수 있지만, 데이터를 4비트로 나누어 두 번 전달해야 하므로, 상대적으로 속도가 떨어지고 프로그래밍이 복잡해 질 수도 있습니다.

 

이글에서는 8비트로 제어하는 방법을 먼저 다루고, 4비트 제어하는 방법도 살펴 볼 예정입니다. Avr Studio 4.19 (Build 730)에서 프로그램 작업을 합니다.

 

 

전편의 글에서 LCD에 데이터를 보내는 데이터 포트로 atmega32의 PORTC를 사용하고, LCD 제어 신호인 RS, RW, E1, E2는 각각 PORTD의 7번, 6번, 5번, 4번 핀을 사용하도록 연결했습니다. 이와 같이 연결한 상태에서 동작시킬 프로그램을 작성하기 위해서 다음과 같이 매크로를 정의했습니다.

 

.EQU LCD_DATA_DDR       = DDRC
.EQU LCD_DATA_PIN       = PINC
.EQU LCD_DATA_PORT      = PORTC
.EQU LCD_CTRL_DDR       = DDRD
.EQU LCD_CTRL_PIIN      = PIND
.EQU LCD_CTRL_PORT      = PORTD
.EQU LCD_RS_BIT         = PD7
.EQU LCD_RW_BIT         = PD6
.EQU LCD_E1_BIT         = PD5
.EQU LCD_E2_BIT         = PD4
.EQU LCD_DATA_DDR_VALUE = 0xFF
.EQU LCD_CTRL_DDR_VALUE = ((1 << LCD_RS_BIT) | (1 << LCD_RW_BIT) | (1 << LCD_E1_BIT) | (1 << LCD_E2_BIT))

.DEF AL = R16
.DEF AH = R17
.DEF BL = R18
.DEF BH = R19

 

이와 같이 매크로로 정의한 후에 프로그램하면 여러가지 장점이 있습니다. 프로그램을 작성하면서 "RS는 어느 핀에 연결했더라?"를 다시 생각하지 않아도 되고, 핀 연결을 착각하여 엉뚱한 핀을 제어하여 프로그램이 제대로 동작하지 않는 실수를 방지할 수 있습니다. 뿐만 아니라 차후에 하드웨어적 연결을 변경하여 다른 포트 또는 다른 핀에 연결하게 되었을 때에 프로그램 수정이 아주 간단해 집니다. 매크로만 바꾸어 주면 되기 때문에 여기저기 프로그램을 찾아 다니면서 수정할 필요가 없습니다. 또한 수정하다가 한 군데를 빠뜨려서 제대로 동작하지 않는 사태를 미리 방지할 수도 합니다.

 

이하의 글엥서는 각각의 포트들과 비트들이 적절한 값으로 초기화 된 것을 가정합니다. 구체적으로는 LCD의 데이터와 명령을 출력하는 포트(본 글의 경우 PORTC)의 모든 비트들과 LCD를 제어하는 신호가 연결된 포트의 해당 비트(본 글의 경우 PORTD의 PD4, PD5, PD6, PD7)들 역시 출력으로 지정되어 있어야 합니다. 또한, 제어신호가 연결된 포트의 해당 비트들의 값이 0(low)로 되어 있어야 합니다. 보통은 이런 작업들을 하는 함수를 만들어 놓고 LCD를 초기화하기 전에 이 함수를 호출하는 방법으로 구현합니다.

 

 

 

(1) 명령을 전송하는 함수 만들기

 

본격적으로 LCD를 동작시키려면 제일 먼저 데이터시트의 타이밍 차트를 봐야 합니다. 다음은 데이터시트에 있는 avr로부터 LCD로 데이터를 전송할 때 따라야 하는 타이밍 차트입니다.

 


 

 

다음은 위의 타이밍 챠트에 필요한 time table입니다.

 


 

 

정식으로 전자 관련 공부를 한 적이 없는지라 해석하기가 참 난감했습니다. 해석한 결과는 다음과 같습니다. low는 0V를 의미하고, high는 5V를 의미합니다.

 

1) LCD의 E 신호는 low, RW 신호도 low, RS신호는 low 또는 high로 한다.

  (LCD에 명령을 전달하려면 RS 신호를 low로 해야 하고, 데이터를 전달하려면 RS 신호를 high로 해야 합니다. LCD에 대한 명령은 뒷 부분에서 다룹니다.)

 

2) tSU1 시간이 경과하도록 기다린다. Time table에 tSU1은 최소 0부터입니다. 따라서 특별히 기다릴 필요가 없습니다. 1)의 RW 신호및 RW 신호와 3)의 E 신호는 동시에 조작해도 된다는 의미입니다.

 

3) LCD의 E 신호를 high로 한다.

 

4) LCD의 DB0부터 DB7 핀에 데이터를 설정한다.

 

5) 3)으로부터는 tw시간(140nS) 이상이 경과하고, 4)로부터는 tSU2시간(40nS) 이상이 경과한 후에 LCD의 E 신호를 low로 한다.

 

6) tH1(10nS) 시간 이상이 경과한 후에 RW 신호를 high로 한다.

 

7) 3)으로부터 tc(1,200nS) 이상의 시간이 경과한 후에 LCD의 E 신호를 high로 한다. 즉, 데이터를 기록은 최소 1,200nS만큼의 간격으로 해야 한다. 다시 말해서 데이터를 기록한 다음에는 1,200nS 이상이 경과한 후에 다음 데이터를 기록해야 한다는 의미입니다.

 

 

Avr의 명령들은 1 클럭 사이클에 실행되는 것이 많습니다. Avr의 클럭으로 16MHz를 사용한다면 1클럭 사이클은 1/16,000,000초 즉, 62.5nS입니다. 따라서 62.5nS보다 작은 대기 시간은 신경 쓰지 않아도 됩니다. 결국 140nS인 tw와 1,200nS인 tc만 신경 쓰면 됩니다.

 

다음의 프로그램 루틴은 LCM4004A에 명령을 전달하는 기능을 하는 함수 LCD_WRITE_COMMAND 함수 입니다. 이 함수는 AL 레지스터에 담겨 있는 명령을 LCD로 전송합니다. LCD의 E 신호는 초기화할 때에 low로 설정한 것으로 가정합니다. 각주로 간단한 설명을 붙였습니다.

 

//////////////////////////////////////////////////
// LCD_WRITE_COMMAND:
// PARAM AL:COMMAND
// RETURN NONE
// CHANGED NONE
//////////////////////////////////////////////////
LCD_WRITE_COMMAND:
    PUSH    AL
    OUT     LCD_DATA_PORT,AL
    CBI     LCD_CTRL_PORT,LCD_RS_BIT      // RS = low
    CBI     LCD_CTRL_PORT,LCD_RW_BIT      // RW = low
    SBI     LCD_CTRL_PORT,LCD_E1_BIT      // E1 = high
    RCALL   DELAY_100US
    CBI     LCD_CTRL_PORT,LCD_E1_BIT      // E1 = low
    RCALL   DELAY_1MS
    POP     AL
    RET

 

위의 LCD_WRITE_COMMAND 함수의 코드는 위의 타이밍 차트에서 살펴 본 순서 1) ~ 7)의 내용과 조금 다릅니다.

 

첫째, 4)에 있던 데이터 설정을 1)보다 먼저했습니다. 이렇게 해도 함수는 정상적으로 동작하며, 이후에 레지스터 AL을 자유롭게 사용할 수 있습니다.

둘째, 데이터시트 상에 tw는 140nS 이상으로 되어 있습니다. PUSH 명령과 POP 명령은 각각 2클럭을 사용하므로 PUSH AL, POP AL만 해도 4 클럭이라 250nS가 되어서 tw로는 충분해야 합니다. 그러나 실제로 이 LCD에서는 제대로 동작하지 않았습니다. 수차례 시행 착오를 반복하다가 100uS 정도 대기해야 정상적으로 동작하는 것을 확인하였습니다. 100uS 만큼 대기하는 함수 DELAY_100US를 만들어서 호출하도록 했습니다.

 

데이터시트 상에 tC가 1,200uS 즉 1.2mS 이상되어야 하므로 함수의 맨 끝 부분에서 1mS 대기하는 함수를 호출합니다. DELAY_100US와 카찬가지로 DELAY_1MS 함수도 별도로 만들어서 호출하도록 했습니다.

 

위 함수는 일반적인 문자형 LCD를 제어하기에 적절한 함수입니다. LCD를 동작시키는 E(enable) 신호로 E1 하나만 제어하고 있습니다. 16 x 2 또는 20 x 2, 20 x 4 등의 문자형 LCD는 제어칩이 하나이지만, LCM4004A와 같이 40x4 형의 문자형 LCD는 제어칩이 두 개이므로 위의 함수를 약간 수정해야 합니다. 앞의 매크로 정의 부분에서도 첫번째 제어칩의 E 신호를 LCD_E1_BIT로, 두번째 제어칩의 E 신호를 LCD_E2_BIT로 정의했습니다.

40 x 4의 문자형 LCD의 1행과 2행에 작업을 하려면 LCD_E1_BIT를, 3행과 4행에 작업을 하려면 LCD_E2_BIT를 제어해야 합니다. 위의 LCD_WRITE_COMMAND 함수는 LCD_E1_BIT만 제어하므로 1행과 2에만 명령을 전달할 수 있습니다. 4행을 모두 다 쓰려면 LCD_E2_BIT도 제어할 수 있도록 해야 합니다. LCD_E1_BIT와 LCD_E2_BIT를 모두 제어하는 방법은 이글의 아래 부분에 있는 (4) 두 개의 제어칩 다루기에서 설명합니다.

 

 

 

(2) 데이터를 전송하는 함수 만들기

 

LCD에 데이터를 전송하는 과정는 기본적으로는 명령을 전송하는 과정과 같습니다. 다만 전달하는 내용이 명령이 아니라 데이터이기 때문에 RS 비트를 high로 한다는 점만 다릅니다.

 

//////////////////////////////////////////////////
// LCD_WRITE_DATA:
// PARAM AL:DATA
// RETURN NONE
// CHANGED NONE
//////////////////////////////////////////////////
LCD_WRITE_DATA:
LCD_PUT_CHAR:
    PUSH    AL
    OUT     LCD_DATA_PORT,AL
    SBI     LCD_CTRL_PORT,LCD_RS_BIT      // RS = high
    CBI     LCD_CTRL_PORT,LCD_RW_BIT      // RW = low
    SBI     LCD_CTRL_PORT,LCD_E1_BIT      // E1 = high
    RCALL   DELAY_100US
    CBI     LCD_CTRL_PORT,LCD_E1_BIT      // E1 = low
    RCALL   DELAY_1MS
    POP     AL
    RET

 

LCD_WRITE_DATA 함수를 호출하면 레지스터 AL에 담겨 있는 내용이 데이터로 LCD에 전달됩니다. LCD에 데이터를 전송하는 목적은 대부분 데이터를 LCD 화면에 출력하고자 함입니다. 그래서 이 함수를 비교적 친숙한 이름인 LCD_PUT_CHAR라는 이름으로도 정의하여서 프로그래밍하는 도중에 편리하게 사용할 수 있도록 하였습니다.

 

 

 

(3) 명령 전송 함수와 데이터 전송 함수 합치기

 

명령을 기록하는 LCD_WRITE_COMMAND 함수와 과 데이터를 기록하는 LCD_WRITE_DATA 함수는 딱 한 행만 다르기 때문에, 위의 두 함수를 합하여 다음과 변형하여 사용합니다. LCD_WRITE_COMMAND 함수에 RJMP 명령이 추가되어서 2 클럭 사이클만큼 시간이 더 지체되기는 합니다. 그러나, LCD 자체가 동작 속도가 그리 빠른 편이 아니라서 실 사용에는 아무런 문제가 없습니다. 한 바이트의 메모리가 아깝던 시절에 많이 사용하던 방법입니다.

유일하게 다른 부분인 RS 처리 부분을 맨 앞으로 보내고, 나머지 공통되는 작업들은 LCD_WRITE_QUIT 이하의 루틴에서 처리하도록 수정했습니다.

 

//////////////////////////////////////////////////
// LCD_WRITE_COMMAND:
// PARAM AL:COMMAND
// RETURN NONE
// CHANGED NONE
//////////////////////////////////////////////////
LCD_WRITE_COMMAND:
    CBI     LCD_CTRL_PORT,LCD_RS_BIT      // RS = low
    RJMP    LCD_WRITE_QUIT
//////////////////////////////////////////////////
// LCD_WRITE_DATA:
// PARAM AL:DATA
// RETURN NONE
// CHANGED NONE
//////////////////////////////////////////////////
LCD_WRITE_DATA:
LCD_PUT_CHAR:
    SBI     LCD_CTRL_PORT,LCD_RS_BIT      // RS = high
LCD_WRITE_QUIT:
    PUSH    AL
    OUT     LCD_DATA_PORT,AL
    CBI     LCD_CTRL_PORT,LCD_RW_BIT      // RW = low
    SBI     LCD_CTRL_PORT,LCD_E1_BIT      // E1 = high
    RCALL   DELAY_100US
    CBI     LCD_CTRL_PORT,LCD_E1_BIT      // E1 = low
    RCALL   DELAY_1MS
    POP     AL
    RET

 

 

 

(4) 두 개의 제어칩 다루기

 

앞에서 언급한 바대로 4004 문자형 LCD에서는 문자를 출력하거나 명령을 전송할 때에, 그 대상 행에 따라 LCD_E1_BIT와 LCD_E2_BIT를 적절히 제어해야 합니다. LCD의 1행과 2행은 LCD_E1_BIT으로 제어하고, 3행과 4행은 LCD_E2_BIT로 제어해야 합니다.

다음과 같은 요령으로 LCD_E1_BIT와 LCD_ E2_BIT를 제어합니다.

 

1) 변수 LCD_CURRENT_EN을 만들어 놓고, 제어하려는 행에 따라 적절한 값을 넣는다.

2) LCD_WRITE_COMMAND 함수나 LCD_WRITE_DATA 함수에서 LCD_CURRENT_EN 변수의 값을 읽어 그 내용에 따라 E1과 E2를 제어한다.

 

 

1) 단계에서는 다음과 같이 변수 LCD_CURRENT_EN 변수를 선언하고, 변수 LCD_CURRENT_EN에 값을 기록하는 함수 LCD_WRITE_CURRENT_EN 함수를 작성합니다.

 

.DSEG
LCD_CURRENT_EN:    .DB   0

.CSEG
//////////////////////////////////////////////////
// LCD_WRITE_CURRENT_EN:
// PARAM AL:VALUE
// RETURN NONE
// CHANGED NONE
//////////////////////////////////////////////////
LCD_WRITE_CURRENT_EN:
    STS     LCD_CURRENT_EN,AL
    RET

LCD_CURRENT_EN의 값을 바꿀 때에는 꼭 위의 LCD_WRITE_CURRENT_EN 함수를 통하는 것이 좋습니다. 이런 방식으로 LCD_CURRENT_EN 변수를 제어하면 LCD가 의도한대로 동작하지 않아 디버깅해야 때에 프로그램의 어느 부분에서 LCD_CURRENT_EN 변수의 값을 잘못 지정하고 있는지를 파악하기가 쉬워집니다.

 

 

다음은 LCD_CURRENT_EN 값을 읽어와서 그 값에 따라 LCD_E1_BIT와 LCD_E2_BIT를 제어하는 LCD_WRITE_COMMAND 함수와 LCD_WRITE_DATA 함수의 예입니다. 이 루틴은 LCM4004A 뿐만 아니라 다른 LCD에서도 정상 동작합니다. 가지고 있는 40x4 문자형 LCD가 두 종류 밖에 없어서 더 이상 테스트하지는 못하지만 다른 2행, 4행 문자형 LCD에서 모두 정상 동작하는 것을 확인하였습니다.

 

//////////////////////////////////////////////////
// LCD_WRITE_COMMAND:
// PARAM AL:COMMAND
// RETURN NONE
// CHANGED NONE
//////////////////////////////////////////////////
LCD_WRITE_COMMAND:
    CBI     LCD_CTRL_PORT,LCD_RS_BIT      // RS = low
    RJMP    LCD_WRITE_QUIT
//////////////////////////////////////////////////
// LCD_WRITE_DATA:
// PARAM AL:DATA
// RETURN NONE
// CHANGED NONE
//////////////////////////////////////////////////
LCD_WRITE_DATA:
LCD_PUT_CHAR:
    SBI     LCD_CTRL_PORT,LCD_RS_BIT      // RS = high
LCD_WRITE_QUIT:
    PUSH    AL
    PUSH    AH
    OUT     LCD_DATA_PORT,AL
    CBI     LCD_CTRL_PORT,LCD_RW_BIT      // RW = low
    LDS     AH,LCD_CURRENT_EN
    SBRC    AH,LCD_E1_BIT
    SBI     LCD_CTRL_PORT,LCD_E1_BIT      // E1 = high
    SBRC    AH,LCD_E2_BIT
    SBI     LCD_CTRL_PORT,LCD_E2_BIT      // E2 = high
    RCALL   DELAY_100US
    SBRC    AH,LCD_E1_BIT                 // E1 = low
    CBI     LCD_CTRL_PORT,LCD_E1_BIT
    SBRC    AH,LCD_E2_BIT
    CBI     LCD_CTRL_PORT,LCD_E2_BIT      // E2 = low
    RCALL   DELAY_500US
    POP     AH
    POP     AL
    RET

 

 

위의 LCD_WRITE_COMMAND 함수와 LCD_WRITE_DATA 함수를 사용하는 방법을 정리하면 다음과 같습니다.

 

1) LCD_WRITE_CURRENT_EN 함수를 이용하여 제어 할 칩을 결정합니다.

1행이나 2행을 제어할 예정이면 (1 << LCD_E1_BIT)을, 3행이나 4행을 제어할 예정이면 (1 << LCD_E2_BIT)을, 1행부터 4행까지 모두 제어할 예정이면 ((1 << LCD_E1_BIT) | (1 << LCD_E2_BIT))을 레지스터 AL에 담은 후에 LCD_WRITE_CURRENT_EN 함수를 호출합니다. LCD_CURRENT_EN에 기록된 값은 MCU 내부의 램에 보관되므로 전원이 끊어지거나 프로그램에서 수정하지 않으면 그 값을 유지하고 있습니다.

 

2) 레지스터 AL에 전달하고자하는 값을 담은 후에 명령을 전달하려면 LCD_WRITE_COMMAND 함수를, 데이터를 전달하려면 LCD_WIRTE_DATA 함수를 각각 호출합니다.

다음은 첫 번째 제어칩과 두 번째 제어칩으로 영문자 'A'를 출력하는 예입니다.

 

    LDI     AL,(1 << LCD_E1_BIT) | (1 << LCD_E2_BIT)
    RCALL   LCD_WRITE_CURRENT_EN
   (중략)
    LDS     AH,LCD_CURRENT_EN
    LDI     AL,'A'
    RCALL   LCD_WRITE_DATA

 

 

 

(5) LCD로부터 값 읽어 오기

 

LCD의 상태나 LCD로부터 데이터를 읽어 오는 함수를 만드는 작업도 timing chart를 보는 것으로부터 시작합니다. 다음은 매뉴얼에 있는 timing diagram과 timing table입니다.

 


 

 


 

위에서 보았던 write의 경우와 크게 달라 보이지는 않습니다. 이전 글에서 설명한대로 16MHz로 동작하는 avr의 경우 1클럭은 62.5nS가 걸리므로 62.5nS 이하의 타이밍은 신경 쓸 필요가 없습니다. 62.5nS를 초과하는 항목은 tc 1,200nS, tw 140nS, tD 100nS입니다.

 

데이터 포트인 LCD_DATA_PORT에서 데이터를 읽은 후에 이 포트에 값을 써 넣을 필요가 없으므로 그대로 둘 예정입니다. 따라서 tD는 신경 쓸 필요가 없습니다. 또한 LCD에 명령을 전달하는 과정에서는 연속적으로 명령을 전달하기 때문에 tc를 심각히 고려해야하지만, 데이터를 읽어오는 작업을 연속적으로 해야할 경우는 거의 없습니다. 따라서 tc는 함수 종료할 때쯤 100uS정도 대기하는 것으로 합니다. 다만 tw 140nS는 LCD_WRITE_COMMAND의 경우에서와 같이 데이터시트와 LCM4004A의 시간이 맞지 않아 시행 착오 끝에100nS 지연시키는 것으로 했습니다.

 

LCD_WRITE_COMMAND 함수와 LCD_WRITE_DATA의 함수는 RS 값만 다르고 다른 루틴은 똑 같았던 것과 마찬가지로, LCD로부터 데이터를 읽어오는 LCD_READ_DATA 함수와 LCD의 Busy Flag 값과 주소값을 읽어오는 LCD_READ_BF_ADDRESS 함수도 RS 값만 다릅니다.

다음은 LCD_READ_DATA 함수와 LCD_READ_BF_ADDRESS 함수입니다.

 

//////////////////////////////////////////////////
// LCD_READ_DATA:
// PARAM NONE
// RETURN AL
// CHANGED AL
//////////////////////////////////////////////////
LCD_READ_DATA:
    SBI     LCD_CTRL_PORT,LCD_RS_BIT      // RS = 1
    RJMP    LCD_READ_QUIT
//////////////////////////////////////////////////
// LCD_READ_BF_ADDRESS:
// PARAM NONE
// RETURN AL
// CHANGED AL
//////////////////////////////////////////////////
LCD_READ_BF_ADDRESS:
    CBI     LCD_CTRL_PORT,LCD_RS_BIT      // RS = 0
LCD_READ_QUIT:
    PUSH    AH
    LDI     AH,0                          // set LCD_DATA_PORT input
    OUT     LCD_DATA_DDR,AH
    SBI     LCD_CTRL_PORT,LCD_RW_BIT      // RW = 1
    LDS     AH,LCD_CURRENT_EN
    SBRC    AH,LCD_E1_BIT
    SBI     LCD_CTRL_PORT,LCD_E1_BIT      // E1 = 1
    SBRC    AH,LCD_E2_BIT
    SBI     LCD_CTRL_PORT,LCD_E2_BIT      // E2 = 1
    RCALL   DELAY_100US
    IN      AL,LCD_DATA_PIN
    SBRC    AH,LCD_E1_BIT
    CBI     LCD_CTRL_PORT,LCD_E1_BIT      // E1 = 0
    SBRC    AH,LCD_E2_BIT
    CBI     LCD_CTRL_PORT,LCD_E2_BIT      // E2 = 0
    LDI     AH,LCD_DATA_DDR_VALUE
    OUT     LCD_DATA_DDR,AH
    RCALL   DELAY_100US
    POP     AH
    RET

 

LCD_WRITE_QUIT 루틴과 다른 부분만 언급하도록 하겠습니다.

 

LCD_WRITE_QUIT에서는 데이터포트(LCD_DATA_PORT, 실제로는 PORTC)가 모두 출력으로 설정된 것으로 가정하고 프로그램을 작성했습니다. LCD_READ_QUIT에서는 데이터포트로부터 입력을 받아야 하므로 데이터포트인 LCD_DATA_PORT를 입력 모드로 설정하여 입력을 받은 후에 다시 출력 모드로 바꾸어 놓아야 합니다. 이 작업을 위하여 LCD_DATA_DDR에 적절한 값을 설정하는 부분이 추가 되어 있습니다.

 

IN   AL,LCD_DATA_PIN 부분이 데이터를 입력 받는 루틴입니다. LCD_DATA_PIN(실제로는 PINC)의 값을 레지스터 AL에 담습니다.

 

LCD_READ_DATA 함수를 호출하면 LCD의 어드레스 카운터가 가리키고 있는 메모리의 값을 읽어 AL 레지스터에 담아서 리턴합니다.

 

LCD_READ_BF_ADDRESS 함수를 호출하면 레지스터 AL에 리턴값이 저장됩니다. AL의 0비트부터 6비트까지 총 7개비트에는 제어칩 내부의 어드레스 카운터 값이 담겨 있고, 최상위 비트인 Bit7에는 LCD의 Busy Flag의 값이 담겨 있습니다.

 

문자형 LCD의 제어칩은 내부 메모리로 하나 당 최대 128 바이트를 가지고 있으며, 이 내부 메모리에 접근하기 위해서 7비트 크기의 어드레스 카운터를 가지고 있습니다. LCD_BF_ADDRESS 함수를 호출하면 제어칩 내부의 어드레스 카운터의 값을 가져 옵니다. 어드레스 카운터의 값은 0부터 0x7F(십진수 127)사이의 값을 갖습니다.

Busy Flag은 LCD가 명령이나 데이터를 받아서 작업을 하는 동안 1로 설정됩니다. 명령 실행이 끝나면 자동으로 0으로 설정됩니다. 이 성질을 이용하면 명령이 끝날 때까지 기다리는 함수를 만들 수 있습니다. LCD의 명령 실행이 끝날 때까지 기다렸다가 다음 명령을 실행하게 할 수도 있습니다.

무조건 일정 시간을 기다리는 것보다 명령을 실행하기 전에 Busy Flag을 검사하여 Busy Flag이 0일 경우에 LCD에 접근하는 방법이 더 효율적인 것으로 생각이 듭니다. 그러나, 다음 글에서 다루는 바와 같이 LCD 초기화 과정에서는 Busy Flag을 검사하면 안되는 경우가 있습니다. 결국 명령을 보내는 함수를 Busy Flag을 검사하는 함수와 검사하지 않는 함수로 두 종류로 만들어야 한다는 문제가 생깁니다. 단순히 일정 시간을 기다리는 방법과 이 방법 중 어느 방법을 택할 것인지는 프로그래머의 선택 사항이라고 생각합니다.

 

다음의 LCD_WRITE_COMMAND_BUSYCHECK 함수는 Busy Flag을 검사하는 루틴을 추가한 예입니다.

 

.EQU LCD_BUSY_FLAG = 7

LCD_WRITE_COMMAND_BUSYCHECK:
    PUSH    AL
LCD_WRITE_COMMAND_BUSYCHECK_WAIT:
    RCALL LCD_READ_BF_ADDRESS
    SBRC    AL,LCD_BUSY_FLAG
    RJMP    LCD_WRITE_COMMAND_BUSYCHECK_WAIT
    POP     AL
    RJMP    LCD_WRITE_COMMAND

 

LCD_READ_BF_ADDRESS 함수를 호출하면 AL의 값이 바뀌기 때문에 AL을 스택에 보관한 후에 LCD_READ_BF_ADDRESS 함수를 호출합니다. LCD_READ_BF_ADDRESS 함수의 리턴 값인 AL의 LCD_BUSY_FLAG(비트7)을 검사하여 이 비트가 0이면 Busy Flag 검사 루프를 빠져 나온 후에 스택에 보관한 원래의 명령을 꺼내서 LCD_WRITE_COMMAND 함수로 갑니다. AL의 LCD_BUSY_FLAG 비트가 1이면 LCD_WRITE_COMMAND_BUSYCHECK_WAIT로 가서 Busy Flag을 검사하는 과정을 반복합니다.

 

 

이상으로 40x4 문자형 LCD를 제어하기 위한 기본적인 함수들은 다 마련하였습니다. 문자형 LCD를 사용하기 위한 기초 공사는 모두 마친 상태입니다. 다음 편에서는 문자형 LCD를 제어하는 명령들을 익히고, 이를 이용하여 문자형 LCD를 사용하도록 하겠습니다.


블로그 이미지

엠쿠스

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

,