สร้างเครื่องวัดระยะทางสุดแม่นยำด้วย Arduino : แก้ปัญหาค่ากระโดดด้วย EMA Filter!

2025-03-22T22:21:11.000+00:00

เคยไหม? สร้างโปรเจกต์วัดระยะด้วย Arduino กับเซ็นเซอร์ HC-SR04 แต่ค่าที่ได้กลับไม่นิ่ง กระโดดไปมา ทำให้หุ่นยนต์เดินเป๋ หรือระบบวัดระดับน้ำทำงานผิดพลาด? ปัญหานี้พบบ่อยมากครับ! แต่ไม่ต้องห่วง วันนี้เราจะมาแก้ปัญหานี้ด้วยเทคนิค Exponential Moving Average (EMA) พร้อมสร้างเครื่องวัดระยะทางสุดแม่นยำ ที่แสดงผลสวยๆ บนจอ LCD I2C!

🚀 สิ่งที่คุณจะได้เรียนรู้

1. ปัญหา "ค่ากระโดด" และทางออกด้วย EMA Filter

ทำไมค่าที่วัดได้ถึงไม่นิ่ง?

เซ็นเซอร์ HC-SR04 ทำงานโดยส่งคลื่นเสียงอัลตราโซนิกออกไป แล้ววัดเวลาที่คลื่นสะท้อนกลับมา แต่ในโลกจริง มีปัจจัยหลายอย่างที่ทำให้เกิดความคลาดเคลื่อน:

EMA Filter ช่วยได้อย่างไร?

Exponential Moving Average (EMA) เป็นเทคนิคการกรองสัญญาณที่ช่วยลดผลกระทบจาก "ค่ากระโดด" เหล่านี้ โดยการคำนวณค่าเฉลี่ยแบบถ่วงน้ำหนัก:

ค่าเฉลี่ยใหม่ = α * ค่าที่วัดได้ปัจจุบัน + (1 - α) * ค่าเฉลี่ยก่อนหน้า

ข้อดีของ EMA:

2. อุปกรณ์ที่ต้องใช้

อุปกรณ์ รายละเอียด จำนวน
บอร์ด Arduino Uno, Nano, หรือรุ่นอื่นๆ ที่รองรับ 1
เซ็นเซอร์ HC-SR04 เซ็นเซอร์วัดระยะทางด้วยคลื่นอัลตราโซนิก 1
จอ LCD I2C จอแสดงผล LCD แบบ I2C (แนะนำขนาด 16x2) 1
สายจัมเปอร์ (Jumper Wires) ตัวผู้-ตัวเมีย (M-F) และ ตัวผู้-ตัวผู้ (M-M) 10
โปรโตบอร์ด (Breadboard) (Optional) สำหรับต่อวงจร 1

3. ต่อวงจรให้ถูกต้อง

สำคัญมาก: การต่อวงจรผิดพลาดอาจทำให้อุปกรณ์เสียหายได้!

การเชื่อมต่อ:

ตรวจสอบให้แน่ใจว่า:

4. เขียนโค้ด Arduino

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// กำหนดขา
const int TRIG_PIN = 9;
const int ECHO_PIN = 10;

// กำหนด LCD
LiquidCrystal_I2C lcd(0x27, 16, 2); // ที่อยู่ I2C อาจต่างกัน (0x27, 0x3F)

// ตัวแปร
float distance = 0.0f;
float filteredDistance = 0.0f;
const float ALPHA = 0.1f; // Smoothing Factor
unsigned long lastUpdate = 0;
const unsigned long BASE_REFRESH_RATE = 1000;
const unsigned long FAST_REFRESH_RATE = 100;
unsigned long refreshRate = BASE_REFRESH_RATE;

// Gauge Bar
const float MAX_DISTANCE = 200.0f; // ระยะสูงสุดที่แสดง (cm)
const int GAUGE_WIDTH = 15; // จำนวนช่องของ Gauge Bar

// Spinner
int spinnerIndex = 0;
const char spinnerChars[] = {'|', '/', '-', '\\'};

void setup() {
  Serial.begin(115200);
  lcd.init();
  lcd.backlight();
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  showSplashScreen();
}

void loop() {
  distance = measureDistance();

  if (isnan(distance)) { // เช็ค error
    displayError();
    return;
  }

  filteredDistance = (ALPHA * distance) + ((1 - ALPHA) * filteredDistance);

  float diff = abs(distance - filteredDistance);

  if (diff > 50) {
    refreshRate = FAST_REFRESH_RATE;
  } else if (diff > 5) {
    refreshRate = 300;
  } else {
    refreshRate = BASE_REFRESH_RATE;
  }

  unsigned long currentMillis = millis();
  if (currentMillis - lastUpdate >= refreshRate) {
    lastUpdate = currentMillis;
    updateDisplay();
  }
}

// --- ฟังก์ชัน ---

float measureDistance() {
  long duration;
  float dist;
  float sum = 0.0f;
  int validSamples = 0;
  const int samples = 5;

  for (int i = 0; i < samples; i++) {
    digitalWrite(TRIG_PIN, LOW);
    delayMicroseconds(2);
    digitalWrite(TRIG_PIN, HIGH);
    delayMicroseconds(10);
    digitalWrite(TRIG_PIN, LOW);

    duration = pulseIn(ECHO_PIN, HIGH, 25000); // เพิ่ม timeout

    if (duration == 0) { // timeout หรือ error
      continue;
    }

    dist = duration * 0.034f / 2.0f; // คำนวณระยะทาง (cm)

    if (dist >= 2 && dist <= 200) { // กรองค่าที่อยู่นอกช่วง
      sum += dist;
      validSamples++;
    }
    delay(10);
  }

    if(validSamples == 0){
      Serial.println("ERROR: Invalid Samples.");
      return NAN;
    }

  return sum / (float)validSamples;
}


void updateDisplay() {
  lcd.clear();
  displayDistance();
  displayGaugeBar();
  displaySpinner();
   Serial.print("Measured: ");
    Serial.print(distance);
    Serial.print(" Filtered: ");
    Serial.println(filteredDistance);
}

void displayDistance() {
  lcd.setCursor(0, 0);
  lcd.print("Dist: ");
  lcd.print(filteredDistance, 1); // ทศนิยม 1 ตำแหน่ง
  lcd.print(" cm");
}

void displayGaugeBar() {
  lcd.setCursor(0, 1);
  float ratio = constrain(filteredDistance / MAX_DISTANCE, 0.0f, 1.0f);
  int filledPixels = (int)(ratio * (GAUGE_WIDTH * 5)); // คูณ 5 เพราะ 1 ตัวอักษร = 5 พิกเซล

  for (int i = 0; i < GAUGE_WIDTH; i++) {
    int cellFill = filledPixels - (i * 5);
    if (cellFill >= 5) {
      lcd.write(5); // เต็มช่อง
    } else if (cellFill > 0) {
      lcd.write((byte)cellFill); // เติมบางส่วน
    } else {
      lcd.write(0); // ช่องว่าง
    }
  }
}

void displaySpinner() {
  lcd.setCursor(GAUGE_WIDTH, 1);
  lcd.print(spinnerChars[spinnerIndex]);
  spinnerIndex = (spinnerIndex + 1) % 4;
}
void displayError(){
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Sensor Error!");
    delay(1000);
}

void showSplashScreen() {
  // สร้าง Custom Characters
  byte progress[5][8] = {
    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
    {0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10},
    {0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18},
    {0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C},
    {0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E}
  };

  for (int i = 0; i < 5; i++) {
    lcd.createChar(i, progress[i]);
  }
  lcd.createChar(5, (byte)B11111111); // full block character

  // แสดงข้อความเริ่มต้น
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Smart Distance");
  lcd.setCursor(0, 1);
  lcd.print("Initializing...");
  delay(1500);

  // แสดง Animation
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Smart Distance");
  int barLength = 16;
  int totalSteps = barLength * 5;

  for (int progress = 0; progress <= totalSteps; progress++) {
    lcd.setCursor(0, 1);
    for (int i = 0; i < barLength; i++) {
      if (i < progress / 5) {
        lcd.write(byte(5)); // ช่องเต็ม
      } else if (i == progress / 5) {
        lcd.write(byte(progress % 5)); // ช่องไม่เต็ม
      } else {
        lcd.write(byte(0)); // ช่องว่าง
      }
    }
    delay(30); // ปรับความเร็ว Animation
  }
  delay(500);
  lcd.clear();
}

คำอธิบายโค้ด:

5. ทดสอบและปรับแต่ง

การปรับค่า Alpha

ค่า Alpha ในการคำนวณ EMA เป็นปัจจัยสำคัญที่ส่งผลต่อประสิทธิภาพของระบบ:

ค่า Alpha ผลลัพธ์ เหมาะสำหรับ
0.05 กรองมาก, ตอบสนองช้า สภาพแวดล้อมที่มีสัญญาณรบกวนสูง
0.1 กรองพอดี, ตอบสนองปานกลาง การใช้งานทั่วไป (ค่าแนะนำ)
0.3 กรองน้อย, ตอบสนองเร็ว การติดตามวัตถุที่เคลื่อนที่เร็ว

การวัดประสิทธิภาพ

เพื่อให้แน่ใจว่าระบบทำงานได้ถูกต้อง ควรทดสอบในสถานการณ์ต่าง ๆ:

  1. การวัดระยะทางคงที่: วางเซนเซอร์ห่างจากวัตถุในระยะคงที่ และบันทึกค่าความแปรปรวน
  2. การวัดระยะทางที่เปลี่ยนแปลงช้า ๆ: เคลื่อนวัตถุเข้าออกอย่างช้า ๆ และตรวจสอบว่าค่าเปลี่ยนแปลงอย่างราบรื่น
  3. การวัดระยะทางที่เปลี่ยนแปลงอย่างรวดเร็ว: เคลื่อนวัตถุเข้าออกอย่างรวดเร็ว และตรวจสอบว่าระบบสามารถติดตามการเปลี่ยนแปลงได้

6. ไอเดียการประยุกต์ใช้

7. Troubleshooting: แก้ปัญหาที่พบบ่อย

ปัญหา สาเหตุที่เป็นไปได้ วิธีแก้ไข
ค่าที่วัดได้กระโดดมาก สัญญาณรบกวนสูง, ALPHA สูงเกินไป, วัตถุมีพื้นผิวไม่เรียบ, วัดระยะนอกช่วงที่กำหนด ลดค่า ALPHA, ตรวจสอบสภาพแวดล้อม, ลองวัดวัตถุอื่น, ตรวจสอบระยะที่วัด
จอ LCD ไม่แสดงผล ต่อสายผิด, Address ของ LCD ไม่ถูกต้อง, Library ไม่ได้ติดตั้ง, โค้ดผิดพลาด ตรวจสอบการต่อสาย, ตรวจสอบ Address ของ LCD (ใช้ I2C Scanner), ติดตั้ง Library, ตรวจสอบโค้ด
เซ็นเซอร์ไม่ตอบสนอง ต่อสายผิด, เซ็นเซอร์เสีย, ขา Trig หรือ Echo ไม่ได้กำหนดถูกต้อง, pulseIn() timeout ตรวจสอบการต่อสาย, ลองเปลี่ยนเซ็นเซอร์, ตรวจสอบการกำหนดขา, เพิ่ม timeout ใน pulseIn()
ค่าที่วัดได้ไม่ถูกต้อง Calibrate ไม่ถูกต้อง, สภาพแวดล้อมมีผลต่อความเร็วเสียง, ใช้สูตรคำนวณผิด ทำ Calibration ใหม่ (ดูโค้ด), ลองวัดในสภาพแวดล้อมที่ต่างกัน, ตรวจสอบสูตรคำนวณ
Serial Monitor ไม่แสดง ไม่ได้เปิด Serial Monitor, Serial.begin() ไม่ได้เรียกใช้, Baud Rate ไม่ตรง, เลือก Port ไม่ถูก เปิด Serial Monitor, เรียกใช้ Serial.begin(115200);, ตั้ง Baud Rate ให้ตรงกัน, เลือก Port ให้ถูก

8. สรุปและก้าวต่อไป

ยินดีด้วย! ตอนนี้คุณได้สร้างเครื่องวัดระยะทางที่แม่นยำ และแสดงผลสวยงามด้วย Arduino พร้อมทั้งเรียนรู้เทคนิค EMA Filter ที่สามารถนำไปประยุกต์ใช้กับโปรเจกต์อื่นๆ ได้อีกมากมาย

ก้าวต่อไป:

แหล่งข้อมูลเพิ่มเติม:

หวังว่าบทความนี้จะเป็นประโยชน์ และช่วยให้คุณสร้างสรรค์โปรเจกต์เจ๋งๆ ได้นะครับ!