سیستم هوشمند کنترل و مانیتورینگ سرم با آردوینو
دنیای الکترونیک وقتی با تجهیزات پزشکی ترکیب میشه، پروژههای فوقالعاده کاربردی و جذابی خلق میکنه. تو این آموزش از آیسیمدار میخوایم یه سیستم هوشمند مانیتورینگ و کنترل سرم (IV) بسازیم که به کمک آردوینو کار میکنه. این دستگاه میتونه سرعت چکیدن قطرات رو تشخیص بده و اگه سرم در حال تموم شدن بود، سریع بهت هشدار بده.
بخش جذابتر ماجرا اینه که سیستم ما فقط یه مانیتور ساده نیست. با اضافه کردن یه سروو موتور، میتونیم سرعت تزریق سرم رو هم به صورت اتوماتیک کنترل کنیم. این یعنی یه قدم بزرگ برای ساخت تجهیزات دستساز که دقت بالایی دارن. قطعات اصلی این پروژه شامل برد آردوینو، یه سنسور نوری و یه سنسور تشخیص مایعات غیرتماسی میشه.
طراحی بدنهها رو با پرینتر سهبعدی انجام میدیم تا همهچیز کاملا حرفهای و منسجم روی پایه سرم نصب بشه. حالا وسایل و قطعاتت رو آماده کن تا بریم سراغ ساخت این پروژه خفن و کاربردی.
قطعات موردنیاز
- ماژول اپتوکوپلر و سنسور سرعت LM393
- سنسور تشخیص سطح مایعات غیرتماسی XKC-Y25-NPN
- برد آردوینو نانو (یا هر بردی با میکروکنترلر ATmega328p)
- بازر پیزو اکتیو (صدادار)
- الایدی (قرمز)
- سروو موتور MG996 اهم
- کلید سلکتوری یا روتاری سوئیچ (6 حالته)
- فیلامنت پرینتر سهبعدی (ترجیحا PETG)
- ورق پلکسی یا اکریلیک 3 میلیمتری (اختیاری)
- چهار عدد پیچ M4 با طول 15 میلیمتر
- سیم جامپر
- برد سوراخدار مدار چاپی (میتونی از بردبورد هم استفاده کنی)
- یک تکه چسب پارچهای دوطرفه (ولکرو)
طراحی محفظه کنترل کننده اصلی
اول از همه باید یه قاب محکم و تمیز برای مغز متفکر پروژهمون یعنی برد اصلی و قطعاتی مثل کلید سلکتوری بسازیم. این محفظه یه جعبه ساده است که پشتش یه گیره فشاری داره تا راحت روی پایه فلزی سرم قفل بشه. چند تا سوراخ هم برای عبور سیمهای سنسور و پورت USB روش در نظر گرفتیم.
فایلهای سهبعدی این قطعات آماده است و میتونی اونها رو تو نرمافزار اسلایسر دلخواهت باز کنی. این طراحی با نرمافزار Fusion انجام شده و برای مونتاژ سریع بهینهسازی شده است. فرقی نمیکنه پرینترت چی باشه، میتونی فایل STL رو به اسلایسری مثل PrusaSlicer بدی و پرینت رو شروع کنی.
فقط موقع اسلایس کردن حواست باشه که لایههای پرینت عمود بر جهت پایه سرم باشن. این ترفند ساده باعث میشه گیره پشتی مقاومت خیلی بیشتری داشته باشه و از بین لایهها نشکنه.
طراحی محفظه اپتوکوپلر و محفظه قطره گیر
محفظه چکه سرم تو برندهای مختلف شکلها و اندازههای متفاوتی داره، پس گیرهای که طراحی میکنی باید دقیقا فیت همون مدل باشه. اینجا یه طرح پایه داریم که میتونی بر اساس قطرهچکان خودت تغییرش بدی.
هدف ما اینه که سنسور اپتوکوپلر رو طوری دور محفظه قرار بدیم که هر قطرهای که میچکه، پرتو مادون قرمز رو قطع یا منحرف کنه. اینطوری یه سیگنال دقیق به میکروکنترلر فرستاده میشه تا قطرهها شمرده بشن.
قبل از اینکه سنسور رو جا بزنی، حتما اون قاب پلاستیکی مشکی رنگ (به شکل U) روی ماژول اپتوکوپلر رو جدا کن. با این کار فرستنده و گیرنده مادون قرمز کاملا آزاد میشن و خیلی راحتتر میتونی مسیر نور رو با مسیر چکیدن قطرهها تنظیم کنی.
پیشنهاد میکنم ارتفاع قرارگیری سنسور رو چند بار تست کنی. شکل و اندازه قطرهها تو قسمتهای مختلف محفظه فرق میکنه و پیدا کردن بهترین نقطه باعث میشه سنسور بدون هیچ خطایی کار کنه.
معمولا اگه سنسور رو تو نیمه پایینی محفظه نصب کنی، دقیقترین نتیجه رو میگیری؛ اما باز هم ممکنه بسته به نوع سرم، تو قسمتهای بالاتر قطرهها درشتتر باشن و سنسور بهتر عمل کنه.
طراحی بدنه گیره برای لوله گذاری داخل وریدی (اختیاری)
اگه فقط میخوای وضعیت سرم رو مانیتور کنی، میتونی از این مرحله رد بشی؛ اما اگه دلت میخواد یه سیستم کامل با قابلیت کنترل سرعت تزریق بسازی، باید این گیره مکانیکی رو هم پرینت کنی.
طراحی این بخش شامل یه بدنه استوانهایه که شیلنگ سرم از وسطش رد میشه و بازوی سروو موتور با فشار دادن شیلنگ، جلوی جریان مایع رو میگیره یا بازش میکنه.
برای روی این محفظه میتونی از یه تکه طلق اکریلیک شفاف استفاده کنی تا شیلنگ بیرون نپره و در عین حال بتونی داخلش رو ببینی. دیدن داخل گیره خیلی مهمه، چون بهت کمک میکنه تا متوجه بشی بازوی موتور چقدر به شیلنگ فشار میاره و آیا نیازی به کالیبره کردن داره یا نه.
طراحی قطعات اکریلیک و پایههای متفرقه
بقیه قطعاتی که تو این پروژه داریم، بیشتر شامل گیرههای نگهدارنده و درپوشهای روی محفظههاست. این قطعات هم گیرههای فشاری دارن تا خیلی سریع و راحت روی بدنه اصلی یا پایه سرم سوار بشن.
اینجا هم یادت نره که جهت پرینت رو طوری تنظیم کنی که لایهها عمود بر فشار باشن تا استحکام قطعات بالا بره و موقع جا زدن روی پایه نشکنن.
اگه به ورقهای اکریلیک یا دستگاه برش لیزر دسترسی نداری، اصلا جای نگرانی نیست. میتونی تمام این درپوشهای شفاف رو با همون پرینتر سهبعدی پرینت بگیری. البته استفاده از طلق شفاف یه زیبایی خاصی به پروژه میده و میتونی متنهای جذابی رو روش حکاکی کنی. برای برش دقیق طلقهای اکریلیک، میتونی از خدمات برش لیزر استفاده کنی تا قطعاتت بینقص دربیان.
لحیم کاری سخت افزار و طراحی شماتیک
حالا میرسیم به بخش شیرین الکترونیک! از اونجایی که بیشتر سنسورهای این پروژه به صورت ماژول آماده هستن، کارمون خیلی راحته و فقط باید سیمها رو به پینهای آردوینو نانو وصل کنیم.
شماتیک کامل مدار تو تصاویر این مرحله مشخصه و میتونی پینهای مربوط به هر قطعه رو ببینی. برای اینکه سیمکشیها شلوغ و درهمبرهم نشه، بهتره یه برد مدار چاپی براش بزنی یا از یه برد سوراخدار استفاده کنی. هدرها رو روی برد لحیم کن تا آردوینو نانو راحت روش سوار بشه و بعد سیمهای سنسورها، سروو موتور و بازر رو به پینهای مربوطه متصل کن.
مونتاژ و کدنویسی
این بخش از کار خیلی سادهست. حالا که مدارت رو بستی، فقط باید قطعات رو سر جای خودشون محکم کنی و کدها رو روی آردوینو آپلود کنی. پیشنهاد میکنم قبل از اینکه سیمهای بلند سنسورها رو وصل کنی، اول سوکتها رو روی قاب اصلی فیکس کنی.
سنسور غیرتماسی XKC باید به کمک یه تکه چسب پارچهای (ولکرو) به بدنه بطری یا کیسه سرم متصل بشه. حتما چسب رو محکم ببند تا سنسور تکون نخوره؛ وگرنه ممکنه سیستم به اشتباه تشخیص بده که سرم تموم شده.
حالا وقتشه بریم سراغ برنامهنویسی. اما قبل از آپلود کد، بیا منطق کارکرد این سیستم رو به چند بخش تقسیم کنیم تا دقیقا بدونی هر قطعه چطور کار میکنه.
محاسبه سرعت سرم با سنسور نوری
تو این بخش میخوایم سرعت تزریق یا همون نرخ چکیدن قطرات رو اندازه بگیریم. برای این کار از اپتوکوپلر مادون قرمز استفاده میکنیم تا تعداد قطرههایی که تو یک دقیقه میچکن رو بشماریم.
منطق کار خیلی سادهست؛ کافیه زمان دقیق بین چکیدن دو قطره متوالی رو اندازه بگیریم. از اونجایی که جریان سرم معمولا یکنواخته (مگر اینکه فشار خارجی بهش وارد بشه)، با داشتن این فاصله زمانی میتونیم تخمین بزنیم تو یک دقیقه چند قطره میچکه.
برای محاسبه این زمان، از دستور Micros تو برنامهنویسی آردوینو استفاده میکنیم. این دستور زمان دقیق افتادن قطره اول رو ثبت میکنه، بعد زمان قطره دوم رو میگیره و با کم کردن این دو تا عدد، فاصله زمانی رو به دست میاره.
تشخیص خالی شدن سرم با سنسور XKC
در نهایت این دیتا تبدیل به واحد DPM (قطره بر دقیقه) میشه. برای اینکه بفهمیم سرم کی تموم میشه، سنسور خازنی رو تو ارتفاع مشخصی از محفظه سرم نصب میکنیم. به محض اینکه سطح مایع از سنسور پایینتر بره، سیستم متوجه میشه. دقیقا به همین دلیله که تأکید کردم سنسور باید کاملا محکم و چسبیده به بطری باشه.
این سنسور دو حالت کاری داره. تو یه حالت وقتی مایع رو تشخیص میده سیگنال HIGH میفرسته و تو حالت دیگه سیگنال LOW تولید میکنه. تو میتونی بسته به کدی که مینویسی، از هر کدوم از این حالتها استفاده کنی.
تنظیم اتوماتیک سرعت تزریق سرم (اختیاری)
عملکرد سنسور خازنی بر اساس تفاوت ثابت دیالکتریک بین هوا و مایعاته. وقتی مایع از جلوی سنسور رد میشه، ظرفیت خازنی و به دنبال اون ولتاژ تغییر میکنه. سنسور این تغییر رو به عنوان حضور یا عدم حضور مایع تشخیص میده.
کارهایی که از اینجا به بعد میگیم کاملا اختیاریه. اگه فقط میخواستی دستگاهت یه مانیتور ساده باشه، همون دو تا سنسور قبلی کارت رو راه میندازن.
اما برای تنظیم اتوماتیک سرعت قطرهها، از یه کنترلکننده ریاضی به اسم الگوریتم PI استفاده کردیم. کار این الگوریتم اینه که خطاهای کوچیک تو نرخ چکیدن قطرهها رو در طول زمان اصلاح و جبران کنه تا جریان کاملا پایدار بمونه.
برای انتخاب سرعت دلخواه، از کلید سلکتوری استفاده میکنیم. این کلید 6 تا پایه داره که وقتی میچرخونیش، پین انتخابشده مدارش به زمین (GND) وصل میشه. آردوینو با خوندن این پایهها دقیقا متوجه میشه که چه سرعتی رو انتخاب کردی. با برنامهنویسی میتونیم هر کدوم از این پینها رو به یه سرعت تزریق خاص اختصاص بدیم.
#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 Variales
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);
}
(نکته: اگه کلید سلکتوری نداری، میتونی از چند تا کلید فشاری معمولی هم استفاده کنی، اما سلکتور کار رو خیلی تمیزتر میکنه.) حالا بیا تمام این منطقها رو تو یه کد واحد جمعبندی کنیم:
این کد کامل پروژهست که باید اون رو از طریق نرمافزارهایی مثل Arduino IDE یا PlatformIO روی برد آردوینو نانو آپلود کنی. خب، کار ما اینجا تموم شد! فقط کافیه محفظه سرم رو داخل گیره قرار بدی، دستگاه رو روشن کنی و از سیستم هوشمندت لذت ببری.
بهبودها و پیشنهادات آینده
این پروژه یه نمونه اولیه عالیه، اما همیشه جای پیشرفت وجود داره. مثلا برای بخش کنترل جریان مایع، به جای استفاده از سروو موتور که شیلنگ رو فشار میده، میتونی از یه پمپ پریستالتیک استفاده کنی. این پمپها تو تجهیزات پزشکی واقعی کاربرد دارن و احتمال آسیب دیدن یا مسدود شدن شیلنگ رو به صفر میرسونن.
یه ایده جذاب دیگه اینه که مغز متفکر پروژه رو از آردوینو نانو به بردهای خانواده ESP32 تغییر بدی. اینطوری سیستم مانیتورینگ سرم به اینترنت اشیا (IoT) وصل میشه و میتونی وضعیت رو از راه دور مانیتور کنی.
تغییرات دیگهای مثل طراحی یه بند تنظیمشونده برای سنسور XKC، یا دستکاری مقاومتهای ماژول نوری برای افزایش دقت سنسور هم میتونه عملکرد دستگاهت رو بینقص و حرفهایتر کنه.
فایلهای پیوست
تو میتونی اولین سازنده باشی!
اگه پروژه رو ساختی به اشتراک بزار و اعتبار هدیه بگیر
این آموزش با استانداردهای اختصاصی آیسیمدار بازنویسی و بهینهسازی شده و تمامی حقوق انتشار آن متعلق به این مجموعه است و در صورت کپی یا بازنشر پیگرد قانونی خواهد داشت.








































گفتگوهای کاربران
هنوز نظری ثبت نشده؛ تو شروع کن!
هنوز نظری ثبت نشده؛ تو شروع کن!