برنامه نویسی

آموزش حلقه های while و do-while در آردوینو

در این مستند با دو حلقه پرکاربرد while و do-while در برنامه نویسی آردوینو آشنا می شویم. یاد می گیریم چگونه اجرای دستورات را تا زمانی که یک شرط برقرار است تکرار کنیم، تفاوت این دو حلقه چیست، چگونه از حلقه های بی نهایت جلوگیری کنیم و چطور از این ساختارها برای خواندن سنسورها، کنترل دکمه ها و مدیریت ورودی ها استفاده کنیم.

سطح مقدماتی

آموزش حلقه های while و do-while در آردوینو

مقدمه

در برنامه نویسی آردوینو، گاهی لازم است یک دستور یا مجموعه ای از دستورات چندین بار اجرا شوند. برای این کار از حلقه ها (Loops) استفاده می کنیم.

تا اینجا ممکن است با حلقه for آشنا شده باشید. این حلقه معمولا زمانی کاربرد دارد که تعداد تکرارها مشخص باشد؛ مثلا روشن کردن ۸ LED به ترتیب یا اجرای یک دستور ۱۰ بار.

اما در بسیاری از پروژه ها، تعداد تکرارها از ابتدا مشخص نیست. برای مثال:

  • تا زمانی که دکمه فشرده است، یک عملیات انجام شود.
  • تا زمانی که مقدار سنسور به حد مشخصی نرسیده است، بررسی ادامه پیدا کند.
  • تا زمانی که داده ای از ورودی دریافت نشده است، برنامه منتظر بماند.
  • یک منو تا زمان دریافت فرمان کاربر فعال بماند.

در چنین شرایطی، حلقه های while و do-while می توانند مفید باشند.

۱. حلقه while چیست؟

حلقه while مجموعه ای از دستورات را تا زمانی اجرا می کند که شرط آن درست باشد.

ساختار کلی:

while (condition) {
  // دستورات تکرارشونده
}

در این ساختار، ابتدا شرط بررسی می شود. اگر شرط درست باشد، دستورات داخل حلقه اجرا می شوند. پس از پایان دستورات، شرط دوباره بررسی می شود.

اگر شرط نادرست باشد، اجرای حلقه متوقف می شود و برنامه به دستور بعد از حلقه می رود.

مثال ساده

int counter = 0;

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

  while (counter < 5) {
    Serial.println(counter);
    counter++;
  }
}

void loop() {
}

خروجی در Serial Monitor

0
1
2
3
4

توضیح کد

  1. متغیر counter در ابتدا برابر صفر است.
  1. شرط counter < 5 بررسی می شود.
  1. اگر شرط درست باشد، مقدار متغیر چاپ می شود.
  1. دستور counter++ مقدار متغیر را یک واحد افزایش می دهد.
  1. حلقه تا زمانی ادامه پیدا می کند که مقدار counter به ۵ برسد.

وقتی مقدار متغیر برابر ۵ شود، شرط نادرست خواهد شد و حلقه پایان می یابد.

نکته: در این مثال، حلقه داخل تابع setup() قرار دارد؛ بنابراین فقط یک بار، هنگام راه اندازی برد اجرا می شود. در پروژه های واقعی می توان از while در داخل loop() یا توابع دیگر نیز استفاده کرد.

۲. حلقه while چگونه کار می کند؟

مهم ترین ویژگی حلقه while این است که شرط را پیش از اجرای دستورات بررسی می کند.

بنابراین ممکن است دستورات داخل حلقه حتی یک بار هم اجرا نشوند.

برای مثال:

int counter = 10;

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

  while (counter < 5) {
    Serial.println(counter);
    counter++;
  }

  Serial.println("Loop finished");
}

void loop() {
}

در این برنامه، مقدار counter از ابتدا ۱۰ است. چون شرط counter < 5 نادرست است، بدنه حلقه اجرا نمی شود و فقط پیام زیر نمایش داده می شود:

Loop finished

این رفتار زمانی اهمیت دارد که اجرای دستورات داخل حلقه به برقرار بودن یک شرط وابسته باشد.

۳. حلقه do-while چیست؟

حلقه do-while نیز مانند while برای تکرار دستورات استفاده می شود؛ اما یک تفاوت مهم دارد: ابتدا دستورات را اجرا می کند و سپس شرط را بررسی می کند.

ساختار کلی:

do {
  // دستورات تکرارشونده
} while (condition);

در پایان ساختار do-while باید بعد از پرانتز شرط، علامت سمی کالن ; قرار گیرد.

مثال ساده

int counter = 10;

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

  do {
    Serial.println(counter);
    counter++;
  } while (counter < 5);
}

void loop() {
}

خروجی

10

چرا فقط عدد ۱۰ چاپ می شود؟

زیرا دستورات داخل do پیش از بررسی شرط اجرا می شوند. پس از چاپ عدد ۱۰ و افزایش متغیر به ۱۱، شرط counter < 5 بررسی می شود. این شرط نادرست است؛ بنابراین حلقه پایان می یابد.

در نتیجه، حتی اگر شرط از ابتدا نادرست باشد، حلقه do-while حداقل یک بار اجرا خواهد شد.

۴. تفاوت while و do-while

تفاوت اصلی این دو حلقه در زمان بررسی شرط است.

ویژگیwhiledo-while
زمان بررسی شرطقبل از اجرای دستوراتبعد از اجرای دستورات
حداقل تعداد اجراصفر باریک بار
کاربرد متداولتکرار تا برقرار بودن شرطاجرای اولیه و سپس بررسی شرط
علامت سمی کالن بعد از شرطلازم نیستلازم است

مقایسه با یک مثال

در این مثال، هر دو حلقه با شرطی شروع می شوند که از ابتدا نادرست است.

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

  int a = 10;

  while (a < 5) {
    Serial.println("while");
  }

  int b = 10;

  do {
    Serial.println("do-while");
  } while (b < 5);
}

void loop() {
}

خروجی:

do-while

حلقه while اجرا نمی شود، اما حلقه do-while یک بار دستورات خود را اجرا می کند.

۵. استفاده از while برای خواندن دکمه

یکی از کاربردهای حلقه while، اجرای یک بخش از برنامه تا زمان رها شدن دکمه است.

فرض کنید دکمه ای بین پایه دیجیتال ۲ و GND متصل شده و از مقاومت پول‌آپ داخلی استفاده می کنیم.

در این حالت:

  • دکمه رها است: HIGH
  • دکمه فشرده است: LOW

کد نمونه

const int buttonPin = 2;
const int ledPin = LED_BUILTIN;

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

void loop() {
  if (digitalRead(buttonPin) == LOW) {
    digitalWrite(ledPin, HIGH);

    while (digitalRead(buttonPin) == LOW) {
      // منتظر رها شدن دکمه می مانیم
    }

    digitalWrite(ledPin, LOW);
  }
}

توضیح عملکرد

  1. برنامه بررسی می کند که آیا دکمه فشرده شده است یا خیر.
  1. اگر دکمه فشرده شده باشد، LED روشن می شود.
  1. حلقه while تا زمانی ادامه پیدا می کند که دکمه همچنان فشرده باشد.
  1. پس از رها شدن دکمه، حلقه پایان می یابد و LED خاموش می شود.

نکته مهم: این مثال ساده برای آموزش است. حلقه انتظار، اجرای سایر دستورات برنامه را تا زمان رها شدن دکمه متوقف می کند. همچنین برای تشخیص دقیق فشار و رها شدن دکمه باید لرزش مکانیکی کنتاکت ها (Debounce) را در نظر گرفت.

۶. استفاده از do-while برای دریافت ورودی

حلقه do-while زمانی مناسب است که بخواهیم یک عملیات حداقل یک بار اجرا شود و سپس درباره ادامه آن تصمیم بگیریم.

برای مثال، می توانیم یک مقدار را از ورودی سریال دریافت کنیم و تا زمانی که مقدار معتبر نشده است، از کاربر بخواهیم دوباره آن را وارد کند.

مثال زیر یک نمونه ساده از بررسی ورودی عددی است:

int value;

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

  do {
    Serial.println("Enter a number from 1 to 10:");

    while (Serial.available() == 0) {
      // منتظر دریافت داده از Serial Monitor می مانیم
    }

    value = Serial.parseInt();

    // پاک کردن داده های باقی مانده در ورودی سریال
    while (Serial.available() > 0) {
      Serial.read();
    }

  } while (value < 1 || value > 10);

  Serial.print("Valid value: ");
  Serial.println(value);
}

void loop() {
}

روش آزمایش

  1. برنامه را روی برد بارگذاری کنید.
  1. Serial Monitor را با سرعت 9600 باز کنید.
  1. مقدار عددی بین ۱ تا ۱۰ وارد کنید و ارسال را انجام دهید.
  1. اگر عدد خارج از این محدوده باشد، پیام درخواست ورودی دوباره نمایش داده می شود.
  1. پس از وارد کردن عدد معتبر، برنامه مقدار را نمایش می دهد.

توجه: این مثال برای آموزش مناسب است، اما Serial.parseInt() تنظیمات زمانی و رفتار خاص خود را دارد. در پروژه های حساس بهتر است ورودی سریال به صورت کامل و با مدیریت پایان خط و خطاهای ورودی پردازش شود.

۷. استفاده از while برای رسیدن سنسور به مقدار مشخص

فرض کنید یک سنسور آنالوگ به پایه A0 متصل است و می خواهیم مقدار آن را تا زمانی که از یک آستانه کمتر است بررسی کنیم.

const int sensorPin = A0;
const int threshold = 500;

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

  while (analogRead(sensorPin) < threshold) {
    Serial.println("Sensor value is below threshold");
    delay(200);
  }

  Serial.println("Threshold reached");
}

void loop() {
}

در این برنامه، تا زمانی که مقدار خوانده شده از سنسور کمتر از ۵۰۰ باشد، پیام نمایش داده می شود. وقتی مقدار به ۵۰۰ یا بیشتر برسد، حلقه پایان می یابد.

نکته مهم درباره این روش

چون حلقه در setup() قرار دارد، اگر مقدار سنسور هیچ وقت به آستانه نرسد، اجرای setup() ادامه پیدا نمی کند و برنامه به loop() نمی رسد.

در پروژه های واقعی باید برای چنین انتظاری، در صورت نیاز، زمان پایان (Timeout) در نظر گرفت. همچنین اگر لازم است سایر بخش های برنامه در این مدت فعال بمانند، بهتر است به جای انتظار مسدودکننده از بررسی دوره ای مقدار سنسور در loop() استفاده شود.

۸. حلقه بی نهایت چیست؟

حلقه بی نهایت (Infinite Loop) زمانی ایجاد می شود که شرط حلقه همیشه درست باقی بماند یا متغیرها به گونه ای تغییر نکنند که شرط نادرست شود.

مثال:

int counter = 0;

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

  while (counter < 5) {
    Serial.println(counter);
  }
}

void loop() {
}

در این برنامه، مقدار counter هیچ وقت افزایش نمی یابد. بنابراین شرط counter < 5 همیشه درست است و حلقه پایان پیدا نمی کند.

روش اصلاح

int counter = 0;

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

  while (counter < 5) {
    Serial.println(counter);
    counter++;
  }
}

void loop() {
}

با اضافه کردن counter++، مقدار متغیر به تدریج افزایش می یابد و در نهایت شرط نادرست می شود.

آیا حلقه بی نهایت همیشه اشتباه است؟

خیر. در آردوینو، تابع loop() به طور مداوم اجرا می شود و عملا ساختاری تکرارشونده دارد. همچنین گاهی در برنامه های تعبیه شده از حلقه بی نهایت به صورت عمدی استفاده می شود.

مسئله زمانی ایجاد می شود که حلقه ناخواسته مانع انجام کارهای دیگر شود؛ مثلا ارتباط سریال، کنترل نمایشگر، خواندن سنسور یا پاسخ به ورودی های دیگر متوقف بماند.

۹. استفاده از break و continue

در حلقه های while و do-while می توان از دو دستور break و continue استفاده کرد.

دستور break

دستور break اجرای حلقه را بلافاصله متوقف می کند و برنامه را به اولین دستور پس از حلقه می برد.

int counter = 0;

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

  while (counter < 10) {
    if (counter == 5) {
      break;
    }

    Serial.println(counter);
    counter++;
  }

  Serial.println("Loop ended");
}

void loop() {
}

در این مثال، اعداد صفر تا چهار چاپ می شوند و پس از آن حلقه پایان می یابد.

دستور continue

دستور continue باعث می شود ادامه دستورات همان دور از حلقه نادیده گرفته شود و اجرای دور بعدی آغاز شود.

int counter = 0;

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

  while (counter < 5) {
    counter++;

    if (counter == 3) {
      continue;
    }

    Serial.println(counter);
  }
}

void loop() {
}

در این مثال، عدد ۳ چاپ نمی شود؛ زیرا با رسیدن مقدار counter به ۳، دستور continue اجرا می شود.

نکته: هنگام استفاده از continue در حلقه ها باید مطمئن شوید که متغیرهای لازم برای تغییر شرط به روز می شوند؛ در غیر این صورت ممکن است حلقه بی نهایت ایجاد شود.

۱۰. نکات مهم در استفاده از while و do-while

نکته اول: شرط حلقه باید امکان پایان یافتن داشته باشد

اگر انتظار دارید حلقه پس از مدتی تمام شود، بررسی کنید که شرط آن بتواند نادرست شود.

نکته دوم: مراقب توقف سایر کارهای برنامه باشید

حلقه های انتظار می توانند مانع اجرای بخش های دیگر برنامه شوند. در پروژه های دارای نمایشگر، شبکه، سنسور یا چند دکمه، این موضوع اهمیت زیادی دارد.

نکته سوم: از Timeout استفاده کنید

اگر منتظر دریافت داده یا رسیدن سنسور به مقدار مشخصی هستید، بهتر است یک زمان محدود برای انتظار تعیین کنید.

نکته چهارم: حلقه do-while حداقل یک بار اجرا می شود

اگر اجرای بدنه حلقه حتی در صورت نادرست بودن شرط اولیه مناسب نیست، از while استفاده کنید.

نکته پنجم: سمی کالن انتهای do-while را فراموش نکنید

ساختار صحیح:

do {
  // دستورات
} while (condition);

نبودن سمی کالن انتهای حلقه می تواند باعث خطای کامپایل شود.

۱۱. مقایسه حلقه های for، while و do-while

ویژگیforwhiledo-while
کاربرد متداولتعداد تکرار مشخصتکرار تا برقرار بودن شرطاجرای حداقل یک بار و سپس تکرار
محل بررسی شرطپیش از هر دورپیش از هر دورپس از هر دور
حداقل تعداد اجراصفر بارصفر باریک بار
مدیریت شمارندهمعمولا در سرآیند حلقهمعمولا در بدنهمعمولا در بدنه
مثالپیمایش آرایهانتظار برای یک شرطدریافت ورودی و بررسی آن

هیچ کدام از این حلقه ها همیشه بهترین انتخاب نیستند. انتخاب درست به نیاز برنامه، نحوه مدیریت زمان و این موضوع بستگی دارد که آیا اجرای بدنه حلقه باید حداقل یک بار انجام شود یا خیر.

۱۲. تمرین های پیشنهادی

برای یادگیری بهتر، تمرین های زیر را انجام دهید:

  1. برنامه ای بنویسید که با استفاده از while اعداد ۱ تا ۲۰ را در Serial Monitor چاپ کند.
  1. برنامه ای بنویسید که با استفاده از do-while حداقل یک بار یک پیام را نمایش دهد.
  1. یک دکمه به پایه دیجیتال ۲ متصل کنید و با استفاده از while تا زمان رها شدن دکمه، LED را روشن نگه دارید.
  1. برنامه ای بنویسید که مقدار یک پتانسیومتر متصل به A0 را تا رسیدن به مقدار مشخصی بررسی کند.
  1. در یک حلقه while از break استفاده کنید تا با رسیدن شمارنده به عدد ۷، حلقه متوقف شود.
  1. برنامه ای طراحی کنید که در انتظار دریافت داده از Serial Monitor، پس از گذشت مدت مشخصی از ادامه انتظار منصرف شود.

جمع بندی

حلقه های while و do-while از ابزارهای مهم برنامه نویسی آردوینو هستند.

  • حلقه while ابتدا شرط را بررسی می کند و فقط در صورت درست بودن آن، دستورات را اجرا می کند.
  • حلقه do-while ابتدا دستورات را اجرا می کند و سپس شرط را بررسی می کند؛ بنابراین حداقل یک بار اجرا می شود.
  • با break می توان حلقه را زودتر متوقف کرد و با continue ادامه دستورات یک دور را نادیده گرفت.
  • اگر شرط حلقه هیچ وقت نادرست نشود، ممکن است حلقه بی نهایت ایجاد شود.
  • در پروژه های واقعی باید به زمان انتظار و توقف احتمالی سایر وظایف برنامه توجه کرد.

با شناخت تفاوت این حلقه ها، می توانید ساختار برنامه های آردوینو را متناسب با نیاز پروژه انتخاب کنید و کنترل بهتری بر تکرار دستورات داشته باشید.

نمونه کد :

int counter = 0;

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

  while (counter < 5) {
    Serial.println(counter);
    counter++;
  }

  int value = 10;

  do {
    Serial.println(value);
    value++;
  } while (value < 12);
}

void loop() {
}