ไลบรารี Adafruit_SSD1306 นั้นทรงพลังมากครับ เพราะมันทำงานร่วมกับ Adafruit_GFX (ซึ่งเป็นตัวจัดการกราฟิกพื้นฐาน) ทำให้เราวาดรูปทรงและข้อความได้หลากหลาย
1. คอนเซ็ป (Core Concept)
ก่อนจะไปดูคำสั่ง ต้องเข้าใจ 2 เรื่องนี้ก่อน:
- พิกัด (Coordinates): มุมซ้ายบนคือ
(0, 0)แกน X แนวนอน (0-127), แกน Y แนวตั้ง (0-63) - ระบบ Buffer: คำสั่งวาดทั้งหมดที่เราเขียน มันจะวาดลงใน หน่วยความจำ (RAM) ของบอร์ดก่อน หน้าจอจะยังไม่เปลี่ยน จนกว่า เราจะสั่ง
display.display()
2. คำสั่งจัดการหน้าจอ (Basic Control)
| คำสั่ง | หน้าที่ |
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); | เริ่มต้นการทำงาน (ใส่ใน setup) |
display.clearDisplay(); | ล้างหน้าจอ (ลบทุกอย่างใน Buffer ให้ดำมืด) |
display.display(); | สั่งแสดงผล (เอาสิ่งที่วาดใน Buffer ขึ้นโชว์บนจอจริง) ห้ามลืม! |
display.invertDisplay(true); | สลับสีหน้าจอ (พื้นขาว ตัวดำ) |
display.dim(true); | หรี่แสงหน้าจอลง (ประหยัดไฟ) |
3. คำสั่งเกี่ยวกับข้อความ (Text)
| คำสั่ง | ตัวอย่าง | หน้าที่ |
setTextSize(n) | display.setTextSize(1); | ขนาดตัวอักษร (1=เล็ก, 2=กลาง, 3=ใหญ่) |
setTextColor(c) | display.setTextColor(WHITE); | สีตัวอักษร (ปกติใช้ WHITE, ถ้าพื้นขาวใช้ BLACK) |
setCursor(x, y) | display.setCursor(0, 10); | วางตำแหน่งเคอร์เซอร์ที่จะเริ่มพิมพ์ |
print("...") | display.print("Temp: "); | พิมพ์ข้อความ (ไม่ขึ้นบรรทัดใหม่) |
println("...") | display.println(25.5); | พิมพ์ข้อความแล้วขึ้นบรรทัดใหม่ |
cp437(true) | display.cp437(true); | เปิดใช้ Font รหัส CP437 (เพื่อให้โชว์สัญลักษณ์พิเศษบางตัวได้) |
ทริค: ถ้าอยากประหยัด RAM ให้ใช้
F()ครอบข้อความ เช่นdisplay.print(F("Hello"));
4. คำสั่งวาดรูปทรง (Graphics Primitives)
ทุกคำสั่งต้องปิดท้ายด้วย สี เสมอ (ปกติคือ WHITE)
4.1 จุดและเส้น
drawPixel(x, y, WHITE);-> จุด 1 จุดdrawLine(x1, y1, x2, y2, WHITE);-> ลากเส้นตรงจากจุด 1 ไปจุด 2
4.2 สี่เหลี่ยม (Rectangle)
drawRect(x, y, w, h, WHITE);-> วาดกรอบสี่เหลี่ยมโปร่งfillRect(x, y, w, h, WHITE);-> วาดสี่เหลี่ยมทึบ (ระบายสีเต็ม)drawRoundRect(x, y, w, h, r, WHITE);-> สี่เหลี่ยมมุมมน (r = รัศมีมุมโค้ง)
4.3 วงกลม (Circle)
drawCircle(x, y, r, WHITE);-> วงกลมโปร่ง (x,y คือจุดศูนย์กลาง)fillCircle(x, y, r, WHITE);-> วงกลมทึบ
4.4 สามเหลี่ยม (Triangle)
drawTriangle(x1,y1, x2,y2, x3,y3, WHITE);-> สามเหลี่ยมโปร่งตามพิกัด 3 มุม
5. คำสั่งเอฟเฟกต์ (Scrolling)
จอ OLED มีฮาร์ดแวร์ช่วยเลื่อนหน้าจอได้เนียนๆ โดยไม่ต้องเขียนโค้ดลบแล้ววาดใหม่
display.startscrollright(startPage, stopPage);-> เลื่อนข้อความไปทางขวาdisplay.startscrollleft(startPage, stopPage);-> เลื่อนไปทางซ้ายdisplay.startscrolldiagright(start, stop);-> เลื่อนเฉียงdisplay.stopscroll();-> หยุดเลื่อน (ต้องสั่งหยุดก่อนจะเขียนข้อความใหม่)
6. การแสดงรูปภาพ (Bitmap)
ถ้าคุณแปลงไฟล์รูปเป็นรหัส Hex (Bitmap Array) แล้ว จะใช้คำสั่งนี้:
drawBitmap(x, y, image_data_array, w, h, WHITE);
ตัวอย่างโค้ดรวม (Template)
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
void setup() {
Serial.begin(115200);
// เริ่มต้นจอ
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
display.clearDisplay(); // 1. ล้างจอ
// วาดกรอบ
display.drawRect(0, 0, 128, 64, WHITE);
// เขียนข้อความ
display.setTextSize(2);
display.setTextColor(WHITE);
display.setCursor(10, 20);
display.println(F("HELLO!"));
display.display(); // 2. สั่งแสดงผล
}
void loop() {
// ใส่โค้ดอนิเมชั่น หรือรับค่า sensor ตรงนี้
}
การวาดโลโก้
การวาดโลโก้ที่มีรูปทรงอิสระ (Curve) อย่าง Nike Swoosh บนจอ OLED เราจะไม่ใช้คำสั่งวาดเส้น (drawLine) เพราะมันยากและไม่สวย แต่เราจะใช้วิธี “แปลงรูปภาพเป็นรหัส (Bitmap)” นี่คือตัวอย่างโค้ดพร้อมข้อมูลภาพโลโก้ Nike ที่แปลงมาแล้ว
วิธีการทำงาน
- เราเก็บข้อมูลภาพเป็น Hex Array (รหัสฐาน 16) ไว้ใน
PROGMEM(เพื่อประหยัด RAM) - ใช้คำสั่ง
drawBitmapเรียกข้อมูลนั้นมาแสดง
โค้ดตัวอย่าง
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// --- ส่วนข้อมูลรูปภาพ (Bitmap Data) ---
// ขนาดภาพ: 64 x 32 pixel
const unsigned char myLogo [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF8, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xE0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x80, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0xFE, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xFF, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x3F, 0xFC, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xC0, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00,
0x00, 0x07, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x1F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
void setup() {
Serial.begin(115200);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
display.clearDisplay();
// --- คำสั่งวาดรูป ---
// รูปแบบ: drawBitmap(x, y, ชื่อตัวแปร, กว้าง, สูง, สี);
// จัดกึ่งกลางจอ (จอ 128x64, รูป 64x32 -> จุดเริ่ม x=32, y=16)
display.drawBitmap(32, 16, myLogo, 64, 32, WHITE);
// เพิ่มข้อความข้างล่างหน่อย
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(35, 55);
display.print("JUST DO IT.");
display.display(); // อย่าลืมคำสั่งนี้!
}
void loop() {
// ไม่ต้องทำอะไร
}
ถ้าอยากเปลี่ยนเป็นรูปอื่นทำอย่างไร?
เราสามารถแปลงรูปอะไรก็ได้ให้เป็นโค้ดแบบข้างบน ด้วยวิธีนี้:
- เตรียมรูป: หารูปขาว-ดำ (ไฟล์ .png หรือ .jpg)
- เข้าเว็บแปลง: ค้นหา Google ว่า “image2cpp” (เว็บยอดฮิตคือ javl.github.io/image2cpp)
- ตั้งค่า:
- Upload รูปเข้าไป
- กำหนดขนาด (เช่น 64×64 หรือ 128×64)
- Background color: เลือกให้เหมาะสม (ปกติ Black)
- Invert colors: ติ๊กถูก ถ้าต้องการให้ส่วนที่เป็นสีดำกลายเป็นแสงสว่างบนจอ
- Generate Code:
- Output format: เลือก Arduino Code
- กด Generate code
- เราจะได้ชุดตัวเลข
0x00, 0xFF...มาแทนที่ชุดnikeLogoในโค้ดได้เลยครับ
การออกแบบเมนูบนจอขนาดเล็ก 128×64 ต้องเน้น “ความชัดเจน” และ “รู้ว่าเลือกอะไรอยู่”
แบบที่ 1: เมนูแบบรายการแนวตั้ง (Vertical List)
ใช้เทคนิค “Invert Color” (ถมดำเปลี่ยนเป็นขาว) ตรงบรรทัดที่ถูกเลือก ทำให้ดูง่ายมาก
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// รายการเมนู
const char* menuItems[] = {"DASHBOARD", "SETTINGS", "WIFI SETUP", "ABOUT"};
int menuLength = 4;
int selectedItem = 0; // ตัวแปรเก็บว่าเลือกเมนูไหนอยู่
void setup() {
Serial.begin(115200);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { for(;;); }
}
void loop() {
display.clearDisplay();
// --- ส่วนหัว (Header) ---
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.println(F(" MAIN MENU "));
display.drawLine(0, 10, 128, 10, WHITE); // ขีดเส้นใต้
// --- ส่วนรายการเมนู ---
int startY = 15; // เริ่มเขียนบรรทัดแรกที่ Y=15
for(int i=0; i<menuLength; i++) {
if(i == selectedItem) {
// ถ้าเป็นตัวที่เลือก: ให้วาดสี่เหลี่ยมทึบเป็นพื้นหลัง
// fillRect(x, y, w, h, color)
display.fillRect(0, startY + (i*12), 128, 11, WHITE);
display.setTextColor(BLACK); // ตัวหนังสือสีดำ
} else {
// ถ้าไม่ใช่ตัวเลือก: พื้นดำ ตัวหนังสือขาว
display.setTextColor(WHITE);
}
display.setCursor(5, startY + 1 + (i*12));
display.println(menuItems[i]);
}
display.display();
// --- จำลองการกดปุ่ม (Simulation) ---
delay(1000); // รอ 1 วินาที
selectedItem++; // เลื่อนลง
if(selectedItem >= menuLength) selectedItem = 0; // วนกลับไปบนสุด
}
แบบที่ 2: เมนูแบบไอคอนแนวนอน (Horizontal Icons)
เหมาะกับเมนูหลักที่มีตัวเลือกน้อยๆ (3-4 อย่าง) ใช้การวาดรูปทรงเรขาคณิตแทนไอคอน (เอา drawBitmap มาใส่แทนได้เลย)
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
int selectedItem = 0;
int menuLength = 3;
void setup() {
Serial.begin(115200);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { for(;;); }
}
void drawIcon(int x, int y, int index, bool selected) {
// วาดกรอบถ้าถูกเลือก
if(selected) {
display.drawRoundRect(x-2, y-2, 36, 36, 4, WHITE); // กรอบมน
}
// วาดรูปแทนไอคอน (ของจริงคุณใช้ drawBitmap ตรงนี้)
if(index == 0) { // Home
display.fillRect(x+8, y+14, 16, 12, WHITE); // บ้าน (สี่เหลี่ยม)
display.fillTriangle(x+16, y, x+6, y+14, x+26, y+14, WHITE); // หลังคา
} else if (index == 1) { // Settings (วงกลม)
display.drawCircle(x+16, y+16, 10, WHITE);
display.drawCircle(x+16, y+16, 4, WHITE);
} else { // Graph (กราฟแท่ง)
display.fillRect(x+6, y+16, 6, 10, WHITE);
display.fillRect(x+14, y+10, 6, 16, WHITE);
display.fillRect(x+22, y+6, 6, 20, WHITE);
}
}
void loop() {
display.clearDisplay();
// ชื่อเมนูที่จะเปลี่ยนตามไอคอน
const char* labels[] = {"HOME", "SYSTEM", "STATS"};
// ตำแหน่ง X ของแต่ละไอคอน (ห่างกันตัวละ 40 pixel)
int positions[] = {10, 50, 90};
// วาดไอคอนทั้งหมด
for(int i=0; i<menuLength; i++) {
bool isSelected = (i == selectedItem);
drawIcon(positions[i], 20, i, isSelected);
}
// แสดงชื่อเมนูด้านล่าง
display.setTextSize(1);
display.setTextColor(WHITE);
// จัดกึ่งกลางข้อความ
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(labels[selectedItem], 0, 0, &x1, &y1, &w, &h);
display.setCursor((128 - w) / 2, 55);
display.println(labels[selectedItem]);
display.display();
// --- จำลองการกดปุ่ม ---
delay(1000);
selectedItem++;
if(selectedItem >= menuLength) selectedItem = 0;
}
วิธีทำให้ใช้งานได้จริง (ต่อปุ่มกด)
ตอนนี้โค้ดมันเลื่อนเอง (Auto Scroll) ถ้าคุณจะต่อปุ่มกด (เช่น 2 ปุ่ม: ปุ่มเลื่อน, ปุ่มเลือก) ให้ทำแบบนี้ครับ:
- ต่อปุ่ม: ขาหนึ่งลง GND, อีกขาเข้า Pin (เช่น D2, D3)
- เขียนโค้ด:
#define BTN_UP 2
#define BTN_SELECT 3
void setup() {
// ... (code จอ) ...
pinMode(BTN_UP, INPUT_PULLUP); // ไม่กดเป็น 1, กดเป็น 0
pinMode(BTN_SELECT, INPUT_PULLUP);
}
void loop() {
// ตรวจจับการกดปุ่ม (Active Low)
if (digitalRead(BTN_UP) == LOW) {
selectedItem++;
if(selectedItem >= menuLength) selectedItem = 0;
delay(200); // กันปุ่มเบิ้ล (Debounce แบบง่าย)
}
if (digitalRead(BTN_SELECT) == LOW) {
// โค้ดเมื่อกดตกลง เช่น เรียกฟังก์ชันอื่น
runSelectedFunction();
delay(200);
}
// ... (ฟังก์ชันวาดหน้าจอ วางตรงนี้) ...
}
