ลุกค่ะเทวดา
คัดลอกโค้ด
IoT Smart Health Reminder Device

โปรเจกต์ "ลุกค่ะเทวดา"
แก้อาการเนือยนิ่ง

นวัตกรรมกล่องเตือนความจำอัจฉริยะอัตโนมัติ ช่วยแก้อาการพฤติกรรมเนือยนิ่ง (Sedentary Behavior) ของคนทำงานออฟฟิศและนักเรียน อุปกรณ์จะสุ่มเล่นเพลงพร้อมแสงไฟจังหวะสามช่าสุดมันส์ และเปลี่ยนเป็นไฟสีแดงนับเวลาถอยหลังทุก 30 นาที!

นับเวลาอัตโนมัติ
แจ้งเตือนทุก 30 นาที
สุ่มเพลง 1-3
เปิดผ่าน DFPlayer Mini
ไฟ NeoPixel 8 ดวง
สามช่า & ไฟแดงนับเวลา
3D Preview: กล่องหุ่นยนต์ลุกค่ะเทวดา
WAITING 30m
RELAY: OFF

* รูปแบบการประกอบอุปกรณ์จำลองในเคสอะคริลิค/พลาสติก 3D Print

รายการอุปกรณ์ที่ใช้ (Hardware Components)

อุปกรณ์อิเล็กทรอนิกส์พื้นฐาน หาง่าย และราคาประหยัด สามารถสร้างใช้งานได้จริง

บอร์ด Arduino Uno R3 / Nano

ไมโครคอนโทรลเลอร์ประมวลผลหลัก สั่งการตามเวลาและรับสัญญาณสถานะ

Main MCU Board

DFPlayer Mini MP3 Module

โมดูลเล่นไฟล์เพลง MP3 ผ่าน MicroSD Card เช็คสถานะการจบเพลงด้วย Serial Feedback

Software Serial (TX/RX)

WS2812B NeoPixel LED (8 ดวง)

หลอดไฟ RGB แสดงไฟจังหวะสามช่าตอนเล่นเพลง และไฟแดงนับเวลาดวงละ ~3.75 นาที

Digital Pin 6

Relay Module / Driver Actuator

สวิตช์อิเล็กทรอนิกส์ควบคุมการเปิด-ปิดอุปกรณ์ภายนอก เช่น มอเตอร์เต้น หรือพัดลม

Digital Pin 3

ลำโพง 3W 8 Ohm

ลำโพงกำลังขับเหมาะสม เสียงดังฟังชัด ต่อตรงขนานเข้ากับขา SPK1/SPK2

Speaker Direct Out

MicroSD Card & ตัวต้านทาน

Format เป็น FAT32 เก็บไฟล์เพลง MP3 0001 - 0003 และตัวต้านทาน 1kΩ สำหรับสายสื่อสาร

FAT32 / Resistor 1kΩ

ตารางและผังการต่อวงจร (Wiring Diagram Guide)

ผังการเชื่อมต่อขาพินระหว่าง Arduino กับโมดูลต่างๆ อย่างถูกต้องและปลอดภัย

โมดูล / อุปกรณ์ ขาอุปกรณ์ (Component Pin) ขาบอร์ด Arduino (Pin Target) หมายเหตุสำคัญ
DFPlayer Mini TX Pin 10 (Software RX) ต่อตรง หรือผ่านตัวต้านทาน 1kΩ
DFPlayer Mini RX Pin 11 (Software TX) ต่อผ่านตัวต้านทาน 1kΩ ป้องกันไฟเกิน
DFPlayer Mini BUSY Unused (ไม่ได้ต่อ) ยกเลิกการใช้ - เปลี่ยนไปใช้ Serial Feedback ตรวจเพลงจบแทน
DFPlayer Mini VCC / GND 5V / GND ใช้ GND ร่วมกับระบบทั้งหมด
NeoPixel LED (8 ดวง) DIN (Data In) Pin 6 ควบคุมสีไฟรายดวง 8 LEDs
NeoPixel LED (8 ดวง) VCC / GND 5V / GND แนะนำต่อกับไฟ 5V ที่เสถียร
Relay / Driver Pin IN / SIG Pin 3 สวิตช์เปิดอุปกรณ์ภายนอกพร้อมเพลง
คำแนะนำด้านความปลอดภัยและการเชื่อมต่อ: ต้องเชื่อมสาย **GND (Grounded)** ของบอร์ด Arduino, DFPlayer Mini และ NeoPixel เข้าด้วยกันทั้งหมด และขาสื่อสาร Serial Feedback ช่วยให้ตรวจจบเพลงผ่านสาย TX/RX ได้อย่างแม่นยำ ไม่ต้องพึ่งพาสาย BUSY

กระบวนการทำงานของระบบ (System Workflow)

ขั้นตอนการทำงานอัตโนมัติที่ไม่ขัดจังหวะผู้ใช้ ตรวจเช็คสถานะสมบูรณ์ผ่านซอฟต์แวร์

1

เริ่มเปิดเครื่อง (Power On)

ระบบสุ่มเล่นเพลงแทร็ก 1 - 3 ทันที สั่งเปิดสวิตช์รีเลย์ และแสดงไฟ NeoPixel จังหวะสามช่าสุดสนุกสนาน

2

เช็คจบเพลงอัตโนมัติ

บอร์ด Arduino ตรวจสอบสัญญาณ Serial Feedback (DFPlayerPlayFinished) จากโมดูล DFPlayer Mini โดยตรงเมื่อเล่นเพลงจบ

3

นับเวลา 30 นาที + ไฟแดง

หยุดเพลงและแสดงไฟสีแดงแทนเวลาดวงละ ~3.75 นาที โดยดวงที่กำลังเดินอยู่จะกระพริบ และดวงที่ผ่านมาแล้วจะติดค้าง

4

เริ่มรอบใหม่ (Loop)

เมื่อครบกำหนด 30 นาที ระบบจะวนกลับไปสุ่มเพลงใหม่และเล่นไฟสามช่าเตือนให้ผู้ใช้นั่งทำงานลุกขยับร่างกายทันที!

ซอร์สโค้ด Arduino ฉบับสมบูรณ์ (Complete Code)

ไฟจังหวะสามช่าตอนเล่นเพลง + ไฟแดงนับเวลาดวงละ ~3.75 นาทีตอนรอ + เช็คเพลงจบด้วย Serial Feedback

StandUpAngel_SerialFeedback.ino C++ / Arduino IDE
#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>
#include <Adafruit_NeoPixel.h>

// ---------------- Pins Configuration ----------------
#define LED_PIN       6     // พินต่อ NeoPixel (8 ดวง)
#define NUM_LEDS      8     
#define ACTION_PIN    3     // พินสั่งเปิด-ปิดอุปกรณ์ (เช่น รีเลย์/มอเตอร์)
// *ยกเลิกการใช้ BUSY_PIN (ขา 4) ปล่อยว่างไว้ได้เลยครับ*

// ---------------- Timing Configuration ----------------
// ตั้งเวลาพักรอนับถอยหลัง 30 นาที = 30 * 60 * 1000 = 1,800,000 มิลลิวินาที
const unsigned long INTERVAL = 1800000UL; 

// ---------------- States Definition ----------------
enum SystemState {
  STATE_PLAYING, // กำลังเล่นเพลงและโชว์ไฟจังหวะสามช่า
  STATE_WAITING  // กำลังรอนับเวลา 30 นาที (ไฟแดงนับถอยหลังดวงละ 4 นาที)
};

SystemState currentState = STATE_PLAYING; 
unsigned long timerStartMillis = 0;

// ---------------- Objects Setup ----------------
SoftwareSerial mySoftwareSerial(10, 11); // RX, TX สำหรับเชื่อมต่อ DFPlayer
DFRobotDFPlayerMini myDFPlayer;
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  Serial.begin(115200);
  mySoftwareSerial.begin(9600);

  // สุ่ม Seed จาก Analog pin
  randomSeed(analogRead(0));

  // Initialize Pins
  pinMode(ACTION_PIN, OUTPUT);
  digitalWrite(ACTION_PIN, LOW); // ปิดอุปกรณ์ไว้ก่อน

  // Initialize NeoPixel
  strip.begin();
  strip.show(); 
  strip.setBrightness(100); // ความสว่างปานกลาง

  // Initialize DFPlayer Mini
  Serial.println(F("Initializing DFPlayer..."));
  if (!myDFPlayer.begin(mySoftwareSerial)) {
    Serial.println(F("DFPlayer Error! Check wiring & SD Card."));
    setAllLEDs(255, 0, 0); // ไฟสีแดงเตือนถ้าพบข้อผิดพลาด
    while (true);
  }
  Serial.println(F("DFPlayer Mini Ready."));
  myDFPlayer.volume(25); // ระดับเสียง (0 - 30)

  // เริ่มต้นสั่งเล่นเพลงรอบแรกทันทีเมื่อเปิดเครื่อง
  triggerPlayRoutine();
}

void loop() {
  switch (currentState) {
    case STATE_PLAYING:
      // กำลังเล่นเพลงอยู่: แสดงไฟจังหวะสามช่าสุดมันส์
      samChaLightEffect();
      
      // ตรวจสอบสัญญาณ Serial Feedback จาก DFPlayer ว่าเพลงเล่นจบหรือยัง
      if (myDFPlayer.available()) {
        // ถ้าประเภทของอีเวนต์คือ เล่นเพลงจบ (Play Finished)
        if (myDFPlayer.readType() == DFPlayerPlayFinished) {
          Serial.println(F("Song finished automatically via Serial Feedback!"));
          stopPlayRoutine();
          
          // สลับเข้าสู่โหมดรอนับเวลา 30 นาที
          currentState = STATE_WAITING;
          timerStartMillis = millis(); 
        }
      }
      break;

    case STATE_WAITING:
      {
        unsigned long currentMillis = millis();
        unsigned long elapsed = currentMillis - timerStartMillis;

        // ตรวจสอบว่าครบ 30 นาทีหรือยัง
        if (elapsed >= INTERVAL) {
          Serial.println(F("30 minutes elapsed. Starting next song..."));
          triggerPlayRoutine(); // วนกลับมาเปิดเพลงใหม่
          currentState = STATE_PLAYING;
        } else {
          // ระหว่างรอ: แสดงไฟสีแดงนับเวลาดวงละ ~3.75 นาที ดวงปัจจุบันกะพริบ ดวงที่ผ่านมาติดค้าง
          showRedProgressWithBlink(elapsed, INTERVAL);
        }
      }
      break;
  }
  
  delay(50); 
}

// ----------------- ฟังก์ชันเริ่มเล่นเพลงและอุปกรณ์ -----------------
void triggerPlayRoutine() {
  int songNumber = random(1, 4); // สุ่มเพลง 1 ถึง 3
  Serial.print(F("Playing Random Track: "));
  Serial.println(songNumber);
  
  myDFPlayer.play(songNumber);
  digitalWrite(ACTION_PIN, HIGH); // เปิดอุปกรณ์ทำงานพร้อมเพลง
}

// ----------------- ฟังก์ชันหยุดเพลงและปิดอุปกรณ์ -----------------
void stopPlayRoutine() {
  myDFPlayer.stop();
  digitalWrite(ACTION_PIN, LOW); // ปิดอุปกรณ์
  clearLEDs();
}

// ----------------- เอฟเฟกต์ไฟจังหวะสามช่า (ตอนเปิดเพลง) -----------------
void samChaLightEffect() {
  // แบ่งรอบเวลา 1 วินาที (1000 มิลลิวินาที) เป็นจังหวะสนุกสนานแนวสามช่า
  unsigned long beatTime = millis() % 1000; 

  if (beatTime < 150) {
    setAllLEDs(255, 20, 100); // บีทที่ 1: สีชมพู/ส้ม
  } 
  else if (beatTime < 250) {
    clearLEDs();              // พักสั้นๆ
  } 
  else if (beatTime < 400) {
    setAllLEDs(0, 255, 100);  // บีทที่ 2: สีเขียวสดใส
  } 
  else if (beatTime < 500) {
    clearLEDs();              // พักสั้นๆ
  } 
  else {
    // ช่วงรัวจังหวะท้าย: วิ่งสีรุ้งหมุนวนเร็วๆ
    static byte wheelPos = 0;
    for (int i = 0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + wheelPos) & 255));
    }
    strip.show();
    wheelPos += 30; 
  }
}

// ----------------- ฟังก์ชันแสดงไฟสีแดง ดวงละ ~3.75 นาที และดวงปัจจุบันกะพริบ -----------------
void showRedProgressWithBlink(unsigned long elapsed, unsigned long total) {
  // คำนวณว่าเวลาที่ผ่านไปตกอยู่ที่หลอดไฟดวงที่เท่าไหร่ (0 ถึง 7)
  int activeLedIndex = (elapsed * NUM_LEDS) / total;
  if (activeLedIndex >= NUM_LEDS) activeLedIndex = NUM_LEDS - 1;

  // สร้างจังหวะกะพริบสำหรับหลอดปัจจุบัน (กระพริบทุกๆ 500ms)
  bool isBlinkOn = (millis() / 500) % 2 == 0;

  strip.clear();
  for (int i = 0; i < NUM_LEDS; i++) {
    if (i < activeLedIndex) {
      // ดวงที่ผ่านมาแล้ว: ติดไฟสีแดงค้างไว้
      strip.setPixelColor(i, strip.Color(100, 0, 0)); 
    } else if (i == activeLedIndex) {
      // ดวงปัจจุบันกำลังนับเวลาอยู่: ให้กะพริบติด-ดับ
      if (isBlinkOn) {
        strip.setPixelColor(i, strip.Color(100, 0, 0)); // สีแดงสว่าง
      } else {
        strip.setPixelColor(i, strip.Color(0, 0, 0));   // ดับ
      }
    } else {
      // ดวงที่ยังมาไม่ถึง: ปิดไว้
      strip.setPixelColor(i, strip.Color(0, 0, 0));
    }
  }
  strip.show();
}

// ----------------- ฟังก์ชันช่วยคำนวณสีรุ้ง -----------------
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if (WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

void setAllLEDs(byte r, byte g, byte b) {
  for (int i = 0; i < NUM_LEDS; i++) {
    strip.setPixelColor(i, strip.Color(r, g, b));
  }
  strip.show();
}

void clearLEDs() {
  strip.clear();
  strip.show();
}

ชุดสาธิตการทำงานแบบจำลอง (Live Demo Simulation)

ทดสอบจำลองเอฟเฟกต์ไฟ LED บนหน้าจอบราวเซอร์

MODE: PLAYING MUSIC & DANCING
Track 0001.mp3 Playing... | Relay: HIGH