সব পোস্টে ফিরে যান
Arduino

delay() এর সমস্যা ও নন-ব্লকিং প্রোগ্রামিংয়ের গাইডলাইন

এমবেডেড সিস্টেমে delay() এর ক্ষতিকর দিক এবং মাল্টিটাস্কিংয়ের জন্য millis() এর সঠিক ব্যবহার

#Programming
delay() এর সমস্যা ও নন-ব্লকিং প্রোগ্রামিংয়ের গাইডলাইন

মাইক্রোকন্ট্রোলার বা এমবেডেড সিস্টেম নিয়ে কাজ শুরু করার সময় আমাদের সবারই প্রথম ভালোবাসা থাকে delay()। একটা এলইডি (LED) ব্লিঙ্ক করতে হবে? লিখে দাও delay(1000)। ব্যাস, কাজ শেষ! শুরুতে এই ফাংশনটা আমাদের অনেক সাহায্য করে, কোড লেখাও খুব সহজ মনে হয়।

কিন্তু যখনই তুমি একটু প্রফেশনাল বা বাস্তবমুখী প্রোজেক্ট করতে যাবে, তখন বুঝতে পারবে এই delay() আসলে তোমার কোডের সবচেয়ে বড় শত্রু। চলো, আমি আজ তোমাকে বুঝিয়ে বলছি কেন delay() ব্যবহার করা ঠিক না এবং এর সেরা বিকল্প কী।

১। delay() এর আসল সমস্যা কোথায়? (বাস্তবমুখী উদাহরণ)

সবচেয়ে বড় সমস্যা হলো— delay() একটি ব্লকিং ফাংশন (Blocking Function)। তুমি যখন কোডে delay(5000) লেখো, তখন মাইক্রোকন্ট্রোলার পুরো ৫ সেকেন্ডের জন্য আক্ষরিক অর্থেই ঘুমিয়ে পড়ে। এই সময়ে সে অন্য কোনো কাজ করতে পারে না, কোনো সেন্সরের ডেটা পড়তে পারে না, এমনকি তুমি যদি কোনো বাটনও চাপো, সেটারও কোনো রেসপন্স সে দেবে না।

বাস্তব জীবনের উদাহরণ: ধরো, তুমি একটি স্মার্ট সিকিউরিটি সিস্টেম বানিয়েছো। সেখানে একটি কোড আছে যা প্রতি ১০ সেকেন্ড পর পর টেম্পারেচার চেক করে (যাতে delay(10000) দেওয়া আছে)। এখন, ওই ১০ সেকেন্ড চলাকালীন সময়ে যদি তোমার রুমে কেউ ঢুকে পড়ে এবং মোশন সেন্সর সিগন্যাল দেয়, তোমার সিস্টেম সেটা টেরই পাবে না! কারণ সে তখন ১০ সেকেন্ডের ঘুমে ব্যস্ত। চোর এসে জিনিসপত্র নিয়ে চলে যাবে, আর তোমার মাইক্রোকন্ট্রোলার ঘুম ভাঙার পর দেখবে টেম্পারেচার ঠিক আছে কি না!

একসাথে একাধিক কাজ (Multitasking) করার ক্ষেত্রে delay() ব্যবহার করা মানে নিজের পায়ে নিজে কুড়াল মারা।

২। delay() এর সেরা বিকল্প কী?

এই সমস্যার সেরা এবং সবচেয়ে জনপ্রিয় সমাধান হলো millis() ফাংশন ব্যবহার করা।

millis() (মিলিসেকেন্ড) হলো মাইক্রোকন্ট্রোলারের নিজস্ব একটা স্টপওয়াচের মতো। মাইক্রোকন্ট্রোলার যখন চালু হয়, তখন থেকেই এই স্টপওয়াচটা শুরু হয়ে যায় এবং কত মিলিসেকেন্ড পার হলো তার হিসেব রাখতে থাকে।

পার্থক্যটা এভাবে চিন্তা করো:

  1. delay(): তুমি অ্যালার্ম ঘড়িতে ৫ মিনিটের অ্যালার্ম দিয়ে ঘুমিয়ে পড়লে। এই ৫ মিনিট দুনিয়ায় কী হলো তুমি কিছুই জানো না।
  2. millis(): তুমি ঘুমাচ্ছো না, বরং কাজ করতে করতে বারবার নিজের হাতের ঘড়ির দিকে তাকাচ্ছো ৫ মিনিট পার হলো কি না। ঘড়ি দেখার ফাঁকে ফাঁকে তুমি অন্য কাজও (যেমন টিভি দেখা, চা খাওয়া) করতে পারছো।

একটু অ্যাডভান্সড লেভেলে (যেমন ESP32 বা ESP-IDF এ FreeRTOS ব্যবহার করলে) আমরা vTaskDelay বা হার্ডওয়্যার টাইমার ব্যবহার করি, তবে সি/সি++ বা আর্ডুইনোর বেসিক নন-ব্লকিং লজিকের জন্য millis() শেখাটা হলো প্রথম এবং সবচেয়ে গুরুত্বপূর্ণ ধাপ।

৩। millis() এর ব্যবহার এবং এক্সপ্লেনেশন

চলো দেখি কিভাবে delay() এর বদলে millis() দিয়ে একটা এলইডি ব্লিঙ্ক করানো যায়।

cpp

// ভেরিয়েবল ডিক্লেয়ারেশন
unsigned long previousMillis = 0;  // সর্বশেষ কখন কাজটা হয়েছিল সেটা মনে রাখার জন্য
const long interval = 1000;        // কতক্ষণ পর পর কাজটা করতে চাই (১০০০ মিলি = ১ সেকেন্ড)
int ledState = LOW;                // এলইডির বর্তমান অবস্থা
const int ledPin = 13;             

void setup() {
pinMode(ledPin, OUTPUT);
}

void loop() {
// ঘড়ির বর্তমান সময়টা দেখে নিলাম
unsigned long currentMillis = millis(); 

// চেক করছি বর্তমান সময় আর আগের সময়ের পার্থক্য আমাদের কাঙ্ক্ষিত ইন্টারভালের চেয়ে বেশি বা সমান কি না
if (currentMillis - previousMillis >= interval) {
  
  // যদি সময় হয়ে থাকে, তবে আগের সময়টাকে আপডেট করে বর্তমান সময় করে দিলাম
  previousMillis = currentMillis;

  // এলইডি এর স্টেট পরিবর্তন করা (অফ থাকলে অন, অন থাকলে অফ)
  if (ledState == LOW) {
    ledState = HIGH;
  } else {
    ledState = LOW;
  }
  digitalWrite(ledPin, ledState);
}

// এখানে তুমি এখন নির্দ্বিধায় অন্য যেকোনো কাজ করতে পারো। 
// বাটন রিড করা বা সেন্সরের ডেটা নেওয়া, কোনো কিছুই আর ব্লক হবে না!
}

কিভাবে কাজ করছে? আমরা অনবরত ঘড়ির দিকে (currentMillis) তাকাচ্ছি। তারপর দেখছি এখনকার সময় থেকে আগে যখন এলইডি জ্বলেছিল (previousMillis), সেই সময়ের ব্যবধান ১০০০ মিলি সেকেন্ড পার হয়েছে কি না। যদি হয়, তবেই আমরা এলইডির অবস্থা বদলাচ্ছি। পুরো ব্যাপারটা নন-ব্লকিং, তাই loop() এর ভেতরে অন্য যেকোনো কোড একদম স্মুথলি চলতে থাকবে।

৪। এটাকে আরও প্রফেশনাল ও রিইউজেবল (Reusable) করার উপায়

উপরের কোডটা কাজ করলেও, প্রোজেক্ট বড় হলে বারবার এতগুলো ভেরিয়েবল ডিক্লেয়ার করা ঝামেলার। তাই আমরা অবজেক্ট ওরিয়েন্টেড প্রোগ্রামিং (OOP) এর সাহায্যে একটি ছোট্ট ক্লাস (Class) বা ফাংশন বানিয়ে নিতে পারি। এতে কোড দেখতেও সুন্দর লাগবে এবং বারবার ব্যবহার করা যাবে।

রিইউজেবল SimpleTimer ক্লাস:

cpp

class SimpleTimer {
private:
  unsigned long previousMillis;
  long interval;

public:
  // কনস্ট্রাক্টর: টাইমার তৈরি করার সময় ইন্টারভাল বলে দিতে হবে
  SimpleTimer(long intv) {
    interval = intv;
    previousMillis = 0;
  }

  // এই ফাংশনটি কল করলে জানাবে সময় হয়েছে কি না (true/false)
  bool isReady() {
    unsigned long currentMillis = millis();
    if (currentMillis - previousMillis >= interval) {
      previousMillis = currentMillis; // সময় আপডেট
      return true;
    }
    return false;
  }
};

// --- ব্যবহার করার নিয়ম ---

// অবজেক্ট তৈরি: বিভিন্ন কাজের জন্য আলাদা টাইমার
SimpleTimer ledTimer(1000);    // ১ সেকেন্ডের টাইমার এলইডির জন্য
SimpleTimer sensorTimer(5000); // ৫ সেকেন্ডের টাইমার সেন্সরের জন্য

const int ledPin = 13;
int ledState = LOW;

void setup() {
pinMode(ledPin, OUTPUT);
Serial.begin(9600);
}

void loop() {
// এলইডি ব্লিঙ্ক করানো ১ সেকেন্ড পর পর
if (ledTimer.isReady()) {
  ledState = !ledState; // স্টেট উল্টে দেওয়া
  digitalWrite(ledPin, ledState);
}

// সেন্সর ডেটা রিড করা ৫ সেকেন্ড পর পর
if (sensorTimer.isReady()) {
  Serial.println("Reading Sensor Data...");
}

// এখানে তুমি বাটন রিড বা অন্য যেকোনো কাজ করতে পারো কোনো বাধা ছাড়াই!
}

কেন এটি সেরা উপায়?

তুমি দেখতেই পাচ্ছো, loop() এর ভেতর কোড কত পরিষ্কার হয়ে গেছে। তোমার যখনই নতুন কোনো ডিলে দরকার হবে, তুমি শুধু SimpleTimer এর নতুন একটা অবজেক্ট তৈরি করে নেবে। কোনো কোড ব্লক হবে না, এবং তোমার পুরো সিস্টেম একটা প্রফেশনাল মাল্টিটাস্কিং সিস্টেমের মতো কাজ করবে।

আজই তোমার কোড থেকে delay() কে চিরতরে ছুটি দিয়ে দাও, আর millis() এর স্মার্টনেস উপভোগ করো! শুভ কোডিং!