ชอบ Pixel Art มาก และพอมาใช้กับจอ OLED แล้วยิ่งเจ๋งเข้าไปใหญ่!
อาจจะดูยากตอนเริ่มวาดบนจอแบบนี้ แต่ฝึกไปซักพักน้องก็ทำโลโก้เองได้ แถมทำอนิเมชั่นได้ด้วยนะ!
เลือกภาพที่จะใช้
เราจะใช้ จอขนาด 128x32 (จอ 128x64 ก็มีเยอะนะ ถ้าอยากได้พื้นที่เยอะๆ) เพราะฉะนั้นเลือกภาพที่ เล็กๆหน่อย มาลองดีกว่า
สไปรท์จากเกมบอยนี่แหละใช้ง่ายสุด แต่จำไว้ว่าเราจำกัดแค่ ขาว-ดำ ในขณะที่เกมบอยแสดง สีเทาได้ 4 ระดับ

แปลงภาพเป็น Pixel Art
ตัวอย่างแรก เราจะใช้โลโก้ของ hackster.io

สิ่งที่ต้องทำคือ:
- ปรับขนาดภาพเป็น 128x32
- วาดภาพใหม่ให้ใช้แค่สองสี
- แปลงเป็นไฟล์ XBM (โค้ด Arduino)
จะใช้โปรแกรมอะไรวาดก็ได้ แต่พี่แนะนำ Krita
- ปรับขนาดภาพให้ใกล้เคียงกับหน้าจอ (128x32) : Image --> Scale to New Size
- ปรับขนาดแคนวาส : Image --> Resize Canvas

ใน brush presets เลือก Pixel Art

แล้วก็ลงมือวาดภาพใหม่เลย:
สีขาวจะหมายถึงไม่มีสี (ดำ) และ สีดำจะหมายถึงมีสี (น้ำเงิน/ขาว ขึ้นอยู่กับจอ)
กด X เพื่อสลับสีใน Krita
มันถึงได้ชื่อว่า Pixel Art ไงล่ะ น้องอาจต้องลองผิดลองถูกซักพักกว่าจะได้ผลลัพธ์ที่พอใจ!
แปลง Pixel Art เป็นโค้ด Arduino
- บันทึกภาพเป็น XBM
- บันทึกเป็น PNG ด้วย เพราะไฟล์ XBM แก้ไขไม่ได้!
เปิดไฟล์ xbm ด้วยโปรแกรมแก้ไขข้อความ แล้วเปลี่ยนชื่อตัวแปร
ไฟล์ต้นฉบับ
#define _width 128
#define _height 32
static char _bits[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00... };
ไฟล์ที่พร้อมใช้
#define logo_width 128
#define logo_height 32
static const unsigned char logo[] U8X8_PROGMEM = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00... };
แสดงโลโก้บนจอ
แนะนำให้ เก็บโลโก้ไว้ในไฟล์ .h อีกไฟล์ จะได้อ่านโค้ดหลักง่ายๆ
เราจะใช้ ไลบรารี่ U8g2 (รองรับจอได้เพียบ)
#include <Wire.h> //I2C
#include <U8g2lib.h>
#include "logo.h"
//I2C SSD1306 128x32 (search U8g2 examples for other display)
U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
void setup() {
u8g2.begin(); //Start Screen
drawLogo();
}
void loop() {
}
void drawLogo() {
u8g2.firstPage();
do {
u8g2.drawXBMP(0, 0, logo_width, logo_height, logo);
} while ( u8g2.nextPage() );
}
พี่เก็บไฟล์ png กับ xbm ไว้ในโฟลเดอร์ res/ ในโปรเจคด้วย จะได้จำได้ว่าไฟล์ logo.h นี่วาดรูปอะไรไว้

อัพโหลดโค้ด แล้วก็จัดไปวัยรุ่น!

ข้อมูลเทคนิคแบบจัดเต็ม: การแปลงภาพมาตรฐานให้ Arduino ใช้ได้
จอ OLED แบบ I2C (อย่าง SSD1306) น่ะ มีความละเอียด 128x64 พิกเซล เท่ากับมี LED เล็กๆ ถึง 8,192 ดวงที่เราต้องสั่งมันให้ทำงานเป๊ะๆ Arduino น่ะมันทางคณิตศาสตร์แล้วส่งไฟล์ภาพตรงๆ ไม่ได้หรอก มันส่งได้แค่ "อาร์เรย์เลขฐานสิบหก (hex array)" เท่านั้น
กระบวนการก็คือการแปลงงานกราฟิกของเราให้กลายเป็นภาษาที่ไมโครคอนโทรลเลอร์เข้าใจได้:
- วาดโลโก้หรือรูปของเราบนพื้นหลังขาวดำสนิท ขนาดเท่ากับจอเป๊ะๆ (เช่น 128x32 หรือ 128x64)
- บันทึกภาพเป็นไฟล์
.BMPหรือ.XBM - ใช้เครื่องมือแปลงภาพ (เช่น LCD Assistant, Image2CPP ออนไลน์ หรือการ Export เป็น XBM ใน Krita ตามที่เห็นด้านบน) เพื่อให้มันวิเคราะห์รูปแล้วสร้างอาร์เรย์ยักษ์ของข้อมูลไบต์เลขฐานสิบหกออกมา
- อาร์เรย์นี้จะถูกเก็บไว้ในหน่วยความจำ Flash ของ Arduino โดยใช้
PROGMEM(หรือU8X8_PROGMEMถ้าใช้ U8g2) เพื่อประหยัด RAM อันมีค่าของเรา
การส่งข้อมูลไปยังบัฟเฟอร์ของจอ
เราจะโหลดอาร์เรย์นี้เข้าไปเก็บใน Flash ของ Arduino
- ไลบรารีกราฟิก (เช่น
<U8g2lib.h>หรือ<Adafruit_SSD1306.h>) จะเริ่มต้นการทำงานของจอบนขาI2C (SDA/SCL) - ฟังก์ชันอย่าง
u8g2.firstPage()จะเริ่มลูปการวาด หรือdisplay.clearDisplay();จะลบกราฟิกเก่าทิ้ง - คำสั่งอย่าง
u8g2.drawXBMP(x, y, width, height, bitmap);จะบอกไลบรารีให้วาดข้อมูลภาพที่เราเก็บไว้ที่ตำแหน่งไหน - Arduino จะดึงข้อมูลจาก Flash memory ส่งผ่านสาย I2C และจอก็จะแสดงผลงานศิลป์ของเราออกมาได้อย่างคมชัด แม่นยำทางคณิตศาสตร์เป๊ะเวอร์!
u8g2.nextPage()หรือdisplay.display();จะเป็นขั้นตอนสุดท้าย บังคับให้จออัพเดทบัฟเฟอร์ภายในและแสดงภาพใหม่ออกมา
อนิเมชั่น - ตำแหน่ง
เราสามารถทำให้พิกเซลอาร์ตของเราเคลื่อนไหวได้สองวิธีหลักๆ:
- เปลี่ยนตำแหน่งมัน
- ใช้หลายๆ ภาพ
ถ้าเราอยากให้สไปรท์ของเราเดินจากซ้ายไปขวา เราก็แค่เปลี่ยน ค่า x position ไปทางขวา ใช่มั้ย?

ถ้าใช้ Arduino Uno มันก็จะทำงานนะ แต่ถ้าใช้บอร์ดที่แรงกว่านี้ (เช่น Arduino MKR1010) อนิเมชั่นของน้อง จะเร็วเกินไปจนมองไม่เห็นเลย!
เราต้องจำกัดอัตราการรีเฟรชของอนิเมชั่น แน่นอนว่าเราใช้ delay ง่ายๆ ก็ได้ แต่มันจะทำให้เราทำงานอื่นพร้อมกันไม่ได้
ดังนั้นเราจะใช้ไลบรารี AsyncDelay เพื่อจัดการเรื่องนี้อย่างมีประสิทธิภาพ
#include <AsyncDelay.h>
const int FPS = 1000 / 30; //30 FPS
AsyncDelay tick;
void setup(){
tick.start(FPS, AsyncDelay::MILLIS);
}
void loop(){
if(tick.isExpired()){
//ANIMATION HERE
tick.repeat();
}
}
สุดท้ายแล้ว เราอยากให้ภาพของเราออกไปนอกจอทางขวา แล้วค่อยๆ โผล่มาจากทางซ้าย
เราก็แค่รีเซ็ตตำแหน่งเมื่อมันเกินความกว้างของ OLED (128) ไปเท่ากับความกว้างของภาพ (128+36)
และตั้งค่าเริ่มต้นตำแหน่งเป็น -36 เพื่อให้ภาพดูเหมือนโผล่มาจากทางซ้าย
void loop() {
if (tick.isExpired()) {
drawAnimation();
pos_x = pos_x + X;
if (pos_x > OLED_WIDTH + cannon_width) {
pos_x = -cannon_width;
}
tick.repeat();
}
}
และนี่คือผลลัพธ์! ดูคลิปเลย
Animation - ใช้หลายรูปภาพ
อีกวิธีทำอนิเมชั่นคือใช้หลายรูปภาพแทน
แทนที่จะเปลี่ยนตำแหน่ง X เราใช้ switch condition และเพิ่มค่า animation_state แทน
void drawAnimation() {
u8g2.firstPage();
do {
switch (animation_state) {
case 0:
u8g2.drawXBMP(0, 0, logo_width, logo_height, logo_1);
break;
case 1:
u8g2.drawXBMP(0, 0, logo_width, logo_height, logo_2);
break;
case 2:
u8g2.drawXBMP(0, 0, logo_width, logo_height, logo_3);
break;
case 3:
u8g2.drawXBMP(0, 0, logo_width, logo_height, logo_4);
break;
case 4:
u8g2.drawXBMP(0, 0, logo_width, logo_height, logo_5);
break;
case 5:
u8g2.drawXBMP(0, 0, logo_width, logo_height, logo_6);
break;
}
} while ( u8g2.nextPage() );
}
แต่พี่อยากให้อนิเมชั่นมันย้อนกลับได้ด้วย เลยใช้ boolean (animation_direction) มาควบคุมให้มันเดินหน้า-ถอยหลังได้
drawAnimation();
if (animation_direction) {
animation_state--;
} else {
animation_state++;
}
if (animation_state == 5) {
animation_direction = true;
}
if (animation_state == 0) {
animation_direction = false;
}
ผลลัพธ์เป็นยังไง ดูเอาเลย:
ถ้าน้องเอาเทคนิคสองอันนี้มาผสมกันล่ะก็ อนิเมชั่นเทพๆ ก็ทำได้แล้วจ้า:
อุปกรณ์ที่ต้องใช้ (จัดไปวัยรุ่น)
- Arduino Uno/Nano (ใช้ I2C นะ)
- จอ OLED 0.96” หรือ 1.3” แบบ SSD1306 / SH1106 I2C
- คอมสักเครื่องไว้รันโปรแกรมแปลงรูป (เช่น Krita หรือ LCD Assistant)
หวังว่าบทเรียนนี้จะมีประโยชน์ แล้วน้องจะสนุกกับการเล่นกับจอ OLED เหมือนพี่นะ สู้งานนะน้อง!
ถ้าน้องวาด pixel art อะไรเจ๋งๆ ออกมา อย่าลืมเอามาอวดกันบ้างล่ะ