เคยไหม? สร้างโปรเจกต์วัดระยะด้วย Arduino กับเซ็นเซอร์ HC-SR04 แต่ค่าที่ได้กลับไม่นิ่ง กระโดดไปมา ทำให้หุ่นยนต์เดินเป๋ หรือระบบวัดระดับน้ำทำงานผิดพลาด? ปัญหานี้พบบ่อยมากครับ! แต่ไม่ต้องห่วง วันนี้เราจะมาแก้ปัญหานี้ด้วยเทคนิค Exponential Moving Average (EMA) พร้อมสร้างเครื่องวัดระยะทางสุดแม่นยำ ที่แสดงผลสวยๆ บนจอ LCD I2C!
🚀 สิ่งที่คุณจะได้เรียนรู้
- เข้าใจปัญหา ของการวัดระยะทางด้วยเซ็นเซอร์อัลตราโซนิก
- รู้จัก EMA Filter: เทคนิคการกรองสัญญาณที่เรียบง่ายแต่ทรงพลัง
- สร้างเครื่องวัดระยะทาง: ต่อวงจร, เขียนโค้ด, ทดสอบ และปรับแต่ง
- แสดงผลอย่างมืออาชีพ: ด้วย Gauge Bar และ Spinner Animation
- นำไปประยุกต์ใช้: กับหุ่นยนต์, ระบบ IoT, และโปรเจกต์อื่นๆ
1. ปัญหา "ค่ากระโดด" และทางออกด้วย EMA Filter
ทำไมค่าที่วัดได้ถึงไม่นิ่ง?
เซ็นเซอร์ HC-SR04 ทำงานโดยส่งคลื่นเสียงอัลตราโซนิกออกไป แล้ววัดเวลาที่คลื่นสะท้อนกลับมา แต่ในโลกจริง มีปัจจัยหลายอย่างที่ทำให้เกิดความคลาดเคลื่อน:
- สัญญาณรบกวน: เสียงสะท้อนจากวัตถุอื่นๆ, เสียงรบกวนจากสภาพแวดล้อม
- พื้นผิวไม่เรียบ: วัตถุที่มีพื้นผิวขรุขระ ทำให้คลื่นสะท้อนไม่สม่ำเสมอ
- อุณหภูมิและความชื้น: เปลี่ยนแปลงความเร็วของเสียง
EMA Filter ช่วยได้อย่างไร?
Exponential Moving Average (EMA) เป็นเทคนิคการกรองสัญญาณที่ช่วยลดผลกระทบจาก "ค่ากระโดด" เหล่านี้ โดยการคำนวณค่าเฉลี่ยแบบถ่วงน้ำหนัก:
ค่าเฉลี่ยใหม่ = α * ค่าที่วัดได้ปัจจุบัน + (1 - α) * ค่าเฉลี่ยก่อนหน้า
α
(อัลฟ่า) คือค่า "Smoothing Factor" (อยู่ระหว่าง 0 ถึง 1)α
น้อย (เช่น 0.1-0.3): กรองมาก, ค่าเปลี่ยนช้า (เหมาะกับสภาพแวดล้อมที่มีสัญญาณรบกวนสูง)α
มาก (เช่น 0.7-0.9): กรองน้อย, ค่าเปลี่ยนเร็ว (เหมาะกับการติดตามวัตถุที่เคลื่อนที่)
ข้อดีของ EMA:
- ง่าย: ใช้โค้ดเพียงไม่กี่บรรทัด
- เร็ว: ใช้ทรัพยากรน้อย เหมาะกับ Arduino
- ได้ผลจริง: ลดความผันผวนของค่าที่วัดได้อย่างมีประสิทธิภาพ
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. ต่อวงจรให้ถูกต้อง
สำคัญมาก: การต่อวงจรผิดพลาดอาจทำให้อุปกรณ์เสียหายได้!

การเชื่อมต่อ:
- HC-SR04:
VCC
->5V
(Arduino)GND
->GND
(Arduino)Trig
-> ขา9
(Arduino)Echo
-> ขา10
(Arduino)
- LCD I2C:
VCC
->5V
(Arduino)GND
->GND
(Arduino)SDA
->A4
(Arduino Uno) หรือขาSDA
SCL
->A5
(Arduino Uno) หรือขาSCL
ตรวจสอบให้แน่ใจว่า:
- ต่อสายถูกต้องตาม Fritzing Diagram
- ขั้ว
VCC
และGND
ไม่สลับกัน - สายไม่หลวม หรือแตะกัน

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();
}
คำอธิบายโค้ด:
#include <Wire.h>
และ#include <LiquidCrystal_I2C.h>
: เรียกใช้ Library ที่จำเป็นconst int TRIG_PIN = 9;
และconst int ECHO_PIN = 10;
: กำหนดขาที่ต่อกับเซ็นเซอร์LiquidCrystal_I2C lcd(0x27, 16, 2);
: สร้าง Object สำหรับ LCD (อาจต้องเปลี่ยน0x27
ถ้า LCD ของคุณมี Address ต่างกัน)const float ALPHA = 0.1f;
: กำหนดค่า Smoothing Factor สำหรับ EMAmeasureDistance()
: วัดระยะทาง, กรองค่าที่ผิดปกติ, และหาค่าเฉลี่ยupdateDisplay()
: แสดงผลบน LCD (เรียกใช้ฟังก์ชันย่อย)displayError
: ฟังก์ชันแสดง errordisplayDistance()
,displayGaugeBar()
,displaySpinner()
: แสดงข้อมูลแต่ละส่วนloop()
: วัดระยะ, คำนวณ EMA, ปรับ Refresh Rate, และแสดงผล
5. ทดสอบและปรับแต่ง
การปรับค่า Alpha
ค่า Alpha ในการคำนวณ EMA เป็นปัจจัยสำคัญที่ส่งผลต่อประสิทธิภาพของระบบ:
ค่า Alpha | ผลลัพธ์ | เหมาะสำหรับ |
---|---|---|
0.05 | กรองมาก, ตอบสนองช้า | สภาพแวดล้อมที่มีสัญญาณรบกวนสูง |
0.1 | กรองพอดี, ตอบสนองปานกลาง | การใช้งานทั่วไป (ค่าแนะนำ) |
0.3 | กรองน้อย, ตอบสนองเร็ว | การติดตามวัตถุที่เคลื่อนที่เร็ว |
การวัดประสิทธิภาพ
เพื่อให้แน่ใจว่าระบบทำงานได้ถูกต้อง ควรทดสอบในสถานการณ์ต่าง ๆ:
- การวัดระยะทางคงที่: วางเซนเซอร์ห่างจากวัตถุในระยะคงที่ และบันทึกค่าความแปรปรวน
- การวัดระยะทางที่เปลี่ยนแปลงช้า ๆ: เคลื่อนวัตถุเข้าออกอย่างช้า ๆ และตรวจสอบว่าค่าเปลี่ยนแปลงอย่างราบรื่น
- การวัดระยะทางที่เปลี่ยนแปลงอย่างรวดเร็ว: เคลื่อนวัตถุเข้าออกอย่างรวดเร็ว และตรวจสอบว่าระบบสามารถติดตามการเปลี่ยนแปลงได้
6. ไอเดียการประยุกต์ใช้
- หุ่นยนต์หลบสิ่งกีดขวาง: ใช้เครื่องวัดระยะทางนี้เป็น "ตา" ให้หุ่นยนต์ เพื่อหลบหลีกสิ่งกีดขวาง
- ระบบเตือนภัย: ตรวจจับการเคลื่อนไหว หรือการบุกรุก
- เครื่องวัดระดับน้ำ: วัดระดับน้ำในถัง หรือแหล่งน้ำ
- ระบบจอดรถอัจฉริยะ: ช่วยกะระยะ เมื่อจอดรถ
- Interactive Art: สร้างงานศิลปะที่ตอบสนองต่อระยะห่าง
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 ที่สามารถนำไปประยุกต์ใช้กับโปรเจกต์อื่นๆ ได้อีกมากมาย

ก้าวต่อไป:
- ลองนำไปใช้จริง: ติดตั้งเครื่องวัดระยะทางนี้บนหุ่นยนต์ หรือในระบบ IoT ของคุณ
- พัฒนาต่อยอด: เพิ่มฟังก์ชันอื่นๆ เช่น การบันทึกข้อมูล, การส่งข้อมูลผ่าน Wi-Fi/Bluetooth, การ Calibrate อัตโนมัติ
- แบ่งปัน: แชร์ผลงานของคุณ และแลกเปลี่ยนความรู้กับผู้อื่น! #ArduinoDistanceProject
แหล่งข้อมูลเพิ่มเติม:
- HC-SR04 Datasheet: https://components101.com/ultrasonic-sensor-working-pinout-datasheet
- Exponential Moving Average: https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
- Arduino-Filters Library: https://github.com/tttapa/Arduino-Filters
หวังว่าบทความนี้จะเป็นประโยชน์ และช่วยให้คุณสร้างสรรค์โปรเจกต์เจ๋งๆ ได้นะครับ!