โปรเจกต์ A.I.M.S: สร้างระบบติดตามและปรับน้ำเกลืออัตโนมัติด้วย Arduino

A.I.M.S Project Hero

บทความนี้จะพาเพื่อนๆ มาดูไอเดียการใช้บอร์ดไมโครคอนโทรลเลอร์ยอดฮิตอย่าง Arduino Nano คู่กับเซนเซอร์อีกนิดหน่อย เพื่อสร้าง ระบบมอนิเตอร์การให้เสาน้ำเกลือ (IV Infusion Monitoring System) แบบพื้นฐานกันครับ แถมเรายังเอาข้อมูลจากเซนเซอร์มาต่อยอดสร้างกลไก (Actuator) สำหรับ "ปรับอัตราการหยดของน้ำเกลือ" แบบอัตโนมัติได้อีกด้วย!

(หมายเหตุ: ระบบปรับปริมาณน้ำเกลืออัตโนมัตินี้เป็นเพียงเครื่องต้นแบบ (Prototype) ที่ทำขึ้นเพื่อการศึกษาเท่านั้น ยังไม่ผ่านการทดสอบทางการแพทย์เพื่อใช้งานจริงกับผู้ป่วยนะครับ)

อุปกรณ์ที่ต้องใช้ (Supplies)

ชิ้นส่วนหลักๆ สำหรับโปรเจกต์นี้ไม่ได้หายากเลยครับ (แอบกระซิบว่าถ้าเพื่อนๆ กำลังหาบอร์ด Arduino, เซนเซอร์แปลกๆ หรือเส้นพลาสติก 3D Print คุณภาพดีสำหรับทำโปรเจกต์ สามารถเข้าไปช้อปได้ที่ Globalbyte เลยครับ ครบจบในที่เดียว!)

  • LM393 Optocoupler Speed Module: โมดูลนับความเร็ว (ใช้นับหยดน้ำเกลือ)
  • XKC-Y25-NPN: เซนเซอร์ตรวจจับของเหลวแบบไร้การสัมผัส (Non-contact fluid sensor)
  • Arduino Nano: (หรือไมโครคอนโทรลเลอร์ตัวไหนก็ได้ที่ใช้ชิป ATmega328p)
  • Piezo Buzzer: ลำโพงบัซเซอร์เตือนเสียง
  • LED (สีแดง): ไฟแสดงสถานะ

ส่วนอุปกรณ์ด้านล่างนี้เป็น "ออปชันเสริม" หากคุณต้องการทำระบบบีบสายน้ำเกลือเพื่อปรับอัตราการหยดอัตโนมัติ และทำเคส 3D ครับ:

  • เซอร์โวมอเตอร์ MG996R Servo
  • สวิตช์แบบหมุน (Rotary Switch) 6 ตำแหน่ง
  • เส้นพลาสติก 3D Print (แนะนำเป็นวัสดุ PETG เพื่อความเหนียวและทนทาน)
  • แผ่นอะคริลิกหนา 3mm (ออปชันเสริม สามารถปริ้นท์ 3D แทนได้)
  • น็อต M4*15 จำนวน 4 ตัว, สายแพ Jumper, แผ่นวงจร PCB อเนกประสงค์ และสายรัดตีนตุ๊กแก (Velcro)
Supplies Image 1
Supplies Image 2 Supplies Image 3 Supplies Image 4 Supplies Image 5 Supplies Image 6

Step 1: ออกแบบกล่องควบคุมหลัก (Main Controller Housing)

ก่อนจะจับทุกอย่างยัดรวมกัน เราต้องมีกล่อง (Housing) สำหรับใส่ตัวควบคุมหลัก, สวิตช์หมุน และช่องร้อยสายไฟไปยังเซนเซอร์ต่างๆ ก่อนครับ

ในดีไซน์นี้วาดด้วย Fusion 360 โดยทำเป็นกล่องง่ายๆ มีคลิปล็อคด้านหลังแบบ Snap-on เอาไว้หนีบเกาะกับเสาน้ำเกลือได้เลย เมื่อวาดเสร็จก็นำไฟล์ .stl ไปตั้งค่าในโปรแกรม Slicer (เช่น Prusa Slicer) ได้เลยครับ

💡 ทริคสำคัญตอนปริ้นท์ 3D: พยายามจัดวางชิ้นงานให้ ลายเส้นเลเยอร์ (Layer lines) ขนานตั้งฉากกับเสาน้ำเกลือ ครับ การทำแบบนี้จะทำให้ตัวหนีบ (Snap-on clip) หันหน้ารับแรงดึงได้ดีขึ้น ช่วยลบจุดอ่อนที่มักจะหักตามรอยต่อเลเยอร์ของการปริ้นท์ 3D นั่นเอง

Main Housing
Main Housing CAD

Step 2: ออกแบบตัวยึดเซนเซอร์และกระเปาะน้ำเกลือ (Optocoupler Housing)

กระเปาะน้ำเกลือ (Drip chamber) ของแต่ละโรงพยาบาลมักจะมีรูปทรงและขนาดไม่เท่ากันครับ ดังนั้นทางที่ดีเราควรออกแบบตัวยึดให้พอดีกับสเปกกระเปาะที่เรามี เป้าหมายคือการล็อคตำแหน่งของ IR Optocoupler ไว้ตรงกลาง เพื่อให้ทุกครั้งที่หยดน้ำเกลือตกลงมา มันจะไปบังหรือหักเหแสงอินฟราเรด ทำให้เซนเซอร์จับสัญญาณไปให้บอร์ดคำนวณได้ครับ

💡 สิ่งที่ต้องทำก่อนประกอบ: แกะพลาสติกสีดำรูปตัว U ที่ครอบโมดูล Optocoupler อยู่ออกก่อนนะครับ เพื่อให้หลอด LED อินฟราเรดกับตัวรับแสงเปิดโล่ง จะได้จัดตำแหน่งให้ลำแสงยิงผ่านหยดน้ำเกลือได้ง่ายขึ้น

แนะนำให้ลองทดสอบระดับความสูงของเซนเซอร์ดูครับ สำหรับโปรเจกต์นี้ ผมพบว่าวางเซนเซอร์ไว้ "ครึ่งล่าง" ของกระเปาะจะจับสัญญาณได้เสถียรกว่า แต่บางครั้งถ้ากระเปาะแบบอื่น การวางไว้สูงๆ ใกล้ปากทางออกที่หยดน้ำยังมีขนาดใหญ่อยู่ อาจจะจับสัญญาณได้ดีกว่าครับ

Optocoupler Housing
Optocoupler Close-up

Step 3: ออกแบบตัวหนีบสายน้ำเกลือ (Clamp Body) - ออปชันเสริม

ข้ามขั้นตอนนี้ได้เลยถ้าคุณต้องการแค่ "มอนิเตอร์" อย่างเดียว แต่ถ้าอยากให้ระบบมันปรับอัตราการหยดได้ด้วย เราต้องออกแบบตัวหนีบสายน้ำเกลือครับ โดยสร้างบล็อกทรงกลมเจาะรูตรงกลางสำหรับใส่ เซอร์โวมอเตอร์ (Servo) แล้วใช้แขนของเซอร์โว (Servo horn) บีบสายน้ำเกลือเพื่อคุมอัตราการไหลครับ

ส่วนแผ่นใสๆ ด้านบนคืออะคริลิก มีไว้กันสายน้ำเกลือหลุดออกมา (สามารถปริ้นท์ 3D ปิดทับไปเลยก็ได้) แต่การใช้อะคริลิกใสจะช่วยให้เรามองเห็นว่าเซอร์โวมันหนีบสายแรงไปหรือเบาไปไหมครับ

Clamp Body
Clamp Body View 2

Step 4: ชิ้นส่วนอะคริลิกและตัวยึดอื่นๆ

ฮาร์ดแวร์ส่วนที่เหลือก็จะเป็นพวกขายึดธรรมดาๆ ที่มีตัวหนีบ (Snap-on) ไว้ติดกับเสาน้ำเกลือครับ ซึ่งอย่างที่บอกไป ว่าต้องสั่งให้เลเยอร์การปริ้นท์ตั้งฉากกับเสาเสมอเพื่อความแข็งแรง

ถ้าไม่อยากใช้อะคริลิกเลย คุณสามารถปริ้นท์ 3D แทนได้ทุกชิ้น 100% ครับ แต่ที่ผมใช้อะคริลิกในบางจุด ก็เพราะเราสามารถเอาไปเข้าเครื่องเลเซอร์เพื่อยิงสลักชื่อหรือโลโก้เท่ๆ ลงไปได้นั่นเอง

Mounts
Acrylic Pieces Laser Etching

Step 5: การบัดกรีฮาร์ดแวร์และต่อวงจร (Soldering & Schematic)

มาถึงฝั่งอิเล็กทรอนิกส์กันบ้าง! เนื่องจากอุปกรณ์ส่วนใหญ่ของเรามาเป็นแบบโมดูลพร้อมเสียบอยู่แล้ว เราก็แค่หาแผ่น PCB อเนกประสงค์มาเป็นฐานรวมสายไฟทั้งหมดเพื่อต่อเข้ากับ Arduino Nano ครับ

คุณสามารถดูแผนผังวงจร (Schematic) และการจัดเลย์เอาต์บอร์ดจาก EasyEDA ได้ในรูปเลย หรือถ้าใครอยากลองทำใน Breadboard ไปก่อนเพื่อประหยัดเวลาก็ทำได้เช่นกันครับ จากนั้นก็จับหัวแร้งบัดกรีสายไฟ คอนเนคเตอร์ และพิน Header สำหรับเสียบ Arduino Nano ให้เรียบร้อย

Schematic
PCB Layout 1 PCB Layout 2

Step 6: การประกอบและการเขียนโค้ด (Assembly & Coding)

Final Assembly

ประกอบทุกอย่างเข้าด้วยกันได้เลยครับ! แนะนำให้เสียบคอนเนคเตอร์ต่างๆ เข้ากับกล่องหลักให้เสร็จก่อน แล้วค่อยลากสายยาวๆ ไปที่เซนเซอร์ สำหรับเซนเซอร์ XKC (วัดระดับน้ำเกลือหมด) ให้ใช้สายตีนตุ๊กแก (Velcro) รัดติดกับถุงน้ำเกลือให้แน่นๆ เพราะถ้ามันหลุดขยับ มันอาจจะส่งสัญญาณมั่วว่า "น้ำเกลือหมดแล้ว" ได้ครับ

ก่อนที่เราจะก๊อปโค้ดไปวาง มาทำความเข้าใจลอจิกเบื้องหลังกันสักนิดครับ:

  • การนับอัตราการหยด (Optocoupler): เราใช้เซนเซอร์ IR จับเวลาที่หยดน้ำเกลือแต่ละหยดตกลงมาตัดลำแสง โดยใช้ฟังก์ชัน Micros() ของ Arduino จับเวลาหยดแรกและหยดที่สอง นำมาลบกันเพื่อหาความห่าง (Interval) จากนั้นนำไปคำนวณประเมินว่า 1 นาทีจะหยดกี่หยด กลายเป็นค่า DPM (Drops Per Minute) นั่นเอง
  • การตรวจจับน้ำเกลือหมด (XKC-Y25): เซนเซอร์ตัวนี้วัดระดับของเหลวแบบไร้การสัมผัส โดยอาศัยหลักการ ค่าความนำไฟฟ้า (Dielectric constants) ที่ต่างกันระหว่างอากาศกับน้ำเกลือ ทำให้ค่าความจุไฟฟ้า (Capacitance) และแรงดันเปลี่ยนไป เราจึงใช้เซนเซอร์ตัวนี้เตือนเมื่อระดับน้ำเกลือต่ำกว่าเส้นที่กำหนดได้ครับ
  • ระบบปรับอัตราการหยดอัตโนมัติด้วย PI Controller (ออปชันเสริม): หากค่า DPM ที่ตั้งไว้เพี้ยนไป ระบบจะใช้สมการ PI Controller คอยส่งสัญญาณไปขยับเซอร์โวมอเตอร์เพื่อเพิ่มหรือลดแรงบีบสายน้ำเกลือทีละนิด เพื่อดึงให้กลับมาตรงกับค่าที่เราตั้งไว้ครับ (ใครอยากเข้าใจสมการ PI ลึกๆ อ่านเพิ่มเติมได้ที่นี่)

เมื่อเข้าใจแล้ว ก๊อปปี้โค้ดฉบับเต็มด้านล่างนี้ไปอัปโหลดลงบอร์ด Arduino Nano (ผ่าน Arduino IDE หรือ PlatformIO) ได้เลย! โค้ดนี้มีอยู่ในไฟล์ main.cpp ด้วยครับ

C/C++ (Arduino)
#include <Servo.h>

// Pin Definitions
const int OPTO_PIN   = 4;
const int SENSOR_PIN = 5;
const int BUZZER_PIN = 2;
const int LED_PIN    = 3;
const int SERVO_PIN  = 9;   // Servo signal

// Rotary switch pins 
const int DPM1_PIN = A0;
const int DPM2_PIN = A1;
const int DPM3_PIN = A2;
const int DPM4_PIN = A3;
const int DPM5_PIN = A4;
const int DPM6_PIN = A5;

// Drip Rate Settings
const uint16_t DPM_VALUES[6] = {7, 14, 20, 28, 40, 60};

// Servo Settings (you may need to adjust these based on your own servo)
const int SERVO_MIN    = 1099;
const int SERVO_MAX    = 1800;
const int SERVO_CENTER = 1250;

// PI Settings
const float KP = 1.5;
const float KI = 0.01;

const unsigned long CONTROL_INTERVAL_MS = 10;
const int DPM_DEADBAND = 2;
const int SERVO_STEP_LIMIT = 50;
const float INTEGRAL_LIMIT = 80.0;

// Global Variables
Servo clampServo;

uint16_t selected_dpm = 0;
uint16_t prev_selected_dpm = 0;
uint16_t current_dpm = 0;
uint16_t last_dpm = 0;

int servo_pos = SERVO_CENTER;
float integral = 0.0;

// Optocoupler Settings
bool last_opto_state = HIGH;
unsigned long last_capture_us = 0;

// Timers 
unsigned long last_control_time = 0;
unsigned long last_debug_time = 0;

// Read selected DPM from rotary switch (using built-in pull up resistors)
uint16_t readSelectedDPM() {
  if (digitalRead(DPM1_PIN) == LOW) return DPM_VALUES[5];
  if (digitalRead(DPM2_PIN) == LOW) return DPM_VALUES[4];
  if (digitalRead(DPM3_PIN) == LOW) return DPM_VALUES[3];
  if (digitalRead(DPM4_PIN) == LOW) return DPM_VALUES[2];
  if (digitalRead(DPM5_PIN) == LOW) return DPM_VALUES[1];
  if (digitalRead(DPM6_PIN) == LOW) return DPM_VALUES[0];
  return 0;
}

// Convert interval between drops to DPM
uint16_t computeDPM(unsigned long interval_us) {
  if (interval_us == 0) return 0;
  // DPM = 60,000,000 us / interval_us
  unsigned long dpm = 60000000UL / interval_us;
  if (dpm > 65535UL) dpm = 65535UL; //overflow cap
  return (uint16_t)dpm;
}

// Approximate feed-forward servo position based on target DPM
int dpmToServo(uint16_t dpm) {
  if (dpm == 0) return SERVO_MIN;
  long range = SERVO_MAX - SERVO_MIN;
  int pos = SERVO_MIN + (range * (long)(dpm - 7)) / (60 - 7);
  if (pos < SERVO_MIN) pos = SERVO_MIN;
  if (pos > SERVO_MAX) pos = SERVO_MAX;
  return pos;
}

// Setting Servo Safe Limits
void servoSetUs(int us) {
  if (us < SERVO_MIN) us = SERVO_MIN;
  if (us > SERVO_MAX) us = SERVO_MAX;
  clampServo.writeMicroseconds(us);
}

// PI controller
void piUpdate(uint16_t setpoint, uint16_t measured) {
  int error = (int)setpoint - (int)measured;
  
  // Discard Spikes / Noise
  if (measured > 120) {
    integral = 0;
    return;
  }

  // Deadband to ignore very small errors
  if (abs(error) < DPM_DEADBAND) {
    return;
  }

  float dt = CONTROL_INTERVAL_MS / 1000.0;
  float new_integral = integral + error * dt;

  // Integral clamp
  if (new_integral > INTEGRAL_LIMIT)  new_integral = INTEGRAL_LIMIT;
  if (new_integral < -INTEGRAL_LIMIT) new_integral = -INTEGRAL_LIMIT;

  float output = KP * error + KI * new_integral;
  int delta = (int)output;

  // Limit how much servo can move each update
  if (delta > SERVO_STEP_LIMIT)  delta = SERVO_STEP_LIMIT;
  if (delta < -SERVO_STEP_LIMIT) delta = -SERVO_STEP_LIMIT;

  int new_servo = servo_pos + delta;

  // Clamp servo range and prevent windup
  if (new_servo > SERVO_MAX) {
    new_servo = SERVO_MAX;
    integral = 0;
  } else if (new_servo < SERVO_MIN) {
    new_servo = SERVO_MIN;
    integral = 0;
  } else {
    integral = new_integral;
  }

  servo_pos = new_servo;
  servoSetUs(servo_pos);
}

// Setup
void setup() {
  Serial.begin(9600);

  pinMode(OPTO_PIN, INPUT_PULLUP);
  pinMode(SENSOR_PIN, INPUT_PULLUP);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(LED_PIN, OUTPUT);

  pinMode(DPM1_PIN, INPUT_PULLUP);
  pinMode(DPM2_PIN, INPUT_PULLUP);
  pinMode(DPM3_PIN, INPUT_PULLUP);
  pinMode(DPM4_PIN, INPUT_PULLUP);
  pinMode(DPM5_PIN, INPUT_PULLUP);
  pinMode(DPM6_PIN, INPUT_PULLUP);

  clampServo.attach(SERVO_PIN);
  servoSetUs(SERVO_CENTER);

  digitalWrite(LED_PIN, HIGH);     // Device active
  digitalWrite(BUZZER_PIN, HIGH);  // Buzzer off if active LOW

  last_opto_state = digitalRead(OPTO_PIN);
}

// Main Loop
void loop() {
  // 1. Read rotary switch
  selected_dpm = readSelectedDPM();

  // 2. If setpoint changed, jump servo near estimated position
  if (selected_dpm != prev_selected_dpm) {
    servo_pos = dpmToServo(selected_dpm);
    servoSetUs(servo_pos);
    integral = 0;
    prev_selected_dpm = selected_dpm;
  }

  // 3. Read optocoupler and detect falling edge
  bool opto_state = digitalRead(OPTO_PIN);

  if (last_opto_state == HIGH && opto_state == LOW) {
    unsigned long now_us = micros();
    if (last_capture_us != 0) {
      unsigned long interval_us = now_us - last_capture_us;
      last_dpm = computeDPM(interval_us);
      current_dpm = last_dpm;
    }
    last_capture_us = now_us;
  }

  last_opto_state = opto_state;

  // 4. XKC sensor controls buzzer
  if (digitalRead(SENSOR_PIN) == LOW) {
    digitalWrite(BUZZER_PIN, LOW);   // buzzer ON
  } else {
    digitalWrite(BUZZER_PIN, HIGH);  // buzzer OFF
  }

  // 5. Run PI controller at fixed interval
  unsigned long now_ms = millis();
  if (now_ms - last_control_time >= CONTROL_INTERVAL_MS) {
    last_control_time = now_ms;
    piUpdate(selected_dpm, current_dpm);
  }

  // 6. Debug print every 200 ms
  if (now_ms - last_debug_time >= 200) {
    last_debug_time = now_ms;

    Serial.print("SP=");
    Serial.print(selected_dpm);
    Serial.print(" DPM=");
    Serial.print(current_dpm);
    Serial.print(" Servo=");
    Serial.println(servo_pos);
  }
}
Wiring Overview

Step 7: แนวทางการพัฒนาในอนาคต (Future Improvements)

เพื่อทำให้โปรเจกต์นี้ใช้งานในระดับจริงจังขึ้น (แบบไม่ต้องใช้ไม้หนีบเซอร์โวมาบีบสาย) ผมแนะนำให้เปลี่ยนไปใช้ Dosing pump หรือ Peristaltic pump (ปั๊มรีดท่อ) แทนครับ เพราะปั๊มพวกนี้ไม่ทำให้สายน้ำเกลือพับหรือผิดรูป ซึ่งเป็นวิธีเดียวกับที่เครื่องให้สารละลายทางการแพทย์ระดับโปรเค้าใช้กัน

นอกจากนี้ การอัปเกรดจากบอร์ด Arduino Nano ไปใช้ซีรีส์ ESP32 ก็เป็นเรื่องที่น่าสนใจมาก เพราะเราจะสามารถโยนข้อมูลเข้าสู่ระบบ IoT แจ้งเตือนเข้ามือถือ หรือเก็บ Data ดูย้อนหลังได้สบายๆ เลยครับ รวมถึงการเพิ่มเซนเซอร์ Optocoupler ซ้าย-ขวา เพื่อให้เช็คหยดน้ำแม่นยำขึ้นก็เป็นไอเดียที่เจ๋งไม่แพ้กัน

และนี่ก็คือโปรเจกต์ A.I.M.S ระบบมอนิเตอร์และปรับหยดน้ำเกลืออัตโนมัติด้วย Arduino ขอบคุณที่ติดตามอ่านครับ หวังว่าจะได้ไอเดียไปพัฒนาต่อยอดกันนะ!

อ้างอิงข้อมูลจาก: Globalbyteshop Blog

ต้นฉบับบทความโดย: AdrHiroshi | Original Link | ดาวน์โหลด PDF ต้นฉบับ

*คำเตือน: เนื้อหานี้เป็นการสรุปและเรียบเรียงจากบทความต้นฉบับภาษาอังกฤษ ข้อมูลฉบับภาษาไทยอาจมีความคลาดเคลื่อนบางประการจากการตีความหรือย่อเนื้อหา สามารถตรวจสอบเนื้อหาโดยละเอียดได้ที่ ต้นฉบับภาษาอังกฤษ และ โปรเจกต์นี้จัดทำขึ้นเพื่อการศึกษาเท่านั้น ไม่ได้ผ่านการรับรองมาตรฐานทางการแพทย์ ห้ามนำไปใช้งานจริงกับผู้ป่วยโดยเด็ดขาด

 

แท็ก


Blog posts

เข้าสู่ระบบ

ลืมรหัสผ่านใช่ไหม?

ยังไม่มีบัญชีใช่ไหม?
สร้างบัญชี