ไลบรารี Adafruit_SSD1306 นั้นทรงพลังมากครับ เพราะมันทำงานร่วมกับ Adafruit_GFX (ซึ่งเป็นตัวจัดการกราฟิกพื้นฐาน) ทำให้เราวาดรูปทรงและข้อความได้หลากหลาย


1. คอนเซ็ป (Core Concept)

ก่อนจะไปดูคำสั่ง ต้องเข้าใจ 2 เรื่องนี้ก่อน:

  1. พิกัด (Coordinates): มุมซ้ายบนคือ (0, 0) แกน X แนวนอน (0-127), แกน Y แนวตั้ง (0-63)
  2. ระบบ 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)

C++
#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 ที่แปลงมาแล้ว

วิธีการทำงาน

  1. เราเก็บข้อมูลภาพเป็น Hex Array (รหัสฐาน 16) ไว้ใน PROGMEM (เพื่อประหยัด RAM)
  2. ใช้คำสั่ง drawBitmap เรียกข้อมูลนั้นมาแสดง

โค้ดตัวอย่าง

C++
#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() {
  // ไม่ต้องทำอะไร
}

ถ้าอยากเปลี่ยนเป็นรูปอื่นทำอย่างไร?

เราสามารถแปลงรูปอะไรก็ได้ให้เป็นโค้ดแบบข้างบน ด้วยวิธีนี้:

  1. เตรียมรูป: หารูปขาว-ดำ (ไฟล์ .png หรือ .jpg)
  2. เข้าเว็บแปลง: ค้นหา Google ว่า “image2cpp” (เว็บยอดฮิตคือ javl.github.io/image2cpp)
  3. ตั้งค่า:
    • Upload รูปเข้าไป
    • กำหนดขนาด (เช่น 64×64 หรือ 128×64)
    • Background color: เลือกให้เหมาะสม (ปกติ Black)
    • Invert colors: ติ๊กถูก ถ้าต้องการให้ส่วนที่เป็นสีดำกลายเป็นแสงสว่างบนจอ
  4. Generate Code:
    • Output format: เลือก Arduino Code
    • กด Generate code
    • เราจะได้ชุดตัวเลข 0x00, 0xFF... มาแทนที่ชุด nikeLogo ในโค้ดได้เลยครับ

การออกแบบเมนูบนจอขนาดเล็ก 128×64 ต้องเน้น “ความชัดเจน” และ “รู้ว่าเลือกอะไรอยู่”

แบบที่ 1: เมนูแบบรายการแนวตั้ง (Vertical List)

ใช้เทคนิค “Invert Color” (ถมดำเปลี่ยนเป็นขาว) ตรงบรรทัดที่ถูกเลือก ทำให้ดูง่ายมาก

C++
#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 มาใส่แทนได้เลย)

C++
#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 ปุ่ม: ปุ่มเลื่อน, ปุ่มเลือก) ให้ทำแบบนี้ครับ:

  1. ต่อปุ่ม: ขาหนึ่งลง GND, อีกขาเข้า Pin (เช่น D2, D3)
  2. เขียนโค้ด:
C++
#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);
  }
  
  // ... (ฟังก์ชันวาดหน้าจอ วางตรงนี้) ...
}