สวัสดีครับเพื่อน ๆ นักพัฒนา Arduino ทุกคน! วันนี้เรามาดูกันว่าเราจะสร้างระบบวัดระยะทางสุดเจ๋งด้วย Arduino, เซ็นเซอร์ HC-SR04 และจอ LCD I2C ได้อย่างไร โดยเพิ่มความแม่นยำด้วยการกรองสัญญาณแบบ Real-time กันเลย! 🚀
โค้ดฉบับเต็ม: https://github.com/mengsokool/Arduino-EMA-Tape
🌟 ทำไมต้องใช้ระบบวัดระยะอัจฉริยะ?
ในยุคที่เทคโนโลยี IoT กำลังมาแรง การวัดระยะทางที่แม่นยำและรวดเร็วเป็นสิ่งสำคัญมาก ๆ ไม่ว่าจะเป็นการทำหุ่นยนต์หลบสิ่งกีดขวาง, ระบบเตือนภัยอัจฉริยะ หรือแม้แต่การทำเครื่องวัดส่วนสูงดิจิทัล ก็ล้วนต้องการระบบวัดระยะที่เชื่อถือได้ทั้งนั้น
แต่ปัญหาคือ... การวัดระยะด้วยเซ็นเซอร์อัลตราโซนิกอย่าง HC-SR04 มักจะมีสัญญาณรบกวนเยอะ ทำให้ค่าที่วัดได้กระโดดไปมา 😵💫 แล้วเราจะแก้ปัญหานี้ยังไงดีล่ะ?

💡 วิธีการแก้ปัญหา: กรองสัญญาณแบบ Real-time!
เราจะใช้เทคนิคการกรองสัญญาณที่เรียกว่า Exponential Moving Average (EMA) ซึ่งเป็นวิธีที่ง่ายแต่มีประสิทธิภาพสูงในการลดสัญญาณรบกวน โดยไม่ทำให้ระบบตอบสนองช้าเกินไป
🔍 หลักการทำงานของ EMA
EMA จะคำนวณค่าเฉลี่ยแบบถ่วงน้ำหนัก โดยให้น้ำหนักกับค่าล่าสุดมากกว่าค่าเก่า ๆ ทำให้ระบบตอบสนองต่อการเปลี่ยนแปลงได้เร็ว แต่ก็ยังคงความเรียบของข้อมูลไว้ได้
สูตรคำนวณ EMA คือ:
EMA = α * ค่าปัจจุบัน + (1 - α) * EMA ก่อนหน้า
โดย α (alpha) คือค่าระหว่าง 0 ถึง 1 ที่กำหนดว่าจะให้น้ำหนักกับค่าใหม่มากแค่ไหน
ในโค้ดของเรา เราใช้ ALPHA = 0.1
ซึ่งให้การกรองที่นุ่มนวลพอดี ไม่ช้าเกินไปและไม่ไวเกินไป
🛠️ มาดูโค้ดกันเลย!
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// กำหนดขาของ HC-SR04
#define TRIG_PIN 9
#define ECHO_PIN 10
// กำหนดหน้าจอ LCD
LiquidCrystal_I2C lcd(0x27, 16, 2);
// ตัวแปรหลัก
float distance = 0.0;
float lastDistance = 0.0;
float filteredDistance = 0.0;
unsigned long lastUpdate = 0;
#define ALPHA 0.1 // กรองค่า Exponential Moving Average (ลดจาก 0.3 เป็น 0.1)
// ตั้งค่าอัตราการอัปเดตจอ LCD
#define BASE_REFRESH_RATE 1000 // ค่าเริ่มต้น (ms)
#define FAST_REFRESH_RATE 100 // ค่าเร็วสุด (ms)
unsigned long refreshRate = BASE_REFRESH_RATE;
// สำหรับ spinner animation
int spinnerIndex = 0;
char spinnerChars[4] = {'|', '/', '-', '\\'};
// สำหรับ gauge bar
const float maxDistance = 200.0; // สมมุติระยะทางสูงสุด 200 cm
const int gaugeWidth = 15; // ใช้ 15 ช่องสำหรับ gauge bar (ช่องสุดท้ายจองไว้สำหรับ spinner)
void setup() {
Serial.begin(115200);
lcd.init();
lcd.backlight();
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
showSplashScreen(); // แสดง splash screen ตอนเปิดเครื่อง
}
void loop() {
delay(100);
// อ่านค่าระยะด้วยการสุ่มตัวอย่างหลายครั้ง (5 ค่า)
distance = measureDistance();
// กรองค่าด้วย Exponential Moving Average (EMA)
filteredDistance = (ALPHA * distance) + ((1 - ALPHA) * filteredDistance);
// ตรวจจับความเปลี่ยนแปลงของระยะ
float diff = abs(lastDistance - filteredDistance);
// ปรับอัตราการอัปเดตจอ LCD ตามการเปลี่ยนแปลง
if (diff > 50) {
refreshRate = FAST_REFRESH_RATE; // เปลี่ยนแปลงมาก → อัปเดตเร็วสุด
} else if (diff > 5) {
refreshRate = 300; // เปลี่ยนแปลงปานกลาง
} else {
refreshRate = BASE_REFRESH_RATE; // เปลี่ยนแปลงน้อย → อัปเดตช้า
}
// อัปเดต LCD เมื่อถึงเวลาหรือมีการเปลี่ยนแปลงมากกว่า 0.1
unsigned long currentMillis = millis();
if ((currentMillis - lastUpdate >= refreshRate) && (diff > 0.1)) {
lastUpdate = currentMillis;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Dist:");
lcd.print(filteredDistance, 1);
lcd.print("cm");
// บรรทัดที่สอง: แสดง gauge bar พร้อม spinner animation
lcd.setCursor(0, 1);
float ratio = filteredDistance / maxDistance;
if (ratio > 1.0) ratio = 1.0;
int totalPixels = gaugeWidth * 5;
int filledPixels = ratio * totalPixels;
for (int i = 0; i < gaugeWidth; i++) {
int cellFill = filledPixels - (i * 5);
if (cellFill >= 5) {
lcd.write(byte(5));
} else if (cellFill > 0) {
lcd.write(byte(cellFill));
} else {
lcd.write(byte(0));
}
}
lcd.setCursor(gaugeWidth, 1);
lcd.print(spinnerChars[spinnerIndex]);
spinnerIndex = (spinnerIndex + 1) % 4;
Serial.print("Measured: ");
Serial.print(filteredDistance);
Serial.println(" cm");
lastDistance = filteredDistance;
}
}
// ฟังก์ชันวัดระยะทางโดยใช้ค่าเฉลี่ยจากหลายการอ่าน
float measureDistance() {
float sum = 0;
int samples = 5; // อ่านค่า 5 ครั้งแล้วหาค่าเฉลี่ย
for (int i = 0; i < samples; i++) {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH);
float dist = duration * 0.034 / 2;
// กรองค่าสุ่มผิดพลาด
if (dist < 2 || dist > 200) {
continue; // ข้ามค่าที่ผิดปกติ
}
sum += dist;
delay(10);
}
return sum / samples; // ค่าเฉลี่ย
}
// ฟังก์ชันแสดง splash screen เท่สุดโต่ง
void showSplashScreen() {
byte progress0[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
byte progress1[8] = {0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10};
byte progress2[8] = {0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18};
byte progress3[8] = {0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C};
byte progress4[8] = {0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,0x1E};
byte progress5[8] = {0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F};
lcd.createChar(0, progress0);
lcd.createChar(1, progress1);
lcd.createChar(2, progress2);
lcd.createChar(3, progress3);
lcd.createChar(4, progress4);
lcd.createChar(5, progress5);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Taksa-on Tape!");
lcd.setCursor(0, 1);
lcd.print("Booting Up...");
delay(1500);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Taksa-on Tape!");
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) {
int frac = progress % 5;
lcd.write(byte(frac));
} else {
lcd.write(byte(0));
}
}
delay(30);
}
delay(500);
lcd.clear();
}
🔬 การทำงานของโค้ด
- การวัดระยะ: ใช้ฟังก์ชัน
measureDistance()
วัดระยะหลาย ๆ ครั้งแล้วหาค่าเฉลี่ย - การกรองสัญญาณ: ใช้ EMA กรองค่าที่วัดได้ให้เรียบขึ้น
- การปรับ Refresh Rate: ปรับความถี่ในการอัพเดทจอ LCD ตามการเปลี่ยนแปลงของระยะทาง
- การแสดงผล: ใช้ Gauge Bar และ Spinner Animation เพื่อให้การแสดงผลน่าสนใจยิ่งขึ้น
🎨 การแสดงผลสุดเจ๋ง!
เราไม่ได้แค่แสดงตัวเลขธรรมดา ๆ นะ แต่เรามี:
- Gauge Bar: แสดงระยะทางเป็นแถบกราฟิก ทำให้เห็นภาพได้ง่ายขึ้น
- Spinner Animation: แสดงว่าระบบยังทำงานอยู่ ไม่ได้ค้างหรือหยุดทำงาน
- Dynamic Refresh Rate: ปรับความเร็วในการอัพเดทจอตามการเปลี่ยนแปลงของระยะทาง
🚀 ประโยชน์ที่ได้รับ
- ความแม่นยำสูง: ด้วยการกรองสัญญาณ EMA ทำให้ค่าที่วัดได้มีความเสถียรมากขึ้น
- การแสดงผลที่น่าสนใจ: Gauge Bar และ Spinner ทำให้ผู้ใช้เข้าใจข้อมูลได้ง่ายขึ้น
- ประหยัดพลังงาน: การปรับ Refresh Rate ช่วยลดการทำงานของ LCD เมื่อไม่จำเป็น
- เหมาะกับงานหลากหลาย: ใช้ได้ทั้งในโปรเจค IoT, หุ่นยนต์ หรือระบบอัตโนมัติต่าง ๆ
💡 ไอเดียการประยุกต์ใช้
- หุ่นยนต์หลบสิ่งกีดขวาง: ใช้วัดระยะห่างจากสิ่งกีดขวางได้อย่างแม่นยำ
- ระบบเตือนภัยอัจฉริยะ: ตรวจจับการเคลื่อนไหวหรือการบุกรุก
- เครื่องวัดส่วนสูงดิจิทัล: สร้างเครื่องวัดส่วนสูงที่แม่นยำและอ่านค่าง่าย
- ระบบจอดรถอัจฉริยะ: ช่วยในการจอดรถโดยบอกระยะห่างจากกำแพงหรือรถคันอื่น
🏆 สรุป
ด้วยการผสมผสานระหว่างเซ็นเซอร์ HC-SR04, จอ LCD I2C และเทคนิคการกรองสัญญาณ EMA เราสามารถสร้างระบบวัดระยะที่ทั้งแม่นยำ น่าใช้ และยืดหยุ่นสูง เหมาะสำหรับนำไปต่อยอดในโปรเจคต่าง ๆ ได้อย่างหลากหลาย

ชอบบทความนี้ไหมครับ? ถ้าใช่ อย่าลืมแชร์ให้เพื่อน ๆ ที่สนใจ Arduino ด้วยนะครับ! และถ้าคุณได้ลองทำตามแล้ว อย่าลืมแท็ก #ArduinoThailand
#DistanceSensorTH
บนโซเชียลมีเดียด้วยล่ะ เราอยากเห็นผลงานของคุณมาก ๆ เลย! 🌟
แหล่งข้อมูล
- "Arduino - Home." Arduino
- "HC-SR04 Ultrasonic Sensor." Components101
- "I2C LCD with Arduino." Last Minute Engineers
- "Exponential Moving Average." Investopedia