아두이노 SSD1309 OLED 디스플레이 128x64 | 2.42인치 I2C OLED 튜토리얼

OLED(유기 발광 다이오드) 디스플레이는 자체 발광 픽셀을 통해 진정한 검은색, 높은 명암비, 넓은 시야각을 제공합니다 — 기존 LCD에 비해 훌륭한 업그레이드입니다. SSD1309는 2.42인치(2.4인치라고도 표기됨) 128×64 I2C OLED 모듈에서 흔히 발견되는 드라이버 IC입니다.

아두이노 ssd1309 oLED 디스플레이

이 단계별 가이드에서는 DIYables_OLED_SSD1309 라이브러리를 사용하여 SSD1309 OLED 128×64를 Arduino 보드와 연결하고 프로그래밍하는 방법을 배웁니다. 구체적으로 다음을 다룹니다:

필요한 하드웨어

1×아두이노 우노 R3 쿠팡 | 아마존
1×USB 2.0 타입 A-to-B 케이블 (USB-A PC용) 쿠팡 | 아마존
1×USB 2.0 타입 C-to-B 케이블 (USB-C PC용) 아마존
1×SSD1309 I2C OLED Display 128x64 (2.42 inch) 아마존
1×점퍼케이블 쿠팡 | 아마존
1×(추천) 아두이노 우노용 스크루 터미널 블록 쉴드 쿠팡 | 아마존
1×(추천) Sensors/Servo Expansion Shield for Arduino Uno 쿠팡 | 아마존
1×(추천) 아두이노 우노용 브레드보드 쉴드 쿠팡 | 아마존
1×(추천) 아두이노 우노용 케이스 쿠팡 | 아마존
1×(추천) 아두이노 우노용 프로토타이핑 베이스 플레이트 & 브레드보드 키트 아마존
공개: 이 포스팅 에 제공된 일부 링크는 아마존 제휴 링크입니다. 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

SSD1309 2.42인치 OLED 디스플레이에 대하여

SSD1309는 128×64 도트 매트릭스 패널용으로 설계된 단일 칩 CMOS OLED 드라이버 IC입니다. 널리 사용되는 SSD1306과 레지스터 호환이 가능하므로, 기존 코드 예제를 최소한의 변경으로 활용할 수 있습니다. 주요 하드웨어 차이점:

  • 내장 차지 펌프 없음 — SSD1309는 외부 VCC 레일이 필요하지만, 사실상 모든 브레이크아웃 보드(2.42인치 및 2.4인치 모듈 포함)에는 온보드 부스트 컨버터가 탑재되어 있어 사용자에게는 투명하게 처리됩니다.
  • 더 높은 전압 허용 범위 — SSD1309는 최대 16V VCC를 허용하는 반면 SSD1306은 약 4.2V로 제한됩니다.

2.42인치(2.4인치) OLED 모듈은 일반적으로 SSD1309 드라이버를 사용하며 I2C 인터페이스로 128×64 픽셀 해상도를 제공합니다. 패널 색상(흰색, 파란색, 노란색, 녹색 또는 이중 영역)은 물리적 OLED 재료에 의해 결정되며 소프트웨어로 제어할 수 없습니다.

이 튜토리얼은 I2C 버스를 통해 디스플레이와 통신하며, 두 개의 신호 와이어(SDA와 SCL)만 필요하고 다른 I2C 주변 장치와 버스를 공유할 수 있습니다.

SSD1309 OLED 핀아웃(I2C 모듈)

일반적인 2.42인치 SSD1309 I2C OLED 모듈에는 네 개의 핀이 있습니다:

  • GND — Arduino 접지 레일에 연결합니다.
  • VCC — 전원 입력. Arduino 5V(또는 모듈의 온보드 레귤레이터에 따라 3.3V)에 연결합니다.
  • SCL — I2C 클록 라인.
  • SDA — I2C 데이터 라인.
ssd1309 oLED 핀아웃

※ 주의:

  • 핀 순서는 제조업체마다 다릅니다. 배선 전에 항상 특정 모듈에 실크스크린으로 인쇄된 라벨을 확인하세요.
  • 이 튜토리얼은 DIYables의 2.42인치 SSD1309 OLED 디스플레이로 검증되었습니다. 다른 SSD1309 기반 2.4/2.42인치 모듈도 동일하게 작동해야 합니다.

배선도 — Arduino & SSD1309 OLED 128×64

  • Arduino와 2.42인치 SSD1309 OLED 128×64 사이의 회로도 배선
아두이노 ssd1309 oLED 128x64 배선도

이 이미지는 Fritzing을 사용하여 만들어졌습니다. 이미지를 확대하려면 클릭하세요.

  • Arduino와 SSD1309 OLED 128×64 사이의 실제 배선 사진
아두이노 ssd1309 oLED 128x64 실제 배선

이 이미지는 Fritzing을 사용하여 만들어졌습니다. 이미지를 확대하려면 클릭하세요.

다른 Arduino 변형을 사용할 때는 I2C 핀이 변경됩니다. 아래 표에는 올바른 연결이 나와 있습니다:

OLED 모듈 Arduino Uno, Nano Arduino Mega
Vin 5V 5V
GND GND GND
SDA A4 20
SCL A5 21

시작하기 — Arduino에서 SSD1309 OLED 사용

1단계: DIYables_OLED_SSD1309 라이브러리 설치

  • Arduino IDE를 열고 왼쪽 사이드바에서 Libraries 아이콘을 클릭합니다.
  • 검색 상자에 "DIYables_OLED_SSD1309"를 입력하고 DIYables에서 게시한 라이브러리를 찾습니다.
  • Install 버튼을 클릭합니다.
아두이노 ssd1309 oLED 라이브러리
  • IDE에서 필수 의존성(Adafruit GFX Library)을 설치하라는 메시지가 표시됩니다. 계속하려면 Install All을 클릭합니다.
아두이노 adafruit gfx 라이브러리

2단계: 기본 프로그래밍 패턴

SSD1309를 구동하는 모든 스케치는 동일한 패턴을 따릅니다: 헤더 포함, 디스플레이 객체 생성, setup()에서 초기화, 오프스크린 버퍼에 그리기, display()로 버퍼를 화면에 전송.

  • 필요한 헤더 포함:
#include <Wire.h> #include <Adafruit_GFX.h> #include <DIYables_OLED_SSD1309.h>
  • 화면 크기 설정(2.42인치 모듈의 경우 128×64):
#define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels
  • 디스플레이 객체 생성(리셋 핀이 연결되지 않은 경우 -1 전달):
// Create an SSD1309 display instance on the default I2C bus DIYables_OLED_SSD1309 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
  • setup() 내에서 디스플레이 초기화:
// Initialise the SSD1309 — use address 0x3C (default for most modules) if (!oled.begin(SSD1309_SWITCHCAPVCC, 0x3C)) { Serial.println(F("SSD1309 allocation failed")); while (true); }
  • 이후에는 드로잉 함수(clearDisplay(), drawPixel(), print() 등)를 호출하고 oled.display()로 화면을 업데이트합니다.

※ 주의:

이 튜토리얼의 모든 예제 코드는 SSD1309 OLED 128×64(2.42인치)를 대상으로 하며 DIYables_OLED_SSD1309 라이브러리를 사용합니다.

Arduino 코드 — SSD1309 OLED에 Hello World 표시

가장 간단한 시작점: 다양한 크기로 텍스트 몇 줄을 출력합니다.

/* * 이 아두이노 코드는 newbiely.kr 에서 개발되었습니다 * 이 아두이노 코드는 어떠한 제한 없이 공개 사용을 위해 제공됩니다. * 상세한 지침 및 연결도에 대해서는 다음을 방문하세요: * https://newbiely.kr/tutorials/arduino/arduino-ssd1309-oled-display */ /* * DIYables OLED SSD1309 – Hello World * Prints text in two sizes on the 2.42 inch 128x64 I2C OLED. */ #include <Wire.h> #include <Adafruit_GFX.h> #include <DIYables_OLED_SSD1309.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C DIYables_OLED_SSD1309 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setup() { Serial.begin(9600); if (!display.begin(SSD1309_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1309 allocation failed")); for (;;); } display.clearDisplay(); display.setTextSize(1); // 6x8 pixels per character display.setTextColor(SSD1309_PIXEL_ON); // turn pixels on display.setCursor(0, 0); display.println(F("Hello, World!")); display.println(); display.setTextSize(2); // 12x16 pixels per character display.println(F("DIYables")); display.setTextSize(1); display.println(); display.println(F("SSD1309 OLED 128x64")); display.display(); // push buffer to screen } void loop() { }

Arduino 코드 — SSD1309 OLED에 텍스트 표시

다음 예제는 더 많은 텍스트 기능을 시연합니다 — 다양한 크기, 숫자 서식, RAM 절약을 위한 F() 매크로.

/* * 이 아두이노 코드는 newbiely.kr 에서 개발되었습니다 * 이 아두이노 코드는 어떠한 제한 없이 공개 사용을 위해 제공됩니다. * 상세한 지침 및 연결도에 대해서는 다음을 방문하세요: * https://newbiely.kr/tutorials/arduino/arduino-ssd1309-oled-display */ /* * DIYables OLED SSD1309 – Display Text Demo * Demonstrates text display features on 2.42" SSD1309 128x64 I2C OLED. */ #include <Wire.h> #include <Adafruit_GFX.h> #include <DIYables_OLED_SSD1309.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C DIYables_OLED_SSD1309 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setup() { Serial.begin(9600); if (!display.begin(SSD1309_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1309 allocation failed")); for (;;); } delay(2000); // wait for initializing display.clearDisplay(); // clear display display.setTextSize(1); // text size = 1 display.setTextColor(SSD1309_PIXEL_ON); // set text color display.setCursor(0, 10); // set position to display display.println(F("Text size = 1")); // display text display.display(); // update display delay(2000); display.setTextSize(2); // text size = 2 display.setCursor(0, 30); // set position to display display.println(F("Size = 2")); // display text display.display(); // update display delay(2000); display.clearDisplay(); // clear display display.setTextSize(1); display.setCursor(0, 0); display.println(F("Display integer:")); display.println(12345); display.println(); display.println(F("Display float:")); display.println(1.2345); display.println(); display.println(F("Display HEX:")); display.println(0xABCD, HEX); display.display(); // update display } void loop() { }

자주 사용하는 디스플레이 함수 레퍼런스

DIYables 라이브러리를 통해 SSD1309 OLED를 작업할 때 가장 자주 사용하는 함수의 빠른 레퍼런스:

  • oled.clearDisplay() — 프레임 버퍼 지우기(모든 픽셀 끄기).
  • oled.display() — 버퍼를 OLED로 전송하여 변경 사항을 표시합니다.
  • oled.drawPixel(x, y, color) — 개별 픽셀 설정 또는 지우기.
  • oled.setTextSize(n) — 폰트를 *n* 배율로 스케일(1 = 6×8, 2 = 12×16, …, 최대 8).
  • oled.setCursor(x, y) — 텍스트 커서를 픽셀 좌표 *(x, y)*로 이동합니다.
  • oled.setTextColor(SSD1309_PIXEL_ON) — 텍스트 전경만(배경은 투명).
  • oled.setTextColor(SSD1309_PIXEL_OFF, SSD1309_PIXEL_ON) — 명시적 배경 색상이 있는 텍스트.
  • oled.println("message") — 문자열을 출력하고 다음 줄로 이동합니다.
  • oled.println(number) — 10진수로 정수 출력.
  • oled.println(number, HEX) — 16진수로 정수 출력.
  • oled.startscrollright(start, stop) — *start*와 *stop* 페이지 사이를 오른쪽으로 하드웨어 스크롤.
  • oled.startscrollleft(start, stop) — 하드웨어 왼쪽 스크롤.
  • oled.startscrolldiagright(start, stop) — 하드웨어 대각선 오른쪽 스크롤.
  • oled.startscrolldiagleft(start, stop) — 하드웨어 대각선 왼쪽 스크롤.
  • oled.stopscroll() — 활성화된 하드웨어 스크롤 중지.
  • oled.setContrast(value) — 디스플레이 밝기 조정(0~255).
  • oled.dim(true/false) — 디스플레이를 최소 밝기로 빠르게 어둡게 하거나 이전 대비로 복원합니다.
  • oled.invertDisplay(true/false) — 하드웨어 수준의 색상 반전(켜진 픽셀 ↔ 꺼진 픽셀).

SSD1309 OLED에서 텍스트를 세로 및 가로 중앙에 배치하는 방법

OLED에서 세로/가로 중앙 정렬하는 방법 참조

Arduino 코드 — SSD1309 OLED에 도형 그리기

DIYables_OLED_SSD1309 라이브러리가 Adafruit_GFX를 확장하기 때문에 완전한 도형 그리기 기본 요소 세트를 사용할 수 있습니다: 픽셀, 선, 사각형, 채워진 사각형, 원, 채워진 원, 삼각형, 채워진 삼각형, 둥근 사각형. 아래 스케치는 애니메이션 데모로 모두 순환합니다.

/* * 이 아두이노 코드는 newbiely.kr 에서 개발되었습니다 * 이 아두이노 코드는 어떠한 제한 없이 공개 사용을 위해 제공됩니다. * 상세한 지침 및 연결도에 대해서는 다음을 방문하세요: * https://newbiely.kr/tutorials/arduino/arduino-ssd1309-oled-display */ /* * DIYables OLED SSD1309 – Draw Shapes * Cycles through every shape primitive on the 2.42" SSD1309 128x64 OLED. */ #include <Wire.h> #include <Adafruit_GFX.h> #include <DIYables_OLED_SSD1309.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C DIYables_OLED_SSD1309 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setup() { Serial.begin(9600); if (!display.begin(SSD1309_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1309 allocation failed")); for (;;); } display.clearDisplay(); } void loop() { // --- Pixels --- display.clearDisplay(); for (int16_t i = 0; i < display.width(); i += 4) { display.drawPixel(i, i * display.height() / display.width(), SSD1309_PIXEL_ON); } display.display(); delay(1500); // --- Lines --- display.clearDisplay(); for (int16_t i = 0; i < display.width(); i += 8) { display.drawLine(0, 0, i, display.height() - 1, SSD1309_PIXEL_ON); } for (int16_t i = 0; i < display.height(); i += 8) { display.drawLine(0, 0, display.width() - 1, i, SSD1309_PIXEL_ON); } display.display(); delay(1500); // --- Rectangles --- display.clearDisplay(); for (int16_t i = 0; i < display.height() / 2; i += 4) { display.drawRect(i, i, display.width() - 2 * i, display.height() - 2 * i, SSD1309_PIXEL_ON); } display.display(); delay(1500); // --- Filled rectangles (inverse) --- display.clearDisplay(); for (int16_t i = 0; i < display.height() / 2; i += 6) { display.fillRect(i, i, display.width() - 2 * i, display.height() - 2 * i, SSD1309_PIXEL_INVERSE); } display.display(); delay(1500); // --- Circles --- display.clearDisplay(); for (int16_t i = max(display.width(), display.height()) / 2; i > 0; i -= 5) { display.drawCircle(display.width() / 2, display.height() / 2, i, SSD1309_PIXEL_ON); } display.display(); delay(1500); // --- Filled circles (inverse) --- display.clearDisplay(); for (int16_t i = max(display.width(), display.height()) / 2; i > 0; i -= 6) { display.fillCircle(display.width() / 2, display.height() / 2, i, SSD1309_PIXEL_INVERSE); } display.display(); delay(1500); // --- Rounded rectangles --- display.clearDisplay(); for (int16_t i = 0; i < display.height() / 2 - 2; i += 4) { display.drawRoundRect(i, i, display.width() - 2 * i, display.height() - 2 * i, display.height() / 4, SSD1309_PIXEL_ON); } display.display(); delay(1500); // --- Filled rounded rectangles (inverse) --- display.clearDisplay(); for (int16_t i = 0; i < display.height() / 2 - 2; i += 4) { display.fillRoundRect(i, i, display.width() - 2 * i, display.height() - 2 * i, display.height() / 4, SSD1309_PIXEL_INVERSE); } display.display(); delay(1500); // --- Triangles --- display.clearDisplay(); for (int16_t i = 0; i < max(display.width(), display.height()) / 2; i += 5) { display.drawTriangle( display.width() / 2, display.height() / 2 - i, display.width() / 2 - i, display.height() / 2 + i, display.width() / 2 + i, display.height() / 2 + i, SSD1309_PIXEL_ON); } display.display(); delay(1500); // --- Filled triangles (inverse) --- display.clearDisplay(); for (int16_t i = max(display.width(), display.height()) / 2; i > 0; i -= 6) { display.fillTriangle( display.width() / 2, display.height() / 2 - i, display.width() / 2 - i, display.height() / 2 + i, display.width() / 2 + i, display.height() / 2 + i, SSD1309_PIXEL_INVERSE); } display.display(); delay(1500); }

Arduino 코드 — SSD1309 OLED 하드웨어 스크롤

SSD1309에는 CPU 부하 없이 디스플레이 콘텐츠를 이동하는 내장 스크롤 엔진이 있습니다. DIYables 라이브러리는 오른쪽, 왼쪽, 대각선 오른쪽, 대각선 왼쪽의 네 가지 스크롤 방향을 제공합니다. 각각 시작 페이지와 끝 페이지 매개변수를 받습니다(페이지는 64픽셀 높이 디스플레이에서 0~7로 번호가 매겨진 8픽셀 높이의 가로 띠입니다).

※ 주의:

스크롤을 시작하기 전에 항상 display()를 호출하여 콘텐츠를 OLED에 전송하세요. 스크롤이 활성화된 동안 새 콘텐츠를 그리지 마세요 — 먼저 stopscroll()을 호출하세요.

/* * 이 아두이노 코드는 newbiely.kr 에서 개발되었습니다 * 이 아두이노 코드는 어떠한 제한 없이 공개 사용을 위해 제공됩니다. * 상세한 지침 및 연결도에 대해서는 다음을 방문하세요: * https://newbiely.kr/tutorials/arduino/arduino-ssd1309-oled-display */ /* * DIYables OLED SSD1309 – Scroll Text * Demonstrates all four hardware scroll directions on the 2.42" OLED. */ #include <Wire.h> #include <Adafruit_GFX.h> #include <DIYables_OLED_SSD1309.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C DIYables_OLED_SSD1309 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setup() { Serial.begin(9600); if (!display.begin(SSD1309_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1309 allocation failed")); for (;;); } display.clearDisplay(); display.setTextSize(2); display.setTextColor(SSD1309_PIXEL_ON); display.setCursor(10, 24); display.println(F("DIYables")); display.display(); delay(2000); } void loop() { // Scroll right across all pages display.startscrollright(0x00, 0x07); delay(3000); display.stopscroll(); delay(500); // Scroll left display.startscrollleft(0x00, 0x07); delay(3000); display.stopscroll(); delay(500); // Diagonal scroll right display.startscrolldiagright(0x00, 0x07); delay(3000); display.stopscroll(); delay(500); // Diagonal scroll left display.startscrolldiagleft(0x00, 0x07); delay(3000); display.stopscroll(); delay(500); }

Arduino 코드 — SSD1309 OLED에 비트맵 이미지 표시

SSD1309 OLED에 비트맵을 렌더링하려면 먼저 이미지를 C 바이트 배열로 변환해야 합니다. 무료 image2cpp 온라인 도구를 사용합니다:

  1. 이미지(PNG, JPG, BMP 등)를 업로드합니다.
  2. 캔버스 크기를 128×64(또는 더 작게)로 설정합니다.
  3. 출력 형식으로 Arduino code를 선택합니다.
  4. 생성된 배열을 스케치에 복사합니다.
이미지를 비트맵 배열로 변환

아래 예제는 16×16 하트 아이콘과 전체 너비 DIYables 로고 사이를 번갈아 표시합니다:

/* * 이 아두이노 코드는 newbiely.kr 에서 개발되었습니다 * 이 아두이노 코드는 어떠한 제한 없이 공개 사용을 위해 제공됩니다. * 상세한 지침 및 연결도에 대해서는 다음을 방문하세요: * https://newbiely.kr/tutorials/arduino/arduino-ssd1309-oled-display */ /* * DIYables OLED SSD1309 – Display Image Demo * Displays bitmap images on the 2.42" SSD1309 128x64 I2C OLED. */ #include <Wire.h> #include <Adafruit_GFX.h> #include <DIYables_OLED_SSD1309.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C DIYables_OLED_SSD1309 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // 16x16 heart bitmap const unsigned char heart16x16[] PROGMEM = { 0x00, 0x00, 0x03, 0xc0, 0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x0f, 0xf0, 0x03, 0xc0, 0x00, 0x00 }; // 128x64 DIYables logo bitmap const unsigned char diyablesLogo[] PROGMEM = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xfc, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x00, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0xff, 0xfc, 0x00, 0x03, 0xff, 0xfc, 0x00, 0x00, 0x7f, 0xf8, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xf0, 0x00, 0x03, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xc0, 0x00, 0x1f, 0xe0, 0x00, 0x0f, 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xf0, 0x00, 0x7f, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xc0, 0x00, 0x1f, 0xff, 0xff, 0xf8, 0x00, 0x3f, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x07, 0x80, 0x00, 0x7f, 0xff, 0xff, 0xfc, 0x00, 0x1f, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x03, 0x80, 0x00, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x1f, 0xc0, 0x01, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0x00, 0x01, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x07, 0xff, 0xf0, 0x7f, 0xff, 0x00, 0x0f, 0xc0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0xf0, 0x7f, 0xff, 0x80, 0x0f, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x1f, 0xff, 0xf0, 0x7f, 0xff, 0x80, 0x07, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xf0, 0x7f, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x7f, 0xff, 0xf0, 0x7f, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xfe, 0x00, 0x00, 0xff, 0xfc, 0x00, 0x00, 0xff, 0xfc, 0x00, 0x03, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xfe, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x01, 0xff, 0xfc, 0x00, 0x01, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xfe, 0x00, 0x00, 0xff, 0xff, 0x00, 0x03, 0xff, 0xfc, 0x00, 0x01, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xfe, 0x00, 0x00, 0xff, 0xff, 0x00, 0x03, 0xff, 0xfc, 0x00, 0x01, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xfe, 0x00, 0x00, 0xff, 0xff, 0x00, 0x03, 0xff, 0xfc, 0x00, 0x01, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x7f, 0xff, 0xf0, 0x7f, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xf0, 0x7f, 0xff, 0x80, 0x07, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x1f, 0xff, 0xf0, 0x7f, 0xff, 0x80, 0x07, 0xc0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x0f, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0x00, 0x01, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x07, 0x80, 0x00, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x1f, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xf0, 0x00, 0x0f, 0xc0, 0x00, 0x3f, 0xff, 0xff, 0xf8, 0x00, 0x3f, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xe0, 0x00, 0x1f, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0xf8, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x3f, 0xe0, 0x00, 0x07, 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xf8, 0x00, 0x03, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xfc, 0x00, 0x03, 0xff, 0xf8, 0x00, 0x00, 0xff, 0xfc, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x00, 0x7f, 0xe0, 0x00, 0x01, 0xff, 0xfe, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x01, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; void setup() { Serial.begin(9600); if (!display.begin(SSD1309_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1309 allocation failed")); for (;;); } delay(2000); display.setCursor(0, 0); } void loop() { // Display 16x16 heart icon display.clearDisplay(); display.drawBitmap((SCREEN_WIDTH - 16) / 2, (SCREEN_HEIGHT - 16) / 2, heart16x16, 16, 16, SSD1309_PIXEL_ON); display.display(); delay(2000); // Display full-screen DIYables logo display.clearDisplay(); display.drawBitmap(0, 0, diyablesLogo, 128, 64, SSD1309_PIXEL_ON); display.display(); delay(2000); // Invert display display.invertDisplay(true); delay(2000); display.invertDisplay(false); delay(500); }

※ 주의:

  • 비트맵 크기는 화면 해상도(2.42인치 모듈의 경우 128×64)를 초과할 수 없습니다.

Arduino 코드 — SSD1309 OLED 대비 및 어둡게 하기

SSD1309는 256단계의 대비(0~255)를 지원합니다. DIYables 라이브러리는 세밀한 제어를 위한 setContrast()와 최소 밝기와 이전에 구성된 수준 사이를 빠르게 전환하기 위한 dim()을 제공합니다.

/* * 이 아두이노 코드는 newbiely.kr 에서 개발되었습니다 * 이 아두이노 코드는 어떠한 제한 없이 공개 사용을 위해 제공됩니다. * 상세한 지침 및 연결도에 대해서는 다음을 방문하세요: * https://newbiely.kr/tutorials/arduino/arduino-ssd1309-oled-display */ /* * DIYables OLED SSD1309 – Contrast & Dim * Sweeps contrast from 0→255→0 then toggles dim mode on the 2.42" OLED. */ #include <Wire.h> #include <Adafruit_GFX.h> #include <DIYables_OLED_SSD1309.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C DIYables_OLED_SSD1309 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setup() { Serial.begin(9600); if (!display.begin(SSD1309_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1309 allocation failed")); for (;;); } // Draw a test pattern so the contrast changes are visible display.clearDisplay(); display.fillRect(0, 0, 64, 32, SSD1309_PIXEL_ON); display.fillRect(64, 32, 64, 32, SSD1309_PIXEL_ON); display.setTextSize(1); display.setTextColor(SSD1309_PIXEL_INVERSE); display.setCursor(16, 28); display.println(F("Contrast Demo")); display.display(); delay(2000); } void loop() { // Ramp up for (int c = 0; c <= 255; c += 5) { display.setContrast((uint8_t)c); delay(30); } delay(1000); // Ramp down for (int c = 255; c >= 0; c -= 5) { display.setContrast((uint8_t)c); delay(30); } delay(1000); // Quick dim toggle display.dim(true); // minimum brightness delay(2000); display.dim(false); // restore delay(2000); }

Arduino 코드 — SSD1309 OLED 사용자 정의 외부 폰트

Adafruit GFX 라이브러리에는 수십 개의 확장 가능한 FreeFont 글꼴(Serif, Sans, Mono — 각각 Regular, Bold, Italic, 네 가지 크기)이 포함되어 있습니다. 해당 헤더를 포함하고 setFont()를 호출하여 SSD1309 디스플레이에서 이들 중 어떤 것이든 활성화할 수 있습니다.

※ 주의:

  • 외부 폰트가 활성화되면 커서 Y 좌표는 왼쪽 상단이 아닌 텍스트 기준선을 참조합니다. 이는 내장 5×7 폰트와 다릅니다.
  • 외부 폰트는 플래시(PROGMEM)에 저장됩니다. 메모리가 제한된 AVR 보드(Arduino Uno = 32KB 플래시)에서는 신중하게 사용하세요.
/* * 이 아두이노 코드는 newbiely.kr 에서 개발되었습니다 * 이 아두이노 코드는 어떠한 제한 없이 공개 사용을 위해 제공됩니다. * 상세한 지침 및 연결도에 대해서는 다음을 방문하세요: * https://newbiely.kr/tutorials/arduino/arduino-ssd1309-oled-display */ /* * DIYables OLED SSD1309 – External Fonts * Cycles through three FreeFont typefaces on the 2.42" SSD1309 OLED. */ #include <Wire.h> #include <Adafruit_GFX.h> #include <DIYables_OLED_SSD1309.h> #include <Fonts/FreeSerif9pt7b.h> #include <Fonts/FreeSansBold12pt7b.h> #include <Fonts/FreeMono9pt7b.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C DIYables_OLED_SSD1309 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setup() { Serial.begin(9600); if (!display.begin(SSD1309_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1309 allocation failed")); for (;;); } display.clearDisplay(); display.display(); } void loop() { // ── Built-in 5×7 font ── display.clearDisplay(); display.setFont(NULL); display.setTextSize(1); display.setTextColor(SSD1309_PIXEL_ON); display.setCursor(0, 0); display.println(F("Built-in 5x7 font")); display.println(); display.setTextSize(2); display.println(F("DIYables")); display.display(); delay(3000); // ── FreeSerif 9pt ── display.clearDisplay(); display.setFont(&FreeSerif9pt7b); display.setTextSize(1); display.setTextColor(SSD1309_PIXEL_ON); display.setCursor(0, 14); // Y = baseline display.println(F("FreeSerif 9pt")); display.setCursor(0, 38); display.println(F("DIYables OLED")); display.setCursor(0, 58); display.println(F("Hello World!")); display.display(); delay(3000); // ── FreeSansBold 12pt ── display.clearDisplay(); display.setFont(&FreeSansBold12pt7b); display.setTextSize(1); display.setTextColor(SSD1309_PIXEL_ON); display.setCursor(0, 20); display.println(F("SansBold")); display.setCursor(0, 52); display.println(F("DIYables")); display.display(); delay(3000); // ── FreeMono 9pt ── display.clearDisplay(); display.setFont(&FreeMono9pt7b); display.setTextSize(1); display.setTextColor(SSD1309_PIXEL_ON); display.setCursor(0, 14); display.println(F("FreeMono 9pt")); display.setCursor(0, 34); display.println(F("0123456789")); display.setCursor(0, 54); display.println(F("!@#$%^&*()")); display.display(); delay(3000); }

동영상

비디오 제작은 시간이 많이 걸리는 작업입니다. 비디오 튜토리얼이 학습에 도움이 되었다면, YouTube 채널 을 구독하여 알려 주시기 바랍니다. 비디오에 대한 높은 수요가 있다면, 비디오를 만들기 위해 노력하겠습니다.

아래 동영상 데모는 아래 코드를 사용합니다. 동영상은 Arduino Uno R4를 보여주지만 Arduino Uno R3에서도 동일하게 작동합니다:

/* * 이 아두이노 코드는 newbiely.kr 에서 개발되었습니다 * 이 아두이노 코드는 어떠한 제한 없이 공개 사용을 위해 제공됩니다. * 상세한 지침 및 연결도에 대해서는 다음을 방문하세요: * https://newbiely.kr/tutorials/arduino/arduino-ssd1309-oled-display */ /* * Robot Eyes v4 - Arduino UNO R4 + OLED SSD1309 2.42" (I2C) + TTP223 * Libraries: DIYables_OLED_SSD1309 and Adafruit GFX * * Design: white elliptical sclera + black pupils with highlights + angled eyelids. * All eye elements are drawn in code for smooth animation. * * Wiring: * OLED VCC -> 5V, GND -> GND, SDA -> A4/SDA, SCL -> A5/SCL * TTP223 VCC -> 5V (or 3.3V), GND -> GND, I/O -> D13 * * Behavior: * - Each tap triggers a quick blink and switches to the next expression: * NORMAL -> HAPPY -> WINK -> ANGRY -> SAD -> CRY -> STAR -> DIZZY * -> LOVE -> SURPRISED -> SUSPICIOUS -> ROLL -> SLEEPY -> (back to NORMAL) * - After RETURN_NORMAL_MS (10s) of inactivity, return to NORMAL. * - After SLEEP_MS (30s) of inactivity, enter SLEEP mode. * - Tap while sleeping to wake up and return to NORMAL. */ #include <Wire.h> #include <Adafruit_GFX.h> #include <DIYables_OLED_SSD1309.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C #define TOUCH_PIN 13 #define ON SSD1309_PIXEL_ON #define OFF SSD1309_PIXEL_OFF // Adjustable parameters const int EYE_RX = 22; // horizontal eye radius const int EYE_RY = 25; // vertical eye radius const int EYE_CY = 32; // eye center on the Y axis const int CX_L = 32; // left eye center const int CX_R = 96; // right eye center const unsigned long RETURN_NORMAL_MS = 10000; const unsigned long SLEEP_MS = 30000; //must be greater than RETURN_NORMAL_MS const unsigned long DEBOUNCE_MS = 60; const unsigned long TRANSITION_MS = 110; //transition blink duration const unsigned long FRAME_MS = 33; // ~30 FPS DIYables_OLED_SSD1309 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); enum Mood { NORMAL, HAPPY, WINK, ANGRY, SAD, CRY, STAR, DIZZY, LOVE, SURPRISED, SUSPICIOUS, ROLL, SLEEPY, SLEEP }; const Mood ORDER[] = { NORMAL, HAPPY, WINK, ANGRY, SAD, CRY, STAR, DIZZY, LOVE, SURPRISED, SUSPICIOUS, ROLL, SLEEPY }; const uint8_t ORDER_N = sizeof(ORDER) / sizeof(ORDER[0]); Mood mood = NORMAL; uint8_t moodIndex = 0; unsigned long moodStart = 0, transUntil = 0; // Look direction (-1..1) float curX = 0, curY = 0, tgtX = 0, tgtY = 0; unsigned long nextLook = 0; // Blinking bool blinking = false; unsigned long blinkStart = 0, nextBlink = 0; const unsigned long BLINK_MS = 170; // Touch sensor bool lastReading = false, stableState = false; unsigned long lastChange = 0, lastActivity = 0, lastFrame = 0; // Eye model struct Style { // expression parameters float lx, ly; // look direction -1..1 float pr; // pupil radius (0 = do not draw) float topIn, topOut; // upper eyelid coverage in pixels (inner / outer) float bot; // lower eyelid coverage in pixels int rx, ry, cy; // eye size and position int shake; // horizontal shake (px) }; struct Eye { // calculated geometry for each eye int cx, cy, rx, ry; float topIn, topOut, bot; int inner; // +1: left eye (inner side is right), -1: right eye }; Style baseStyle() { Style s = { 0, 0, 13, 0, 0, 0, EYE_RX, EYE_RY, EYE_CY, 0 }; return s; } Eye makeEye(int side, const Style& s) { Eye e; e.cx = (side == 0 ? CX_L : CX_R) + s.shake + (int)(s.lx * 3); // slightly offset eye toward look direction e.cy = s.cy + (int)(s.ly * 2); e.rx = s.rx; e.ry = s.ry; e.topIn = s.topIn; e.topOut = s.topOut; e.bot = s.bot; e.inner = (side == 0) ? 1 : -1; return e; } float lidTopAt(const Eye& e, float u) { // u: -1..1 across the eye width float t = (e.inner * u + 1.0) * 0.5; // 0 = outer side, 1 = inner side return e.cy - e.ry + e.topOut + (e.topIn - e.topOut) * t; } // Draw the eyeball column by column, clipped by the eyelids. void drawEyeball(const Eye& e) { for (int dx = -e.rx; dx <= e.rx; dx++) { float u = (float)dx / e.rx; float hy = e.ry * sqrt(1.0 - u * u); float y0 = e.cy - hy, y1 = e.cy + hy; float lt = lidTopAt(e, u); float lb = e.cy + e.ry - e.bot; if (y0 < lt) y0 = lt; if (y1 > lb) y1 = lb; if (y1 >= y0) display.drawFastVLine(e.cx + dx, (int)y0, (int)(y1 - y0) + 1, ON); } } // Draw the pupil and highlights. Highlights are skipped when covered by the eyelids. void drawPupil(const Eye& e, const Style& s) { int pr = (int)(s.pr + 0.5); float mx = e.rx - pr - 2; if (mx < 0) mx = 0; float my = e.ry - pr - 2; if (my < 0) my = 0; int px = e.cx + (int)(s.lx * mx); int py = e.cy + (int)(s.ly * my); display.fillCircle(px, py, pr, OFF); float lb = e.cy + e.ry - e.bot; int hr = max(2, pr / 4); int hx = px - pr / 3, hy = py - pr / 3; if (hy - hr > lidTopAt(e, (float)(hx - e.cx) / e.rx) + 1 && hy + hr < lb - 1) display.fillCircle(hx, hy, hr, ON); int h2x = px + pr / 3, h2y = py + pr / 3; if (pr >= 8 && h2y + 1 < lb - 1) display.fillCircle(h2x, h2y, 1, ON); } void drawEyeSide(int side, const Style& s) { Eye e = makeEye(side, s); drawEyeball(e); if (s.pr > 0) drawPupil(e, s); } void drawPair(const Style& s) { drawEyeSide(0, s); drawEyeSide(1, s); } // Auxiliary shapes drawn inside the eyeball void drawHeart(int cx, int cy, int r, uint16_t col) { display.fillCircle(cx - r, cy - r / 2, r, col); display.fillCircle(cx + r, cy - r / 2, r, col); display.fillTriangle(cx - 2 * r + 1, cy - r / 2 + 2, cx + 2 * r - 1, cy - r / 2 + 2, cx, cy + 2 * r + 2, col); } void drawStar(int cx, int cy, float R, float rot, uint16_t col) { float px[10], py[10]; for (int i = 0; i < 10; i++) { float ang = rot + i * PI / 5.0 - PI / 2.0; float rad = (i % 2 == 0) ? R : R * 0.45; px[i] = cx + cos(ang) * rad; py[i] = cy + sin(ang) * rad; } for (int i = 0; i < 10; i++) { int j = (i + 1) % 10; display.fillTriangle(cx, cy, (int)px[i], (int)py[i], (int)px[j], (int)py[j], col); } } void drawSpiral(int cx, int cy, float phase, int dir, uint16_t col) { float px = cx, py = cy; for (float a = 0.4; a <= 11.5; a += 0.4) { float rad = a * 1.3; float ang = dir * (a + phase); float x = cx + cos(ang) * rad, y = cy + sin(ang) * rad; display.drawLine((int)px, (int)py, (int)x, (int)y, col); display.drawLine((int)px + 1, (int)py, (int)x + 1, (int)y, col); px = x; py = y; } } void drawHappyEye(int cx, int cy) { // smiling arch eye "^" display.fillCircle(cx, cy + 10, 17, ON); display.fillCircle(cx, cy + 18, 17, OFF); } void drawClosedEye(int cx, int cy) { // Closed eye display.fillRoundRect(cx - EYE_RX, cy - 1, 2 * EYE_RX, 3, 1, ON); } // Expressions void drawNormal(unsigned long now) { float cover = 0; if (blinking) { float p = (float)(now - blinkStart) / BLINK_MS; if (p >= 1.0) { blinking = false; nextBlink = now + (random(5) == 0 ? 180 : random(2000, 5500)); // occasional double blink } else { cover = sin(p * PI) * EYE_RY * 0.96; } } else if (now >= nextBlink) { blinking = true; blinkStart = now; } Style s = baseStyle(); s.lx = curX; s.ly = curY; s.pr = 13 + sin(now / 700.0) * 0.7; // subtle pupil breathing animation s.topIn = s.topOut = s.bot = cover; drawPair(s); } void drawHappy(unsigned long now) { int bob = (int)(sin(now / 120.0) * 3); drawHappyEye(CX_L, EYE_CY - 4 + bob); drawHappyEye(CX_R, EYE_CY - 4 + bob); } void drawWink(unsigned long now) { Style s = baseStyle(); s.pr = 13 + sin(now / 700.0) * 0.7; drawEyeSide(0, s); drawHappyEye(CX_R, EYE_CY - 4); } void drawAngry() { Style s = baseStyle(); s.pr = 10; s.ly = 0.15; s.topIn = 26; s.topOut = 1; s.bot = 3; s.shake = random(-1, 2); drawPair(s); } void drawSad() { Style s = baseStyle(); s.ly = 0.6; s.topIn = 0; s.topOut = 18; drawPair(s); } void drawCry(unsigned long now) { Style s = baseStyle(); s.rx = 20; s.ry = 22; s.cy = 26; s.pr = 12; s.ly = 0.6; s.topIn = 0; s.topOut = 16; drawPair(s); int tx[2] = { CX_L - 8, CX_R + 8 }; for (int i = 0; i < 2; i++) { int ty = 44 + (int)((now / 55 + i * 9) % 19); display.fillCircle(tx[i], ty, 2, ON); display.fillTriangle(tx[i] - 2, ty, tx[i] + 2, ty, tx[i], ty - 5, ON); } } void drawStarEyes(unsigned long now) { float R = 15 + sin(now / 150.0) * 1.5; float rot = sin(now / 500.0) * 0.25; Style s = baseStyle(); for (int side = 0; side < 2; side++) { Eye e = makeEye(side, s); drawEyeball(e); drawStar(e.cx, e.cy, R, rot, OFF); } } void drawDizzy(unsigned long now) { float phase = now / 120.0; Style s = baseStyle(); for (int side = 0; side < 2; side++) { Eye e = makeEye(side, s); drawEyeball(e); drawSpiral(e.cx, e.cy, phase, side == 0 ? 1 : -1, OFF); } } void drawLove(unsigned long now) { int r = 7 + (int)(sin(now / 150.0) * 1.5); Style s = baseStyle(); for (int side = 0; side < 2; side++) { Eye e = makeEye(side, s); drawEyeball(e); drawHeart(e.cx, e.cy - 3, r, OFF); } } void drawSurprised() { Style s = baseStyle(); s.rx = 24; s.ry = 28; s.pr = 5; drawPair(s); } void drawSuspicious(unsigned long now) { Style s = baseStyle(); s.pr = 11; s.lx = sin(now / 700.0); s.ly = 0.1; s.topIn = s.topOut = 22; s.bot = 8; drawPair(s); } void drawRoll(unsigned long now) { float t = (float)((now - moodStart) % 1300) / 1300.0 * 2.0 * PI; Style s = baseStyle(); s.lx = cos(t); s.ly = sin(t) * 0.9; drawPair(s); } void drawSleepy(unsigned long now) { float k = (sin(now / 450.0) + 1.0) / 2.0; // drowsy motion Style s = baseStyle(); s.lx = curX * 0.3; s.ly = 0.5; s.topIn = s.topOut = 10 + k * 24; s.bot = 3; drawPair(s); } void drawSleep(unsigned long now) { int cx[2] = { CX_L, CX_R }; for (int i = 0; i < 2; i++) { // Closed eye cong "︶" display.fillCircle(cx[i], EYE_CY - 2, 18, ON); display.fillCircle(cx[i], EYE_CY - 5, 18, OFF); } int phase = (now / 600) % 3; display.setTextColor(ON); display.setTextSize(1 + phase); display.setCursor(104 - phase * 6, 12 - phase * 4); display.print(phase == 0 ? F("z") : F("Z")); } void updateLook(unsigned long now) { if (now >= nextLook) { if (random(4) == 0) { tgtX = 0; tgtY = 0; } else { tgtX = random(-100, 101) / 100.0; tgtY = random(-70, 71) / 100.0; } nextLook = now + random(1200, 3500); } curX += (tgtX - curX) * 0.3; curY += (tgtY - curY) * 0.3; } void setMood(Mood m) { unsigned long now = millis(); if (mood == SLEEP && m != SLEEP) display.dim(false); if (m == SLEEP && mood != SLEEP) display.dim(true); if (m == NORMAL) { blinking = false; nextBlink = now + random(1500, 4000); } mood = m; moodStart = now; } bool touchPressed(unsigned long now) { bool reading = (digitalRead(TOUCH_PIN) == HIGH); if (reading != lastReading) { lastChange = now; lastReading = reading; } if ((now - lastChange) >= DEBOUNCE_MS && reading != stableState) { stableState = reading; if (stableState) return true; } return false; } void onTap(unsigned long now) { lastActivity = now; transUntil = now + TRANSITION_MS; // quick blink during expression transition if (mood == SLEEP) { moodIndex = 0; setMood(NORMAL); return; } moodIndex = (moodIndex + 1) % ORDER_N; setMood(ORDER[moodIndex]); } void setup() { Serial.begin(9600); pinMode(TOUCH_PIN, INPUT); randomSeed(analogRead(A0)); if (!display.begin(SSD1309_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1309 allocation failed")); for (;;); } Wire.setClock(400000); display.clearDisplay(); display.display(); unsigned long now = millis(); lastActivity = now; nextBlink = now + 2500; nextLook = now + 1500; } void loop() { unsigned long now = millis(); if (touchPressed(now)) onTap(now); if (stableState) lastActivity = now; unsigned long idle = now - lastActivity; if (mood != SLEEP) { if (idle >= SLEEP_MS) { moodIndex = 0; setMood(SLEEP); } else if (idle >= RETURN_NORMAL_MS && mood != NORMAL) { moodIndex = 0; setMood(NORMAL); } } if (now - lastFrame < FRAME_MS) return; lastFrame = now; if (mood == NORMAL || mood == SLEEPY) updateLook(now); display.clearDisplay(); if (now < transUntil) { drawClosedEye(CX_L, EYE_CY); drawClosedEye(CX_R, EYE_CY); } else { switch (mood) { case NORMAL: drawNormal(now); break; case HAPPY: drawHappy(now); break; case WINK: drawWink(now); break; case ANGRY: drawAngry(); break; case SAD: drawSad(); break; case CRY: drawCry(now); break; case STAR: drawStarEyes(now); break; case DIZZY: drawDizzy(now); break; case LOVE: drawLove(now); break; case SURPRISED: drawSurprised(); break; case SUSPICIOUS: drawSuspicious(now); break; case ROLL: drawRoll(now); break; case SLEEPY: drawSleepy(now); break; case SLEEP: drawSleep(now); break; } } display.display(); }

SSD1309 OLED 문제 해결

스케치를 업로드한 후 2.42인치 SSD1309 OLED에 아무것도 표시되지 않는 경우 다음 확인 사항을 검토하세요:

  • 배선 확인 — SDA, SCL, VCC, GND가 올바른 Arduino 핀에 연결되어 있는지 확인합니다.
  • 드라이버 칩 확인 — 이 라이브러리는 SSD1309용으로 설계되었습니다. 모듈이 다른 컨트롤러(예: SH1106)를 사용하는 경우 올바르게 응답하지 않습니다.
  • I2C 주소 확인 — 대부분의 SSD1309 모듈의 기본 주소는 0x3C이지만 일부는 0x3D를 사용합니다. 아래 I2C 스캐너 스케치를 실행하여 실제 주소를 감지합니다:
// I2C address scanner — prints every detected device to the Serial Monitor #include <Wire.h> void setup() { Wire.begin(); Serial.begin(9600); Serial.println("I2C Scanner"); } void loop() { byte error, address; int nDevices = 0; Serial.println("Scanning..."); for (address = 1; address < 127; address++) { Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0) { Serial.print("I2C device found at address 0x"); if (address < 16) Serial.print("0"); Serial.print(address, HEX); Serial.println(" !"); nDevices++; } else if (error == 4) { Serial.print("Unknown error at address 0x"); if (address < 16) Serial.print("0"); Serial.println(address, HEX); } } if (nDevices == 0) Serial.println("No I2C devices found"); else Serial.println("done"); delay(5000); }

SSD1309가 감지될 때 예상되는 시리얼 모니터 출력:

∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
Arduino Uno
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'Arduino Uno' on 'COM15')
New Line
9600 baud
Scanning... I2C device found at address 0x3C ! done
Ln 11, Col 1
Arduino Uno on COM15
2
  • display()가 호출되었는지 확인 — SSD1309는 프레임 버퍼를 사용합니다. 드로잉 함수는 RAM의 버퍼만 수정합니다. oled.display()를 호출해야 화면에 표시됩니다.
  • 전원 공급 확인 — 2.42인치 모듈은 더 작은 OLED보다 더 많은 전류를 소비합니다. 전원 공급이 충분한 전류(전체 밝기에서 일반적으로 20~40mA)를 공급할 수 있는지 확인합니다.