ESP32와 Arduino 코어를 이용한 OLED 애니메이션 구현



자명종, 책색종이, 조각색종이 조각 - 예시는 lordicon.com 에서 가져왔습니다.
중요 사항
아두이노 코어와 아다프루트 라이브러리의 작동 방식 때문에 모든 애니메이션이 원본보다 약간 느리게 재생됩니다. 이 문제를 해결하려면 ezgif.com 과 같은 온라인 유틸리티를 사용하여 GIF 속도를 높이면 됩니다 .
GIF를 더 빠르게 또는 원래 속도로 실행하려면 ESP-IDF를 사용한 OLED 애니메이션 관련 제 다른 프로젝트를 참조하세요 .
아두이노 IDE에서 열려 있는 파일은 IDE 외부에서 편집하지 마십시오. 이는 여러 문제를 야기할 수 있습니다. 파이썬 스크립트를 실행할 때는 항상 아두이노 IDE를 닫아야 합니다. 외부에서 파일을 편집하여 발생하는 문제는 일반적으로 아두이노 IDE를 다시 시작하면 해결됩니다.
소개 및 요구 사항
이 프로젝트는 SSD1306/SSD1315 등의 OLED 화면에서 애니메이션을 만드는 방법을 자세히 설명합니다. 이 프로젝트는 Adafruit의 GFX 라이브러리를 사용하여 디스플레이 관련 저수준 프로세스를 최대한 추상화합니다.
SSD1306/SSD1315의 저수준 기능에 대한 자세한 내용은 ESP-IDF를 사용한 OLED 애니메이션 관련 제 다른 프로젝트를 참조하세요 .
본 프로젝트의 요구사항은 다음과 같습니다.
- ESP32
기본형 아두이노(예: Uno 또는 Nano)는 애니메이션 프레임에 많은 저장 공간이 필요하기 때문에 적합하지 않습니다. 예를 들어, 128x64 흑백 화면(예: SSD1306/SSD1315)에는 8192개의 픽셀이 있으며, 각 픽셀은 흰색 또는 검은색일 수 있으므로 1비트(0 또는 1)로 생각할 수 있습니다. 8192비트는 1024바이트, 즉 애니메이션 프레임당 1킬로바이트의 공간을 차지합니다. 따라서 30프레임으로 구성된 애니메이션은 저장 공간만 해도 30킬로바이트가 필요합니다. Atmega328 기반 보드인 Uno rev3는 32킬로바이트의 플래시 메모리를 가지고 있어 프로그램과 프레임을 모두 저장할 수 있습니다. Nano 33 BLE는 1MB의 플래시 메모리를 가지고 있으므로 괜찮을 것입니다.
- Adafruit 라이브러리가 제공되는 OLED 스크린입니다. 일반적으로 사용되는 OLED 스크린은 SSD1306입니다.
참고: 이 프로젝트는 흑백 OLED 디스플레이를 사용하여 개발되었지만, 약간의 수정을 통해 컬러 OLED 디스플레이도 지원할 수 있습니다.
- Arduino IDE 또는 이와 동등한 환경(Platform IO 등)에 다음 ESP32 보드와 라이브러리가 설치되어 있어야 합니다.
아다프루트 GFX
디스플레이 전용 Adafruit 라이브러리
- I2C 프로토콜에 대한 기본적인 이해가 필요합니다. 필요하시면 복습 자료를 참고하세요 .
화면의 I2C 주소를 알아야 합니다. 모르는 경우 WireScan 예제 스케치를 사용하여 찾으세요. 예제 스케치는 WireScan파일 메뉴의 예제(Examples) -> 와이어(Wire)에서 찾을 수 있습니다 Examples for the ESP32 Dev Module. ESP32의 기본 I2C 핀은 다음과 같습니다.
설명 핀
SDA 21
SCL 22
경고: I2C 통신에는 반드시 3.3V를 사용하십시오.
Python Imaging Library가 설치된 표준 Python 설치
터미널에서 다음 명령어를 실행하면 됩니다.pip3 install pillow
프로젝트 목표 및 건축 설계
우리는 다음과 같을 것입니다:
- .gif 파일 확보.
- 파이썬을 사용하여 프레임을 분할하고 2차원 배열로 저장하는 방법.
- 파일을 메인 스케치에 연결합니다.
- Adafruit 라이브러리 함수를 사용하여 이러한 프레임을 화면에 표시합니다.
이 프로젝트 사용 방법
1. 이 저장소를 다운로드하고 새 .gif 파일을 같은 디렉토리에 넣어주세요.
아두이노 IDE가 닫혀 있는지 확인하세요. IDE 외부에서 파일을 편집하면 문제가 발생할 수 있습니다.
2. img2frames.py 스크립트에서 대상 .gif 파일을 변경하고, 기존 .c 또는 .h 파일을 삭제해야 합니다. 그렇지 않으면 Arduino에서 문제가 발생합니다.
3. img2frames.py 스크립트를 실행하여 새 헤더 파일을 생성하고 모든 프레임 내용을 2차원 배열로 저장하십시오.
방금 생성한 파일 외에 .c 또는 .h 파일이 없는지 확인하십시오.
방금 생성한 것 외에 .c 또는 .h 파일이 없도록 하십시오.
4. 아두이노 IDE를 열고 네 번째 #include지시문을 방금 생성한 .h 파일로 변경하세요.
5. 파이썬 스크립트를 실행하면 GIF의 프레임 수가 출력됩니다. 이 숫자를 for아두이노 스케치의 반복문 조건문에 넣으세요. 만약 이 숫자를 찾을 수 없다면, .h 파일에서 1024 옆에 있는 값을 확인해 보세요.
6. 그다음 스케치를 업로드하고 필요한 경우 마이크로컨트롤러를 재설정합니다.
필수 이론
Adafruit GFX 함수는 drawBitmap바이트 배열을 입력으로 받으며, 각 비트가 설정된 경우 픽셀이 켜져 있음을 나타내고, 각 비트가 해제된 경우 픽셀이 꺼져 있음을 나타냅니다.
자세한 내용은 여기에서 확인하세요: AdafruitGFX 비트맵
각 바이트(또는 8개의 픽셀 그룹)는 화면의 첫 번째 픽셀 행부터 시작하여 각 행이 모두 채워질 때까지 가로 방향으로 순서대로 배치됩니다.
애니메이션 조달 및 준비
이 튜토리얼에서는 다양한 애니메이션 아이콘을 제공하는 Lordicon.com에서 애니메이션을 가져오겠습니다.
1. lordicon.com으로 이동하여 '라이브러리 탐색'을 클릭하세요. 카테고리를 선택하세요. 단색 OLED 디스플레이의 경우, 가장 깔끔해 보이는 Wired Outline, System Outline 또는 System Solid 컬렉션을 추천합니다.
2. 무료 아이콘을 선택하여 유료 아이콘을 필터링한 후, 무료 아이콘 중 원하는 것을 선택하세요. 일부 애니메이션은 여러 색상과 동작 유형을 가지고 있습니다. 마음에 드는 것을 선택하세요.
제 디스플레이는 흑백이기 때문에, 이미지 프레임을 C 스타일의 2차원 배열로 변환하는 파이썬 스크립트를 작성하여 흰색을 제외한 모든 색상을 디스플레이의 기본 색상(흰색)으로 대체했습니다. 다시 말해, 이미지에서 흰색이 아닌 모든 색상은 제 화면에서 흰색으로 표시되고, 이미지에서 흰색은 배경으로 간주되어 검은색으로 표시됩니다.
3. 안에 있는 초록색 버튼을 클릭하여 GIF설정 상자를 여세요. 애니메이션이 정사각형이고 제 화면 크기가 128x64이므로 64픽셀로 크기를 조정하겠습니다. 화면 크기에 맞게 크기를 조정하세요.
컬러 화면을 사용하시는 경우, 아이콘 미리보기 아래에 있는 오른쪽 아이콘을 클릭하여 애니메이션의 색상을 조정할 수 있습니다. 가장 중요한 것은 최종 애니메이션이 화면에 잘 맞는지 확인하는 것입니다.
4. .gif 파일을 다운로드하여 PC에 저장하세요.
프레임을 C 스타일 2D 배열로 분할 및 저장
이 섹션에서는 파이썬 스크립트를 사용하여 .gif 파일의 프레임을 분할하고 이를 C 스타일의 2차원 배열 2개로 저장합니다.
파이선 스크립트의 첫 부분:
from PIL import Image
buffer = []
WIDTH = 128
HEIGHT = 64
fileName = "./alarm.gif"
outputString = "#include \""
outputString += fileName[2:-3]+"h"
outputString += "\"\n\nconst unsigned char bufferAnimation["
def drawPixel(x, y, colour):
global buffer
byteNum = int((y*(WIDTH/8)) + int(x/8))
# print(byteNum)
bitNum = x % 8
actualByte = buffer[byteNum]
if (colour == 1):
actualByte = actualByte | (1 << (7-bitNum))
else:
actualByte = actualByte & (~(1 << (7-bitNum)))
buffer[byteNum] = actualByte
imageObject = Image.open(fileName)
print(imageObject.is_animated)
print(imageObject.n_frames)
outputString += str(imageObject.n_frames)
outputString += "][1024]={\n"
이 부분에서는 프레임 버퍼를 초기화하고, 화면의 너비와 높이를 저장할 변수를 선언하고, 대상 .gif 파일을 선언하고, 모든 프레임을 저장할 .c 파일의 시작 부분을 작성합니다.
또한 0부터 시작하는 인덱스 좌표와 색상(1 또는 0)을 받아 해당 비트를 설정하거나 지우는 drawPixel 함수를 생성합니다. 이 함수는 1024바이트로 구성된 버퍼 리스트에 프레임을 생성하는 데 사용됩니다. 먼저 해당 바이트 내의 바이트 번호와 비트 번호를 찾습니다. 그런 다음 버퍼 리스트에서 해당 바이트를 읽고 특정 비트를 지우거나 설정합니다. 수정된 바이트는 다시 버퍼에 저장됩니다. 이 함수는 나중에 사용됩니다.
그 후, .gif 파일을 열고 애니메이션인지 확인한 다음 프레임 수를 출력합니다. 이 값을 출력 프레임 파일에 추가하고 2차원 배열 생성을 시작합니다.
스크립트의 다음 부분은 다음과 같습니다.
for frameNum in range(0, imageObject.n_frames):
outputString += "{"
buffer = []
for i in range(0, 1024):
buffer.append(0)
imageObject.seek(frameNum)
im = imageObject.convert('RGBA')
# im.show()
px = im.load()
for i in range(0, 64):
for j in range(0, 64):
#print("Orig ", str(i), " ", str(j))
if (px[i, j][0] < 250 and px[i, j][1] < 250 and px[i, j][2] < 250):
# ADJUST PARAMETERS BELOW FOR X AND Y OFFSET
drawPixel(i + 32, j + 0, 1)
for i in range(0, 1024):
outputString += str(buffer[i])
if (i != 1023):
outputString += ", "
outputString += "},\n"
outputString = outputString[:-1] # remove comma from last one
outputString += "\n};"
f = open(fileName[:-3]+"c", "w")
f.write(outputString)
f.close()
hFileContent = "extern const unsigned char bufferAnimation ["
hFileContent += str(imageObject.n_frames)
hFileContent+="][1024];"
f=open(fileName[:-3]+"h", "w")
f.write(hFileContent)
f.close()
이 스크립트 부분에서는 .gif의 각 프레임을 순회하기 위해 for 루프를 실행합니다. 먼저 프레임 버퍼를 비운 후, 0으로 채웁니다(빈 화면을 나타냄). 그런 다음 프레임을 가져와 픽셀 접근을 용이하게 하기 위해 RGBA 형식으로 변환하고, 중첩된 두 개의 for 루프를 사용하여 프레임 내 각 픽셀을 순회합니다.
각 픽셀이 RGB(250, 250, 250)보다 어두운지 테스트하고, 해당 조건을 충족하면 drawPixel 함수를 사용하여 버퍼에 배치합니다. .gif 프레임은 64x64이고 화면은 128x64이므로, 프레임을 x축 방향으로 32픽셀 오프셋하여 프레임이 화면 왼쪽이 아닌 중앙에 표시되도록 했습니다.
버퍼 리스트가 채워지면 이를 문자열로 변환하고, 적절한 위치에 괄호를 추가하여 C 스타일 배열인 outputString에 연결합니다. 이 작업이 완료되면 outputString을 .gif 파일과 동일한 이름의 .c 파일에 기록하고, 메인 스케치에서 링크될 동일한 이름의 .h 파일을 준비하기 시작합니다.
메인 스케치에 파일을 연결하는 방법 (C 및 H 파일 설명 포함)
우리의 .c 파일은 대략 다음과 같을 것입니다.
#include "gifName.h"
const unsigned char bufferAnimation[<number of frames>][1024]={
{0, 0, 0 ... rest of the frame ... 0, 0, 0},
{0, 0, 0 ... rest of the frame ... 0, 0, 0},
{0, 0, 0 ... rest of the frame ... 0, 0, 0},
{0, 0, 0 ... rest of the frame ... 0, 0, 0},
... goes on
};
첫 번째 줄은 이 파일을 위에서 언급한 .h 파일에 연결하여 이 거대한 2차원 배열을 별도의 파일에 저장할 수 있도록 합니다. 이에 대한 설명은 아래에 있습니다. 이전에는 const이 배열이 너무 커서 RAM에 저장할 수 없었기 때문에 플래시 메모리에 저장했습니다.
우리는 C 언어에서 2의 보수 표현 관련unsigned char 문제를 일으키지 않도록 항상 주의를 기울였습니다. 프레임 수만큼 1차원 배열이 존재하는데, 각 1차원 배열은 화면 왼쪽 상단 모서리부터 픽셀을 나타내는 비트(설정 또는 해제됨)들의 연속체를 저장합니다.
우리의 .h 파일은 대략 다음과 같을 것입니다.
extern const unsigned char bufferAnimation[41][1024];
우리는 extern 키워드를 사용하여 2D 배열의 가시성을 확장합니다. 즉, 메인 스케치에서 이 .h 파일을 링크하면 .c 파일에 정의된 bufferAnimation 배열에 메인 스케치에서 접근할 수 있게 됩니다.
const그리고 unsigned char이는 위에서 이미 설명되었습니다.
이 프레임들을 표시합니다 (메인 스케치 설명 포함).
저희의 주요 스케치는 매우 간단하며 다음과 같습니다.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "gifName.h"
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void setup() {
Serial.begin(9600);
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;); // Don't proceed, loop forever
}
}
void loop() {
for (int i = 0; i < <number of frames>; i++) {
display.clearDisplay();
display.drawBitmap(0, 0, bufferAnimation[i], 128, 64, 1);
display.display();
}
}
Adafruit GFX 및 디스플레이 전용 라이브러리를 포함하는 것으로 시작합니다. 또한 Adafruit 라이브러리가 I2C 기능에 접근하기 위해 필요한 Wire 라이브러리도 포함해야 합니다. 상단의 Python 스크립트로 생성된 관련 .h 파일도 반드시 포함해야 합니다. 화면 너비와 높이, I2C 주소, OLED 리셋을 위한 #define 지시문을 생성합니다. 일반적으로 시중에서 구할 수 있는 화면을 사용하는 경우 OLED 리셋은 -1로 설정됩니다.
디스플레이를 제어하기 위한 함수를 호출할 수 있는 'display' 객체를 생성합니다. 이 객체는 디스플레이의 I2C 주소와 디스플레이 전압 생성 방식을 요구합니다. 대부분의 경우 SSD1306_SWITCHCAPVCC를 제공하여 3.3V VCC 라인에서 전압을 생성할 수 있습니다. 디스플레이 객체의 begin 함수를 사용하여 화면을 초기화합니다.
IDE에서 열려 있는 탭은 표시하려는 애니메이션의 탭만 확인하십시오. 이는 컴파일러의 링크 작업과 관련이 있습니다. Arduino IDE는 메인 스케치에 포함된 .h 파일을 해당 .c 파일과 함께 추가하겠지만, 이전 애니메이션의 .c 또는 .h 파일은 제거하지 않을 가능성이 높습니다. 여기서 발생하는 문제는 열려 있는 모든 탭의 파일을 링크하고 컴파일하려고 시도한다는 점입니다. 이전 애니메이션의 .h 또는 .c 파일이 여러 개 존재할 경우, Python 스크립트가 각 2D 배열을 bufferAnimation으로 호출하기 때문에 컴파일 오류가 발생합니다. 즉, bufferAnimation이 여러 파일에서 상충되는 선언을 가지게 됩니다.
이 오류를 방지하려면, 표시하려는 애니메이션과 관련 없는 탭을 반드시 닫아야 합니다. 이를 위해 시리얼 모니터 버튼 아래 드롭다운 메뉴를 열고, 관련 없는 탭을 선택한 후 '삭제'를 클릭하세요.

여러 애니메이션을 저장하고 표시하려는 경우, 각 애니메이션의 2차원 배열 이름을 .c, .h 및 메인 스케치 파일에서 서로 다르게(예: secondBufferAnimation) 편집해야 합니다. Python 스크립트는 항상 bufferAnimation이라는 2차원 배열을 생성하기 때문입니다.
alarm.hand 및 alarm.c 파일로 구성된 하나의 애니메이션의 경우, IDE는 다음과 같이 구성되어야 합니다:

올바른 IDE 탭
그런 다음 애니메이션의 프레임 수를 수동으로 편집해야 합니다. 이 값은 파이썬 스크립트가 실행될 때 콘솔에 출력되며, 애니메이션의 .h 또는 .c 파일에서도 확인할 수 있습니다.
반복문 안에서 우리는 이전에 표시된 프레임에서 설정된 픽셀을 화면 버퍼에서 지우고, 현재 프레임을 화면 버퍼에 그린 다음, 버퍼에 있는 내용을 패널에 표시하도록 디스플레이를 트리거합니다.
이 버퍼는 화면에 물리적으로 존재하며, Python의 buffer나 C의 bufferAnimation과는 다릅니다.
이 코드를 컴파일하고 업로드하면 모든 데이터가 ESP32로 전송되고, 모든 연결이 제대로 되어 있다면 화면에서 애니메이션이 시작됩니다.
경우에 따라 이전 프로그램 실행이 부적절한 시점에 중단되었을 경우, 마이크로컨트롤러를 물리적으로 재설정해야 할 수도 있습니다(ESP32의 리셋/EN 버튼을 누르는 방식).
본 튜토리얼의 코드와 설명은 다음 사이트에 있습니다. 원문 저자에게 감사합니다.
소스코드 압축 파일을 가져왔어요.,
소스 코드: https://github.com/intellar/oled_eye_display
#Git을 사용하는 경우, 다음 명령어를 사용하여 코드를 복제(다운로드)하세요: git clone https://github.com/intellar/oled_eye_display
업데이트: 2025-10: 두 개의 디스플레이와 ESP32를 사용하는 새로운 프로젝트 , dual_display_esp32 2025-03: U8G2 라이브러리로 포팅되었습니다. 저장소 를 확인하세요 . 2024-10: 컬러 LCD 버전이 추가되었습니다. 컬러 LCD 디스플레이에서 아두이노 나노를 이용한 애니메이션 눈
'ESP32 Project' 카테고리의 다른 글
| ESP32-C3 및 LED 매트릭스를 활용한 디지털 모래시계 (0) | 2026.03.01 |
|---|---|
| 영원한 메이커의 고향, 시계 ESPclock 스마트 시계 (1) | 2026.02.24 |
| ESP32-CAM 보드에서 YOLO를 구현 (0) | 2026.02.04 |
| ESP32-CAM 영상 물체 탐지 기능 구현 웨비나 (1) | 2026.01.28 |
| ESP32의 OTA 업데이트는 쉬워 보이지만 (1) | 2026.01.12 |
| ESP32 C3 Super Mini Pomodoro timer ESP-IDF (1) | 2026.01.06 |
| 와이파이를 동작 감지 센서로 바꾸는 방법 (2) | 2025.12.30 |
| ESP32를 사용한 간단한 GPS 추적기 제작 방법 및 지도 상의 데이터 시각화 (0) | 2025.11.17 |
취업, 창업의 막막함, 외주 관리, 제품 부재!
당신의 고민은 무엇입니까? 현실과 동떨어진 교육, 실패만 반복하는 외주 계약,
아이디어는 있지만 구현할 기술이 없는 막막함.
우리는 알고 있습니다. 문제의 원인은 '명확한 학습, 실전 경험과 신뢰할 수 있는 기술력의 부재'에서 시작됩니다.
이제 고민을 멈추고, 캐어랩을 만나세요!
코딩(펌웨어), 전자부품과 디지털 회로설계, PCB 설계 제작, 고객(시장/수출) 발굴과 마케팅 전략으로 당신을 지원합니다.
제품 설계의 고수는 성공이 만든 게 아니라 실패가 만듭니다. 아이디어를 양산 가능한 제품으로!
귀사의 제품을 만드세요. 교육과 개발 실적으로 신뢰할 수 있는 파트너를 확보하세요.
캐어랩