Easy-to-Understand Learning for Fuzzy Logic Controller with Arduino UNO [LEP]

บทความนี้เป็นตอนที่ 2 ของการเรียนรู้การควบคุมแบบฟัซซีลอจิก (Fuzzy Logic Control หรือ FLC) โดยใช้บอร์ด Arduino UNO ซึ่งในตอนที่ 2 นี้จะมีความแตกต่างจากตอนที่ 1 (บทความตอนที่ 1 คลิกที่นี่) ในเรื่องของการกำหนดฟังก์ชันความเป็นสมาชิก (Membership Functions) และค่าผลลัพธ์เอาพุต (Defuzzification) รวมทั้งสามารถกำหนดค่าความเร็วมอเตอร์ (Setpoint) ให้ทำงานได้ตามที่ต้องการ โดยในตอนนี้จะเป็นตัวอย่างที่จะช่วยให้เรานำไปใช้งานได้ง่ายและนำไปประยุกต์ใช้งานได้หลากหลายยิ่งขึ้น

รูปที่ 1 กราฟฟังก์ชันความเป็นสมาชิก

รายละเอียดของแต่ละฟังก์ชัน

  • Negative (N) – สีแดง/เส้นซ้าย เป็นกราฟรูปสี่เหลี่ยมคางหมูด้านซ้าย (Left-Trapezoidal)
    • ถ้า Error น้อยกว่าหรือเท่ากับ -1.0 ค่าความเข้ากันได้จะเป็น 1.0 เต็ม (Error ติดลบมาก)
    • จากนั้นจะค่อยๆ ลาดเอียงลงเป็นเส้นตรงจนเหลือ 0.0 ที่จุด Error = 0.0
  • Zero (Z) – สีเขียว/เส้นตรงกลาง
    • เป็นกราฟรูปสามเหลี่ยมสมมาตร (Symmetrical Triangular) อยู่ตรงกลาง
    • มีจุดยอดสูงสุดอยู่ที่ Error = 0.0 (ค่าความเข้ากันได้เป็น 1.0)
    • ลาดเอียงลงทั้งสองข้างจนกลายเป็น 0.0 ที่จุด Error = -1.0 และ 1.0
  • Positive (P) – สีน้ำเงิน/เส้นขวา
    • เป็นกราฟรูปสี่เหลี่ยมคางหมูด้านขวา (Right-Trapezoidal)
    • เริ่มจาก 0.0 ที่จุด Error = 0.0 แล้วค่อยๆ ลาดเอียงขึ้นเป็นเส้นตรงจนถึง 1.0 ที่จุด Error = 1.0
    • ถ้า Error มากกว่าหรือเท่ากับ 1.0 ค่าความเข้ากันได้จะคงที่อยู่ที่ 1.0 (Error เป็นบวกมาก)

การวิเคราะห์ตรรกะกำหนดค่าอินพุตและเอาต์พุตทางภาษา 3 สถานะ

  • Negative (N) ค่าความคลาดเคลื่อน (Error) คือ -1 (มอเตอร์หมุนเร็วเกินไป —> ลดความเร็วลง)
  • Zero (Z) ค่าความคลาดเคลื่อนคือ 0 (มอเตอร์หมุนที่ความเร็วเป้าหมาย —> คงค่า PWM ไว้เท่าเดิม)
  • Positive (P) ค่าความคลาดเคลื่อนคือ 1 (มอเตอร์หมุนช้าเกินไป —> เพิ่มความเร็วขึ้น)

// Pin Definitions
const int motorPWM = 9;   // PWM pin to motor driver (e.g., L298N ENA)
const int motorIN1 = 8;   // Control pin 1
const int motorIN2 = 7;   // Control pin 2

// System Variables
float targetSpeed = 100.0; // Desired speed (e.g., in RPM or sensor ticks)
float currentSpeed = 0.0;  // Measured speed from your sensor
float currentPWM = 127.0;  // Start at mid-range (0-255)

// Fuzzy Membership Functions for Input (Error)
// Error = Target - Current. Range normalized roughly between -1.0 and 1.0
float fuzzyInputN(float e) {
  if (e <= -1.0) return 1.0;
  if (e < 0.0)   return -e;
  return 0.0;
}

float fuzzyInputZ(float e) {
  if (e <= -1.0 || e >= 1.0) return 0.0;
  if (e < 0.0) return e + 1.0;
  return 1.0 - e;
}

float fuzzyInputP(float e) {
  if (e >= 1.0) return 1.0;
  if (e > 0.0)  return e;
  return 0.0;
}

// Defuzzification using Center of Gravity (Centroid Method)
// Output Singletons (Change in PWM): Negative Change (-30), No Change (0), Positive Change (30)
float calculateFuzzyOutput(float error) {
  // 1. Fuzzification
  float muN = fuzzyInputN(error);
  float muZ = fuzzyInputZ(error);
  float muP = fuzzyInputP(error);
  
  // 2. Rule Base & Inference (Mamdani)
  // Rule 1: IF Error is N THEN Change is Negative (-30)
  // Rule 2: IF Error is Z THEN Change is Zero (0)
  // Rule 3: IF Error is P THEN Change is Positive (30)
  float outN = -30.0;
  float outZ = 0.0;
  float outP = 30.0;
  
  // 3. Defuzzification (Centroid / Weighted Average for singletons)
  float numerator = (muN * outN) + (muZ * outZ) + (muP * outP);
  float denominator = muN + muZ + muP;
  
  if (denominator == 0) return 0; // Avoid division by zero
  return numerator / denominator;
}

void setup() {
  pinMode(motorPWM, OUTPUT);
  pinMode(motorIN1, OUTPUT);
  pinMode(motorIN2, OUTPUT);
  
  // Set motor direction forward
  digitalWrite(motorIN1, HIGH);
  digitalWrite(motorIN2, LOW);
  
  Serial.begin(9600);
}

void loop() {
  // 1. Read your speed sensor here (Placeholder simulation value)
  // In reality, calculate currentSpeed from an encoder or analog read
  currentSpeed = analogRead(A0) * (200.0 / 1023.0); 

  // 2. Calculate Normalized Error
  float error = targetSpeed - currentSpeed;
  
  // Constrain error to our fuzzy universe limits [-1, 1]
  // Adjust this scaling factor based on your real-world sensor readings
  float scaledError = constrain(error / 50.0, -1.0, 1.0); 

  // 3. Compute Fuzzy Adjustment
  float pwmChange = calculateFuzzyOutput(scaledError);
  
  // 4. Apply Adjustment to PWM
  currentPWM += pwmChange;
  currentPWM = constrain(currentPWM, 0, 255); // Keep within hardware limits
  
  analogWrite(motorPWM, (int)currentPWM);

  // Debugging
  Serial.print("Target: "); Serial.print(targetSpeed);
  Serial.print(" | Current: "); Serial.print(currentSpeed);
  Serial.print(" | Error: "); Serial.print(scaledError);
  Serial.print(" | New PWM: "); Serial.println(currentPWM);
  
  delay(100); // Sampling time
}

หลักการทำงานของโปรแกรม Arduino

จากโปรแกรม Arduino ที่แสดงข้างบนจะเห็นว่า ฟังก์ชันการเป็นสมาชิก (Membership Functions) ของตัวแปรอินพุต Error (e) ที่ถูกจัดกลุ่ม (Normalize) ให้อยู่ในช่วง [-1.0, 0, 1.0] จะประกอบไปด้วย 3 ฟังก์ชันหลัก ได้แก่ Negative (N), Zero (Z) และ Positive (P) ดังรูปกราฟจำลองของฟังก์ชันทั้งสาม (ในรูปที่ 1) สำหรับรายละเอียดหลักการทำงานของโปรแกรมสามารถอธิบายเพิ่มเติมเพื่อให้เข้าใจได้ง่ายคือ


1. การปรับสเกลค่าความคลาดเคลื่อน (Scaling the Error) เนื่องจากกฎแบบฟัซซี่ (fuzzy rules) จะตรวจสอบค่า -1, 0 และ 1 จากนั้นคำสั่ง constrain(error / 50.0, -1.0, 1.0) จะทำหน้าที่ปรับค่าให้อยู่ในเกณฑ์มาตรฐาน (normalization) หากค่าเป้าหมาย (Setpoint) คือ 100 RPM และมอเตอร์ทำงานอยู่ที่ 50 RPM ค่าความคลาดเคลื่อนดิบ (raw error) จะเท่ากับ +50 ซึ่งเมื่อหารด้วย 50 แล้ว ค่าจะถูกแปลงเป็น 1.0 พอดี (ถือเป็นค่าความคลาดเคลื่อนทางบวกสูงสุด)

2. ฟังก์ชันสมาชิกรูปสามเหลี่ยม (Triangular Memberships) ฟังก์ชัน fuzzyInputN, fuzzyInputZ และ fuzzyInputP จะสร้างรูปสามเหลี่ยมที่ซ้อนทับกัน หากค่าความคลาดเคลื่อน (Error) มีค่าเท่ากับ 0.5 ที่ตำแหน่ง Zero (ศูนย์) และ Positive (บวก) หมายถึง ระดับความแรงเท่ากับครึ่งหนึ่งพร้อมกัน

3. ค่า Singleton (outN, outZ, outP) จะใช้ค่าน้ำหนักที่กำหนดไว้คงที่ (เช่น -30 เพื่อลดความเร็ว และ +30 เพื่อเพิ่มความเร็ว) จากนั้นกระบวนการ defuzzification จะทำการรวมค่าน้ำหนักที่ได้ โดยขึ้นอยู่กับระดับค่าความคลาดเคลื่อน (Error) ที่เกิดขึ้น

ค่า Error (e)ระดับความเป็นสมาชิก N (muN)ระดับความเป็นสมาชิก Z (muZ)ระดับความเป็นสมาชิก P (muP)คำอธิบายสถานะระบบ
-1.0 หรือน้อยกว่า1.00.00.0มอเตอร์หมุนเร็วเกินไปมาก (Error ติดลบเยอะ)
-0.50.50.50.0มอเตอร์เร็วเกินไปเล็กน้อย
0.00.01.00.0ความเร็วได้ตามเป้าหมายพอดี (Perfect)
0.50.00.50.5มอเตอร์ช้าเกินไปเล็กน้อย
1.0 หรือมากกว่า0.00.01.0มอเตอร์ช้ากว่าเป้าหมายมาก (Error เป็นบวกเยอะ)

การคำนวณหาค่าผลลัพธ์การทำงานทีละขั้นตอน เมื่ออ่านค่าผิดพลาด (Error) ได้เท่ากับ 0.5

1. Fuzzification Stage

ระบบวัดค่าความผิดพลาด (Error) อินพุตได้เท่ากับ 0.5 และเมื่อพิจารณาตำแหน่งที่ได้ในรูปที่ 1 จะอยู่กึ่งกลางระหว่างฟังก์ชันความเป็นสมาชิกของ Zero (Z) และ Positive (P) จากนั้นคำนวณหาความเป็นสมาชิก คือ

  • Negative (N) Membership: 0.0 (ค่าผิดพลาด ไม่อยู่ในช่วงที่กำหนด)
  • Zero (Z) Membership: (1 – 0.5)/(1 – 0) = 0.5/1.0 = 0.5
  • Positive (P)  Membership: (1 – 0.5)/(1 – 0) = 0.5/1.0 = 0.5

2. Defuzzification Math

นำค่าความเป็นสมาชิกมาคำนวณด้วยสมการ เพื่อหาค่าเฉลี่ยแบบถ่วงน้ำหนัก (Weighted Average) โดยกำหนดค่า Singleton ดังนี้

outN = -30
outZ = 0
outP = 30

motorPWM=​(μNegative​×outN)+(μZero​×outZ)+(μPositive​×outP)​/(μNegative​+μZero​​+μPositive)​

motorPWM= (0​×-30)+(0.5​×0)+(0.5×30)​/(0.5​+0.5)​ = 15/1 = 15

3. Final Controller Output

ผลลัพธ์ที่ได้จากการคำนวณ Defuzzification Math จะส่งออกไปชดเชยในการขับมอเตอร์ (PWM Signal) ที่มีค่าเดิมอยู่ โดยค่าผลลัพธ์ที่ได้จากการคำนวณ จะแบ่งออกเป็น 3 กรณีคือ กรณีที่ 1 เมื่อค่า targetSpeed > currentSpeed ค่าผลลัพธ์ชดเชยในการขับมอเตอร์จะเป็นค่าบวก กรณีที่ 2 เมื่อค่า targetSpeed = currentSpeed ค่าผลลัพธ์ชดเชยในการขับมอเตอร์จะมีค่าเท่ากับ 0 และกรณีที่ 3 เมื่อค่า targetSpeed < currentSpeed ค่าผลลัพธ์ชดเชยในการขับมอเตอร์จะมีค่าเป็นค่าลบ ซึ่งการปรับระดับความเร็วจะเป็นไปอย่างนุ่มนวลที่ถูกต้อง

การเรียนรู้การควบคุมแบบฟัซซีลอจิก (Fuzzy Logic Control) โดยใช้บอร์ด Arduino UNO และที่สามารถกำหนดค่าความเร็วมอเตอร์ (Setpoint) ได้ตามที่ต้องการ
รูปที่ 2 ลักษณะวงจรสำหรับจำลองการทำงานในเว็บ www.tinkercad.com

ในรูปที่ 2 แสดงลักษณะของวงจรสำหรับจำลองการทำงานในเว็บ www.tinkercad.com โดยใช้โปรแกรม Arduino ข้างบนในการทดลอง สำหรับในส่วนของสัญญาณอินพุตวัดความเร็วรอบมอเตอร์จะต่อเข้าที่ขา A0 โดยจะจำลองใช้ตัวต้านทานปรับค่า (VR) แทนซึ่งสามารถปรับค่าได้ในช่วง 0-200 รอบต่อนาที (RPM) และมีโวลต์มิเตอร์แสดงผลให้ทราบใกล้กัน ในส่วนสัญญาณเอาต์พุต PWM จะจ่ายออกมาที่ขา D9 ให้ทรานซิสเตอร์เพื่อขับมอเตอร์ และใช้ออสซิลโลสโคปวัดสัญญาณให้เห็นการเปลี่ยนแปลงได้ง่ายยิ่งขึ้น

Easy-to-Understand Learning for Fuzzy Logic Controller with Arduino UNO
รูปที่ 3 ผลการทดลองที่ 1 เมื่อกำหนดให้ targetSpeed > currentSpeed
Easy-to-Understand Learning for Fuzzy Logic Controller with Arduino UNO
รูปที่ 4 ผลการทดลองที่ 2 เมื่อกำหนดให้ targetSpeed < currentSpeed

สำหรับในรูปที่ 3 และรูปที่ 4 เป็นผลการทดลองเมื่อปรับค่า currentSpeed ให้มีค่ามากกว่าและน้อยกว่า targetSpeed โดยในกรณีที่ปรับค่า currentSpeed น้อยกว่า targetSpeed ในกรณีนี้ระบบจะปรับชดเชยการขับมอเตอร์เพิ่มขึ้น (รูปที่ 3) เพื่อให้ความเร็วมอเตอร์มีค่าเท่ากับ targetSpeed (เพิ่มค่า motorPWM ด้วยค่าที่เป็นบวก) ซึ่งจะสังเกตความกว้างของสัญญาณ PWM ที่จอออสซิลโลสโคปและข้อมูลที่แสดงในกรอบสีส้ม สำหรับในกรณีที่ currentSpeed เท่ากับ targetSpeed ระบบจะไม่ปรับชดเชยการทำงานใดๆ โดยจะคงค่า motorPWM เดิมที่เกิดขึ้นไว้

ในทางกลับกันเมื่อปรับค่า currentSpeed มากกว่า targetSpeed ในกรณีนี้ระบบจะปรับชดเชยการขับมอเตอร์ลดลง (รูปที่ 4) เพื่อให้ความเร็วมอเตอร์มีค่าเท่ากับ targetSpeed (ลดค่า motorPWM ด้วยค่าที่เป็นลบ) ซึ่งจะสังเกตความกว้างของสัญญาณ PWM ที่จอออสซิลโลสโคปและข้อมูลที่แสดงในกรอบสีส้ม สำหรับเนื้อหาในบทความตอนสุดท้ายนี้ เป็นอีกรูปแบบหนึ่งของการควบคุม โดยกำหนดค่าที่ต้องการได้ (Setpoint) ซึ่งเราสามารถปรับเปลี่ยนโปรแกรม Arduino สำหรับไปใช้งานได้ง่ายและนำไปประยุกต์ใช้งานได้หลายหลายขึ้น ท้ายนี้หวังว่าบทความจะช่วยให้ผู้อ่านเข้าใจการควบคุมแบบฟัซซี่ลอจิกเพิ่มขึ้นครับ.

Reference

  1. https://en.wikipedia.org/wiki/Fuzzy_control_system
  2. https://forum.arduino.cc/t/how-make-fuzzy-logic-coding-for-arduino-uno/282381/4
  3. https://github.com/alvesoaj/eFLL
  4. https://github.com/amimaro/FuzzyLibrary
  5. https://fuzzylite.com/
  6. https://fuzzylite.com/downloads/FuzzyLite-Libraries.pdf
  7. https://www.microjpm.com/products/ad52300/
  8. https://www.tinkercad.com/dashboard
  9. https://www.circuitbasics.com/arduino-thermistor-temperature-sensor-tutorial/
  10. https://gemini.google.com/
  11. https://copilot.microsoft.com