아두이노 우노로 만드는 LCD 애니메이션 & 게임 기초
부품 및 소모품
| 부품 | 수량 |
| 아두이노 우노 (Arduino UNO) | 1 |
| 영숫자 LCD 16x2 (Alphanumeric LCD, 16x2) | 1 |
소프트웨어
프로젝트 개요
아두이노와 16x2 캐릭터 LCD를 이용해 기본적인 텍스트 출력부터 스크롤, 커스텀 캐릭터, 간단한 애니메이션, 그리고 버튼 입력을 활용한 미니 게임까지 단계별로 구현하는 튜토리얼입니다. 다른 크기의 LCD에도 응용 가능합니다.
1단계: LCD 배선

| LCD 핀 | 연결 |
| 1: VSS (GND) | 브레드보드 음극 레일 (아두이노 GND와 연결) |
| 2: VDD (VCC) | 브레드보드 양극 레일 (아두이노 5V와 연결) |
| 3: VO (Contrast) | 가변저항(포텐셔미터) 중간 단자 (양끝은 양극/음극 레일) |
| 4: RS (Register Select) | 아두이노 디지털 핀 12 |
| 5: R/W (Read/Write) | 음극 레일 (쓰기 모드 고정) |
| 6: E (Enable) | 아두이노 디지털 핀 11 |
| 11: D4 | 아두이노 디지털 핀 4 |
| 12: D5 | 아두이노 디지털 핀 5 |
| 13: D6 | 아두이노 디지털 핀 6 |
| 14: D7 | 아두이노 디지털 핀 7 |
| 15: A (Anode, 백라이트 +) | 양극 레일 (220Ω 저항 직렬) |
| 16: K (Cathode, 백라이트 -) | 음극 레일 |
데이터 핀(D4~D7) 연결 전 전원만 인가해 하단 줄이 켜지는지 확인하세요. 안 켜지면 납땜/배선 재점검 또는 LCD 불량 의심.
2단계: 기본 출력 테스트
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 4, 5, 6, 7); // rs, e, d4, d5, d6, d7
void setup() {
lcd.begin(16, 2);
lcd.setCursor(0, 1);
lcd.print("Working");
}
void loop() {}
"Working"이 정상 표시되면 배선 성공. 안 보이면 대비 조절, 데이터 핀 납땜 쇼트 여부, 핀 번호 일치 여부 확인.
3단계: 코드 구조 이해
LiquidCrystal lcd(12,11,4,5,6,7); - 인터페이스 핀 지정으로 lcd 객체 생성.
lcd.begin(16,2); - LCD 열(16), 행(2) 설정.
lcd.setCursor(0,1); - 커서를 0열, 1행(두 번째 줄)으로 이동 (인덱스 0부터 시작).
lcd.print("Welcome"); - 커서 위치에 문자열 출력.
4단계: 텍스트 스크롤링 (광고판 효과)
void setup() { lcd.begin(16,2); }
void loop() {
int n = 15;
char message[] = "scrolling text!";
char* ptr = message;
while (n != -14) {
lcd.clear();
lcd.setCursor(n, 1);
if (n < 0) ptr++; // 화면 왼쪽 밖으로 나간 문자 스킵
lcd.print(ptr);
n--;
delay(250); // 속도 조절
}
}
포인터 연산으로 문자열을 한 칸씩 왼쪽으로 이동시키며 스크롤 효과 구현. delay()로 속도 조절.
5단계: 커스텀 캐릭터 정의 (예: 공룡)
8x5 픽셀 패턴을 바이트 배열로 정의:
byte dino[] = {
B01110, B01110, B01100, B01111,
B11101, B01010, B01010, B01010
};
캐릭터 생성 및 출력:
void setup() {
lcd.createChar(0, dino); // 0번 슬롯에 등록
lcd.begin(16,2);
lcd.setCursor(0,1);
lcd.write(byte(0)); // 0번 커스텀 문자 출력
}
직접 만들기: LCD Character Creator
6단계: 화살 발사 애니메이션 (커스텀 문자 3개 조합)
byte arrowhead[] = { B00001, B00011, B00111, B01111, B00111, B00011, B00001, B00000 };
byte arrowbody[] = { B00000, B00000, B00000, B11111, B11111, B00000, B00000, B00000 };
byte arrowtail[] = { B00011, B00111, B11111, B11111, B11111, B00111, B00011, B00000 };
void setup() {
lcd.createChar(0, arrowhead);
lcd.createChar(1, arrowbody);
lcd.createChar(2, arrowtail);
lcd.begin(16,2);
}
void loop() {
int n = 15;
while (n != -1) {
lcd.clear();
lcd.setCursor(0,1);
lcd.write(byte(0));
lcd.write(byte(1));
lcd.write(byte(2));
n--;
delay(50);
}
}
화살 머리-몸통-꼬리 세 문자를 연속 출력하며 이동. 잔상 효과로 화살이 길어 보일 수 있으나 정상.
7단계: 간단한 LCD 게임 (화살 피하기)
게임 규칙: 주인공(사람 모양)은 1행(인덱스 1)에 있다. 오른쪽에서 화살이 날아오면 버튼(핀 2, 인터럽트 0)으로 0행(인덱스 0)으로 점프해 피한다. 맞으면 게임 오버. 점수는 루프당 1점 증가, 우상단 표시.
주요 코드 포인트
attachInterrupt(0, buttonin, CHANGE); - 핀 2(인터럽트 0) 상태 변화 시 buttonin 호출. 실행 중 어디에서든 즉시 반응.
randomSeed(analogRead(A0)); - 플로팅 핀 A0의 아날로그 노이즈로 진정한 난수 시드 생성.
- 인터럽트 서비스 루틴
buttonin()에서 버튼 상태 읽고 주인공 행(level) 변경.
- 메인 루프에서 화살 위치(
n) 업데이트, 충돌 검사, 점수 갱신.
전체 게임 코드
// include the library code:
#include <LiquidCrystal.h>
const int rs = 12, en = 11, d4 = 4, d5 = 5, d6 = 6, d7 = 7;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
int level = 1; int score = 0;
byte arrowhead[] = { B00001, B00011, B00111, B01111, B00111, B00011, B00001, B00000 };
byte arrowbody[] = { B00000, B00000, B00000, B11111, B11111, B00000, B00000, B00000 };
byte arrowtail[] = { B00011, B00111, B11111, B11111, B11111, B00111, B00011, B00000 };
byte man[] = { B01110, B01110, B00100, B01110, B10101, B00100, B01010, B10001 };
void setup() {
lcd.createChar(0, arrowhead);
lcd.createChar(1, arrowbody);
lcd.createChar(2, arrowtail);
lcd.createChar(3, man);
lcd.begin(16, 2);
attachInterrupt(0, buttonin, CHANGE);
randomSeed(analogRead(A0));
}
int n;
void loop() {
n = 15;
int rnd = random(15, 25);
while (n != -1) {
lcd.clear(); delay(10);
drawman();
lcd.setCursor(n, 1);
if (n == 1) { if (level == 1) { stopgame(); continue; } }
lcd.write(byte(0)); lcd.write(byte(1)); lcd.write(byte(2));
lcd.setCursor(10, 0); lcd.print(score);
delay(100 - rnd); n--; score++; if (level == 0) score--;
}
}
void drawman() { lcd.setCursor(1, level); lcd.write(byte(3)); }
void buttonin() { if (digitalRead(2) == LOW) level = 0; else level = 1; }
void stopgame() {
lcd.clear(); lcd.setCursor(0,0); lcd.print("Game over");
lcd.setCursor(10,0); lcd.print(score);
level = 1; score = 0; n = 15; delay(3000);
}
회로도
