C 언어로 LCD 디스플레이를 제어하는 방법

C 언어는 임베디드 시스템 개발에서 널리 사용되는 프로그래밍 언어로, LCD 디스플레이 제어와 같은 하드웨어 상호작용에서도 필수적입니다. 이 기사에서는 LCD 디스플레이의 작동 원리부터 C 언어를 사용해 디스플레이를 제어하는 방법, 그리고 구체적인 코드 구현 예제까지 단계별로 다룹니다. 이를 통해 하드웨어 프로그래밍에 대한 이해도를 높이고, 임베디드 시스템 개발에 필요한 실질적인 기술을 배울 수 있습니다.

LCD 디스플레이란 무엇인가


LCD(Liquid Crystal Display)는 액정 물질을 사용하여 빛을 제어하고 이미지를 표시하는 디스플레이 기술입니다. 주로 텍스트, 숫자, 그래픽을 표시하기 위해 사용되며, 임베디드 시스템에서 상태 표시나 사용자 인터페이스로 널리 활용됩니다.

LCD 디스플레이의 구조


LCD는 아래와 같은 주요 구성 요소로 이루어져 있습니다.

  • 액정 셀: 전압에 따라 빛의 투과율을 조절하는 핵심 부품입니다.
  • 백라이트: 디스플레이의 가독성을 높이기 위해 뒤에서 빛을 비추는 장치입니다.
  • 편광 필터: 액정 셀이 빛의 방향을 조절할 수 있도록 돕습니다.
  • 구동 회로: 전압을 제어하여 화면에 원하는 이미지를 표시합니다.

LCD 디스플레이의 작동 원리


LCD는 전압이 가해질 때 액정 분자가 빛의 투과율을 조절하는 원리를 기반으로 합니다. 이를 통해 특정 픽셀을 밝히거나 어둡게 하여 화면에 이미지를 형성합니다.

임베디드 시스템에서의 역할


임베디드 시스템에서는 LCD 디스플레이를 활용해 다음과 같은 기능을 수행할 수 있습니다.

  • 상태 표시: 시스템의 동작 상태나 오류를 사용자에게 전달.
  • 데이터 입력/출력: 키패드나 터치스크린과 결합하여 입력된 데이터를 보여줌.
  • 사용자 인터페이스: 메뉴, 알림, 그래픽 아이콘 등을 통해 사용자와 소통.

LCD 디스플레이는 저전력 소모와 높은 가독성 덕분에 소형 전자기기부터 산업용 장비까지 폭넓게 사용됩니다.

LCD 디스플레이와 마이크로컨트롤러의 연결

하드웨어 연결 방식


LCD 디스플레이를 마이크로컨트롤러에 연결하기 위해 다음과 같은 하드웨어 구성 요소가 필요합니다.

  • GPIO 핀: 데이터를 송수신하거나 제어 신호를 전송합니다.
  • 전원 공급: LCD 디스플레이에 적절한 전압(Vcc, GND)을 제공합니다.
  • 저항 및 커패시터: 안정적인 신호 전송을 위해 사용됩니다.

병렬 및 직렬 연결


LCD 디스플레이와 마이크로컨트롤러 간의 연결 방식은 병렬 모드와 직렬 모드로 나뉩니다.

  • 병렬 모드:
  • 데이터 핀(D0~D7)을 통해 한 번에 여러 비트를 전송합니다.
  • 빠른 데이터 전송이 가능하지만 더 많은 GPIO 핀이 필요합니다.
  • 직렬 모드(SPI 또는 I2C):
  • 데이터를 한 비트씩 순차적으로 전송합니다.
  • 적은 핀을 사용하므로 복잡도가 낮고, 소형 디바이스에 적합합니다.

핵심 핀 설명


LCD 디스플레이를 제어하기 위해 자주 사용하는 핀들:

  • RS(Register Select): 명령 모드와 데이터 모드 전환.
  • E(Enable): 데이터를 LCD로 전송할 때 트리거 신호 역할.
  • R/W(Read/Write): 데이터를 읽거나 쓰는 모드 선택.
  • D0~D7(Data Pins): 데이터를 LCD로 전송하는 라인.

배선 연결 예시


아래는 16×2 LCD 디스플레이와 마이크로컨트롤러 연결 예입니다.

LCD 핀마이크로컨트롤러 핀설명
VssGND전원 접지
Vcc5V전원 공급
RSGPIOx명령/데이터 선택
RWGND쓰기 모드 고정
EGPIOy활성화 신호
D4~D7GPIOz1~GPIOz4데이터 핀 (4비트 모드)

필수 고려 사항

  1. 전압 호환성: 마이크로컨트롤러와 LCD의 전압이 일치해야 합니다. 필요시 레벨 시프터 사용.
  2. 타이밍 제어: 신호 전송 시 정확한 타이밍을 유지해야 정상적으로 작동합니다.
  3. 전류 제한: 필요시 저항을 추가해 과전류로 인한 손상을 방지합니다.

이 단계에서의 정확한 연결은 LCD 디스플레이의 올바른 동작에 필수적입니다.

C 언어에서 LCD 제어 기본 라이브러리

LCD 제어를 위한 표준 라이브러리


C 언어로 LCD 디스플레이를 제어하려면 하드웨어와 소통할 수 있는 기본 라이브러리와 API가 필요합니다. 일반적으로 마이크로컨트롤러 제조사가 제공하는 라이브러리를 사용하거나, 공개된 오픈소스 라이브러리를 활용합니다.

대표적인 라이브러리

  1. HAL 라이브러리(STM32)
  • HAL(Hardware Abstraction Layer)은 STMicroelectronics에서 제공하는 라이브러리로, 하드웨어 제어를 추상화하여 사용하기 쉽습니다.
  • LCD 제어를 위한 GPIO, I2C, SPI 설정을 간편하게 구성할 수 있습니다.
  1. AVR-Libc(AVR 마이크로컨트롤러)
  • AVR 마이크로컨트롤러를 위한 오픈소스 C 라이브러리로, GPIO 핀 제어 및 타이머 설정 기능을 제공합니다.
  1. Arduino 라이브러리
  • LiquidCrystal 라이브러리는 Arduino 환경에서 LCD 제어를 쉽게 구현할 수 있도록 설계되었습니다.

LCD 제어를 위한 주요 함수


아래는 일반적으로 사용되는 LCD 제어 함수들입니다.

  1. lcd_init()
  • LCD 디스플레이를 초기화하는 함수입니다.
  • 핀 모드 설정, 화면 클리어, 커서 위치 초기화 등을 수행합니다.
   void lcd_init() {
       // GPIO 핀 초기화 및 명령 전송
   }
  1. lcd_command()
  • 명령을 LCD로 전송하여 화면 상태를 변경합니다.
  • 예: 화면 클리어, 커서 위치 변경.
   void lcd_command(unsigned char cmd) {
       // 명령어 전송
   }
  1. lcd_print()
  • 화면에 문자열을 출력합니다.
   void lcd_print(const char *str) {
       while (*str) {
           // 각 문자 출력
       }
   }

코드 예시


아래는 16×2 LCD 디스플레이를 초기화하고 “Hello, World!”를 출력하는 간단한 코드입니다.

#include <avr/io.h>  // AVR 라이브러리 사용

#define RS PD0
#define E  PD1
#define DATA_PORT PORTB
#define CTRL_PORT PORTD

void lcd_init() {
    CTRL_PORT |= (1 << RS) | (1 << E);  // RS, E 핀 초기화
    // 초기화 명령 전송
}

void lcd_command(unsigned char cmd) {
    DATA_PORT = cmd;  // 명령 전달
    CTRL_PORT &= ~(1 << RS);  // 명령 모드 설정
    CTRL_PORT |= (1 << E);
    _delay_ms(2);
    CTRL_PORT &= ~(1 << E);
}

void lcd_print(const char *str) {
    while (*str) {
        DATA_PORT = *str++;
        CTRL_PORT |= (1 << RS);  // 데이터 모드 설정
        CTRL_PORT |= (1 << E);
        _delay_ms(2);
        CTRL_PORT &= ~(1 << E);
    }
}

int main() {
    lcd_init();
    lcd_print("Hello, World!");
    while (1);
}

라이브러리 선택 시 주의점

  1. 마이크로컨트롤러에 적합한 라이브러리 사용: 칩셋에 따라 다른 라이브러리를 선택해야 합니다.
  2. 호환성 확인: 프로젝트에 필요한 기능이 라이브러리에서 지원되는지 확인합니다.
  3. 타이밍 이슈 해결: LCD 디스플레이는 정확한 신호 타이밍이 중요하므로 이를 지원하는 라이브러리가 필요합니다.

이러한 기본 라이브러리를 사용하면 LCD 제어 작업을 더 간단하고 효율적으로 수행할 수 있습니다.

데이터 전송 모드 설정

LCD 데이터 전송 모드 개요


LCD 디스플레이는 데이터를 전송하는 방식에 따라 4비트 모드8비트 모드로 동작할 수 있습니다. 각 모드는 데이터 전송 속도와 핀 사용량에서 차이가 있습니다.

4비트 모드


4비트 모드는 데이터 핀(D4~D7) 4개만 사용해 데이터를 두 번에 나누어 전송하는 방식입니다.

  • 장점: 적은 핀 사용으로 마이크로컨트롤러 핀을 절약할 수 있습니다.
  • 단점: 8비트 모드에 비해 전송 속도가 느립니다.

4비트 모드 데이터 전송 예시

  1. 명령이나 데이터를 상위 4비트와 하위 4비트로 분리합니다.
  2. 상위 4비트를 먼저 전송한 후 하위 4비트를 전송합니다.
void lcd_send_4bit(unsigned char data) {
    // 상위 4비트 전송
    DATA_PORT = (data & 0xF0);  
    CTRL_PORT |= (1 << E);
    _delay_us(1);
    CTRL_PORT &= ~(1 << E);

    // 하위 4비트 전송
    DATA_PORT = (data << 4);  
    CTRL_PORT |= (1 << E);
    _delay_us(1);
    CTRL_PORT &= ~(1 << E);
}

8비트 모드


8비트 모드는 데이터 핀(D0~D7) 8개를 사용해 데이터를 한 번에 전송하는 방식입니다.

  • 장점: 한 번에 데이터를 전송하므로 속도가 빠릅니다.
  • 단점: 8개의 GPIO 핀이 필요하여 핀 자원이 많이 소모됩니다.

8비트 모드 데이터 전송 예시

  1. 명령이나 데이터를 한 번에 8비트로 전송합니다.
  2. 별도의 상/하위 분리가 필요하지 않습니다.
void lcd_send_8bit(unsigned char data) {
    DATA_PORT = data;  
    CTRL_PORT |= (1 << E);
    _delay_us(1);
    CTRL_PORT &= ~(1 << E);
}

모드 설정 코드 예시


LCD를 초기화할 때, 4비트 모드 또는 8비트 모드를 선택할 수 있습니다. 아래는 초기화 코드 예시입니다.

void lcd_init() {
    // 핀 초기화
    CTRL_PORT |= (1 << RS) | (1 << E);
    _delay_ms(20);  // LCD 안정화 대기

    // 4비트 모드 설정
    lcd_send_4bit(0x02);  // 4비트 모드 명령
    lcd_command(0x28);    // Function Set: 4비트 모드, 2라인
    lcd_command(0x0C);    // Display ON, Cursor OFF
    lcd_command(0x06);    // Entry Mode Set
    lcd_command(0x01);    // Clear Display
}

4비트 vs 8비트 모드 선택 기준

  • 4비트 모드
  • 마이크로컨트롤러 핀이 부족할 때 사용.
  • 속도가 중요하지 않은 경우 적합.
  • 8비트 모드
  • 빠른 데이터 전송이 필요한 애플리케이션에 적합.
  • 핀이 충분히 여유 있는 경우 사용.

모드 설정 시 주의사항

  1. 하드웨어 연결 확인: 4비트 모드에서는 D4~D7 핀만 연결해야 합니다.
  2. 초기화 명령 순서 준수: LCD의 데이터 전송 모드 변경은 초기화 단계에서 정확히 설정해야 합니다.
  3. 타이밍 조정: 전송 후 적절한 지연 시간을 설정하여 데이터 유실을 방지합니다.

데이터 전송 모드의 선택은 LCD 디스플레이의 성능과 시스템 요구 사항을 고려해 결정해야 합니다.

문자 및 그래픽 출력

LCD 디스플레이에 문자 출력


LCD 디스플레이는 ASCII 코드를 사용해 문자를 표시합니다. 이를 위해 문자 데이터를 LCD의 데이터 레지스터에 전송해야 합니다.

문자 출력 함수 예시


아래는 단일 문자를 LCD에 출력하는 함수입니다.

void lcd_write_char(char character) {
    DATA_PORT = character;         // ASCII 코드 전송
    CTRL_PORT |= (1 << RS);        // 데이터 모드 설정
    CTRL_PORT |= (1 << E);         // Enable 신호 트리거
    _delay_us(50);                 // 신호 처리 대기
    CTRL_PORT &= ~(1 << E);        // Enable 신호 비활성화
}

문자열 출력


LCD 화면에 여러 문자를 출력하려면 문자열 데이터를 반복적으로 전송합니다.

문자열 출력 함수 예시

void lcd_write_string(const char *str) {
    while (*str) {
        lcd_write_char(*str++);   // 각 문자를 순차적으로 출력
    }
}

커서 위치 제어


LCD 디스플레이는 커서를 이동하여 특정 위치에 문자를 출력할 수 있습니다.

  • LCD 화면의 각 위치는 메모리 주소로 매핑됩니다.
  • 예: 16×2 LCD 디스플레이의 첫 번째 줄 시작 주소는 0x80, 두 번째 줄 시작 주소는 0xC0입니다.

커서 이동 함수 예시

void lcd_set_cursor(uint8_t row, uint8_t col) {
    uint8_t address = (row == 0) ? 0x80 : 0xC0;  // 줄 시작 주소
    address += col;                              // 열 위치 추가
    lcd_command(address);                        // 커서 위치 설정 명령
}

그래픽 출력


일부 LCD 디스플레이는 사용자 정의 문자(Custom Character) 기능을 지원합니다. 이를 활용하면 간단한 그래픽이나 심볼을 출력할 수 있습니다.

사용자 정의 문자 생성

  1. 5×8 도트 매트릭스 형식으로 문자 패턴을 정의합니다.
  2. LCD의 CGRAM(Character Generator RAM)에 패턴을 저장합니다.

사용자 정의 문자 작성 코드 예시

void lcd_create_custom_char(uint8_t location, uint8_t pattern[]) {
    location &= 0x07;               // CGRAM 주소는 0~7번까지만 사용 가능
    lcd_command(0x40 | (location << 3));  // CGRAM 주소 설정
    for (int i = 0; i < 8; i++) {
        lcd_write_char(pattern[i]); // 패턴 데이터 전송
    }
}

커스텀 캐릭터 출력 예시


아래는 웃는 얼굴(☻) 심볼을 표시하는 코드입니다.

uint8_t smiley_face[8] = {
    0b00000,
    0b01010,
    0b01010,
    0b00000,
    0b10001,
    0b01110,
    0b00000,
    0b00000
};

int main() {
    lcd_init();
    lcd_create_custom_char(0, smiley_face); // CGRAM에 심볼 저장
    lcd_set_cursor(0, 0);                   // 커서 위치 설정
    lcd_write_char(0);                      // 사용자 정의 문자 출력
    while (1);
}

문자 및 그래픽 출력 시 고려 사항

  1. 문자 데이터의 유효성 확인: LCD가 표시할 수 없는 문자를 전송하면 표시 오류가 발생할 수 있습니다.
  2. 화면 영역 관리: 표시할 내용이 LCD 화면 크기를 초과하지 않도록 주의해야 합니다.
  3. CGRAM 제한: 사용자 정의 문자 슬롯은 제한적이므로, 필요한 심볼을 우선적으로 저장합니다.

문자와 그래픽 출력을 적절히 사용하면 LCD 디스플레이를 통해 정보를 더 효과적으로 전달할 수 있습니다.

사용자 입력 처리

사용자 입력을 위한 인터페이스


LCD 디스플레이는 주로 키패드, 버튼, 또는 센서를 통해 사용자 입력을 처리하고 이를 화면에 표시하는 데 사용됩니다. 마이크로컨트롤러와 결합하여 입력 데이터를 실시간으로 표시하거나 반응을 출력하는 기능을 구현할 수 있습니다.

키패드 입력 처리


키패드는 행(Row)과 열(Column)로 구성된 매트릭스 형태로 연결되며, 특정 버튼이 눌리면 해당 행과 열의 신호를 읽어 버튼 입력을 감지합니다.

키패드 입력 처리 코드 예시

#define ROWS 4
#define COLS 4

char keypad[ROWS][COLS] = {
    {'1', '2', '3', 'A'},
    {'4', '5', '6', 'B'},
    {'7', '8', '9', 'C'},
    {'*', '0', '#', 'D'}
};

uint8_t row_pins[ROWS] = {R0, R1, R2, R3};  // 행 핀
uint8_t col_pins[COLS] = {C0, C1, C2, C3};  // 열 핀

char get_key() {
    for (int i = 0; i < ROWS; i++) {
        // 현재 행을 활성화
        GPIO_Write(row_pins[i], LOW);
        for (int j = 0; j < COLS; j++) {
            if (GPIO_Read(col_pins[j]) == LOW) {  // 버튼 눌림 감지
                while (GPIO_Read(col_pins[j]) == LOW);  // 버튼 해제 대기
                return keypad[i][j];  // 눌린 버튼 값 반환
            }
        }
        GPIO_Write(row_pins[i], HIGH);  // 현재 행 비활성화
    }
    return '\0';  // 입력 없음
}

버튼 입력 처리


단일 버튼 입력은 GPIO 핀을 통해 간단히 처리할 수 있습니다.

  • 버튼을 누를 때 GPIO 핀의 상태를 읽어 입력을 감지합니다.
  • 풀업 저항이나 풀다운 저항을 사용하여 신호 안정성을 높입니다.

버튼 입력 코드 예시

#define BUTTON_PIN PD0

char button_input() {
    if (GPIO_Read(BUTTON_PIN) == LOW) {  // 버튼 눌림 확인
        _delay_ms(50);  // 디바운싱 처리
        if (GPIO_Read(BUTTON_PIN) == LOW) {  // 버튼 상태 재확인
            return 'B';  // 버튼 값 반환
        }
    }
    return '\0';  // 입력 없음
}

입력 데이터 LCD 출력


사용자 입력 데이터를 LCD 디스플레이에 실시간으로 표시합니다.

키패드 입력 표시 코드 예시

int main() {
    lcd_init();
    lcd_set_cursor(0, 0);  // 커서 초기 위치 설정
    char key;
    while (1) {
        key = get_key();  // 키 입력 읽기
        if (key) {
            lcd_write_char(key);  // 입력된 키를 LCD에 출력
        }
    }
}

버튼 입력 표시 코드 예시

int main() {
    lcd_init();
    lcd_set_cursor(0, 0);  // 커서 초기 위치 설정
    char btn;
    while (1) {
        btn = button_input();  // 버튼 입력 읽기
        if (btn) {
            lcd_write_char(btn);  // 입력된 버튼 값을 LCD에 출력
        }
    }
}

입력 처리 시 주의사항

  1. 디바운싱 처리: 버튼이나 키패드 입력 시 신호의 떨림을 방지하기 위해 지연 시간을 추가합니다.
  2. 입력 에러 처리: 잘못된 입력이나 입력 초과 시 LCD에 경고 메시지를 표시합니다.
  3. 실시간 반응성: 입력 처리가 화면 출력과 원활히 연동되도록 설계합니다.

사용자 입력과 LCD 디스플레이를 조합하면 임베디드 시스템에서 효율적인 인터페이스를 구현할 수 있습니다.

코드 최적화 및 디버깅

LCD 제어 코드 최적화


LCD 디스플레이 제어 코드를 최적화하면 실행 속도와 코드 효율성을 개선할 수 있습니다. 특히, 반복적인 데이터 전송 작업과 같은 연산을 줄이는 것이 중요합니다.

코드 최적화 전략

  1. 매크로 활용
  • 반복적으로 사용되는 명령을 매크로로 정의하여 코드 가독성과 실행 속도를 향상시킵니다.
   #define LCD_CLEAR 0x01
   #define LCD_HOME  0x02
   lcd_command(LCD_CLEAR);
  1. 루프 및 조건문 간소화
  • 중복된 루프와 조건문을 최소화하여 코드의 실행 시간을 줄입니다.
   for (int i = 0; i < num_chars; i++) {
       lcd_write_char(buffer[i]);
   }
  1. 타이밍 제어 최적화
  • 불필요한 지연을 줄이고, 정확한 타이밍을 유지하기 위해 하드웨어 타이머를 활용합니다.
   void delay_us(uint16_t us) {
       // 하드웨어 타이머 활용한 정확한 마이크로초 지연
   }
  1. 메모리 사용 최적화
  • 메모리를 효율적으로 사용하여 디바이스 리소스를 절약합니다. 예를 들어, 문자열은 플래시 메모리에 저장합니다.
   const char message[] PROGMEM = "Hello, World!";

디버깅 기법


LCD 제어는 하드웨어와 소프트웨어가 밀접하게 연관되어 있어 디버깅이 중요합니다. 디버깅은 신호 문제, 타이밍 오류, 코드 로직 오류 등을 탐지하고 수정하는 데 도움을 줍니다.

디버깅 단계

  1. 하드웨어 연결 확인
  • 핀 연결 상태와 전원 공급 여부를 점검합니다.
  • 멀티미터 또는 오실로스코프를 사용해 신호의 정확성을 확인합니다.
  1. LCD 초기화 확인
  • 초기화 명령이 제대로 실행되었는지 확인하기 위해 첫 줄과 두 번째 줄의 커서 상태를 점검합니다.
  1. GPIO 신호 확인
  • 데이터와 제어 핀의 상태를 확인합니다.
  • GPIO 상태를 디버그 출력으로 기록합니다.
   printf("RS: %d, E: %d, Data: %02X\n", RS, E, DATA_PORT);
  1. 테스트 메시지 출력
  • 간단한 테스트 메시지를 LCD에 출력하여 기본 동작 여부를 확인합니다.
   lcd_set_cursor(0, 0);
   lcd_write_string("Test");
  1. 타이밍 문제 점검
  • Enable 핀 신호의 지속 시간을 확인합니다. 너무 짧으면 데이터가 전달되지 않을 수 있습니다.

디버깅 도구

  1. 시리얼 디버깅
  • LCD 디스플레이 동작을 확인하기 위해 데이터를 UART 시리얼로 출력합니다.
   printf("LCD Command Sent: %02X\n", command);
  1. 오실로스코프
  • Enable 신호와 데이터 전송의 타이밍을 시각적으로 분석합니다.
  1. LED 상태 표시
  • LCD 초기화 단계에서 LED를 켜거나 끄는 방식으로 디버깅 과정을 가시화합니다.
   GPIO_Write(LED_PIN, HIGH);  // 단계별 성공 표시

코드 최적화 및 디버깅 예시


아래는 최적화된 코드와 디버깅 로그를 포함한 예제입니다.

void lcd_command(uint8_t cmd) {
    DATA_PORT = cmd;
    CTRL_PORT &= ~(1 << RS);  // 명령 모드
    CTRL_PORT |= (1 << E);
    _delay_us(1);
    CTRL_PORT &= ~(1 << E);

    printf("Command Sent: 0x%02X\n", cmd);  // 디버깅 출력
}

최적화 및 디버깅 시 주의사항

  1. 디버깅 정보 과다 출력 방지
  • 디버깅 로그는 개발 단계에서만 활성화하고, 배포 단계에서는 비활성화합니다.
  1. 타이밍 조정
  • 지연 시간은 LCD 데이터 시트의 권장 값을 준수해야 합니다.
  1. 하드웨어 문제와 소프트웨어 문제 분리
  • 하드웨어 및 소프트웨어 문제를 구분하여 효율적으로 해결합니다.

코드 최적화와 효과적인 디버깅은 LCD 디스플레이를 안정적이고 효율적으로 제어하는 데 필수적인 과정입니다.

실습 예제

16×2 LCD 디스플레이 제어: 실습 프로젝트


이 실습 예제에서는 16×2 LCD 디스플레이를 제어하여 다음 기능을 구현합니다:

  1. LCD 초기화
  2. 문자열 출력
  3. 커서 이동
  4. 사용자 입력 데이터 표시

하드웨어 연결


16×2 LCD 디스플레이와 마이크로컨트롤러를 아래와 같이 연결합니다.

LCD 핀마이크로컨트롤러 핀설명
VssGND전원 접지
Vcc5V전원 공급
RSPD0명령/데이터 선택
RWGND쓰기 모드 고정
EPD1활성화 신호
D4~D7PB0~PB3데이터 핀 (4비트 모드)

LCD 제어 코드

#include <avr/io.h>
#include <util/delay.h>

#define RS PD0
#define E  PD1
#define DATA_PORT PORTB
#define CTRL_PORT PORTD

void lcd_command(uint8_t cmd) {
    DATA_PORT = (cmd & 0xF0);  // 상위 4비트 전송
    CTRL_PORT &= ~(1 << RS);   // 명령 모드
    CTRL_PORT |= (1 << E);
    _delay_us(1);
    CTRL_PORT &= ~(1 << E);

    DATA_PORT = (cmd << 4);    // 하위 4비트 전송
    CTRL_PORT |= (1 << E);
    _delay_us(1);
    CTRL_PORT &= ~(1 << E);

    _delay_ms(2);              // 명령 처리 대기
}

void lcd_data(uint8_t data) {
    DATA_PORT = (data & 0xF0);  // 상위 4비트 전송
    CTRL_PORT |= (1 << RS);     // 데이터 모드
    CTRL_PORT |= (1 << E);
    _delay_us(1);
    CTRL_PORT &= ~(1 << E);

    DATA_PORT = (data << 4);    // 하위 4비트 전송
    CTRL_PORT |= (1 << E);
    _delay_us(1);
    CTRL_PORT &= ~(1 << E);

    _delay_ms(2);              // 데이터 처리 대기
}

void lcd_init() {
    // 핀 설정
    DDRD |= (1 << RS) | (1 << E);  // RS와 E 핀 출력 모드
    DDRB = 0xFF;                   // 데이터 핀 출력 모드
    _delay_ms(20);                 // LCD 안정화 대기

    lcd_command(0x02);  // 4비트 모드 설정
    lcd_command(0x28);  // Function Set: 4비트 모드, 2라인
    lcd_command(0x0C);  // Display ON, Cursor OFF
    lcd_command(0x06);  // Entry Mode Set
    lcd_command(0x01);  // Clear Display
}

void lcd_print(const char *str) {
    while (*str) {
        lcd_data(*str++);
    }
}

void lcd_set_cursor(uint8_t row, uint8_t col) {
    uint8_t address = (row == 0) ? 0x80 : 0xC0;
    address += col;
    lcd_command(address);
}

int main() {
    lcd_init();                       // LCD 초기화
    lcd_set_cursor(0, 0);             // 커서 위치 설정 (첫 줄 첫 번째 열)
    lcd_print("Hello, World!");       // 문자열 출력

    lcd_set_cursor(1, 0);             // 커서 위치 설정 (두 번째 줄 첫 번째 열)
    lcd_print("LCD Ready!");          // 문자열 출력

    while (1);
}

실습 결과

  1. LCD 화면 첫 번째 줄에 Hello, World!가 표시됩니다.
  2. 두 번째 줄에는 LCD Ready!가 표시됩니다.

응용 과제

  1. 동적 데이터 출력: 사용자 입력에 따라 다른 메시지를 출력하는 기능을 추가하세요.
  2. 실시간 업데이트: 센서 데이터를 읽어 LCD 화면에 실시간으로 표시하세요.
  3. 커스텀 문자 출력: 사용자 정의 문자를 생성하고 이를 출력해보세요.

디버깅 및 문제 해결

  1. 화면에 표시되지 않을 경우: 핀 연결 및 전원 공급 상태를 확인합니다.
  2. 문자가 깨져 보일 경우: 초기화 명령과 데이터 전송 타이밍을 점검합니다.
  3. 실행 오류 발생 시: 코드를 단계별로 실행하며 디버깅 로그를 확인합니다.

이 실습 예제를 통해 LCD 디스플레이 제어의 기본 개념과 실제 구현 방법을 익힐 수 있습니다.

요약


이 기사에서는 C 언어를 사용해 16×2 LCD 디스플레이를 제어하는 방법을 다뤘습니다. LCD의 기본 구조와 연결 방법, 데이터 전송 모드 설정, 문자 및 그래픽 출력, 사용자 입력 처리, 코드 최적화 및 디버깅 기법을 설명하고, 실습 예제를 통해 구현 과정을 구체적으로 제시했습니다. 이를 통해 임베디드 시스템 개발에 필수적인 LCD 제어 기술을 실습하며 익힐 수 있습니다.