برنامه نویسی

آموزش Function و Sub در آردوینو و تفاوت آن ها

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

سطح مقدماتی

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

در زبان برنامه نویسی آردوینو که بر پایه C++ است، از تابع (Function) برای این کار استفاده می کنیم.

ممکن است در آموزش های برنامه نویسی با اصطلاح Sub یا Subroutine نیز روبرو شوید. این اصطلاح معمولا به یک زیربرنامه یا مجموعه ای از دستورات اشاره دارد که برای انجام یک کار مشخص نوشته شده است.

در این آموزش یاد می گیریم Function چیست، چگونه از آن استفاده کنیم و تفاوت آن با Sub چیست.

۱. Function چیست؟

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

برای مثال، فرض کنید در چند قسمت از برنامه باید چراغ LED روشن شود. به جای اینکه هر بار دستور روشن کردن چراغ را تکرار کنیم، می توانیم یک تابع بنویسیم و هر زمان لازم بود آن را اجرا کنیم.

ساختار کلی تعریف یک تابع به شکل زیر است:

نوع_خروجی نام_تابع(پارامترها) {
  // دستورات تابع
}

اجزای این ساختار عبارت اند از:

  • نوع خروجی: مشخص می کند تابع چه نوع مقداری برمی گرداند؛ مانند int، float یا bool. اگر تابع مقداری برنگرداند، از void استفاده می کنیم.
  • نام تابع: نامی است که برای شناسایی و فراخوانی تابع انتخاب می کنیم.
  • پارامترها: اطلاعاتی هستند که هنگام فراخوانی به تابع داده می شوند. اگر تابع به ورودی نیاز نداشته باشد، پرانتز خالی می ماند.
  • بدنه تابع: دستورات و عملیاتی هستند که با اجرای تابع انجام می شوند.

۲. اولین تابع در آردوینو

مثال زیر را در نظر بگیرید:

const int ledPin = 13;

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

void loop() {
  turnOnLED();
  delay(1000);
}

void turnOnLED() {
  digitalWrite(ledPin, HIGH);
}

در این برنامه، تابع turnOnLED() برای روشن کردن LED تعریف شده است.

در تابع setup() پایه شماره ۱۳ به عنوان خروجی تنظیم می شود. سپس در تابع loop()، تابع turnOnLED() فراخوانی می شود و LED روشن می شود.

از آنجا که در این مثال دستوری برای خاموش کردن LED وجود ندارد، چراغ روشن می ماند. دستور delay(1000) نیز باعث می شود برنامه به مدت یک ثانیه مکث کند و سپس اجرای حلقه ادامه پیدا کند.

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

۳. تابع void چیست؟

کلمه void در تعریف تابع به این معنی است که تابع هیچ مقداری را به محل فراخوانی خود برنمی گرداند.

برای مثال:

void turnOffLED() {
  digitalWrite(13, LOW);
}

این تابع فقط LED متصل به پایه ۱۳ را خاموش می کند و نتیجه ای مانند عدد یا مقدار منطقی برنمی گرداند.

می توانیم آن را در برنامه فراخوانی کنیم:

void loop() {
  turnOffLED();
  delay(1000);
}

توابع setup() و loop() نیز از نوع void هستند؛ زیرا مقدار بازگشتی ندارند.

۴. تابع دارای مقدار بازگشتی

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

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

int sumNumbers(int a, int b) {
  return a + b;
}

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

  int result = sumNumbers(10, 20);

  Serial.print("Result: ");
  Serial.println(result);
}

void loop() {
}

در این مثال:

  • نوع خروجی تابع int است؛ یعنی تابع یک عدد صحیح برمی گرداند.
  • پارامترهای a و b دو عدد ورودی هستند.
  • دستور return نتیجه محاسبه را برمی گرداند.
  • با فراخوانی sumNumbers(10, 20) مقدار ۳۰ به دست می آید و در متغیر result قرار می گیرد.

خروجی Serial Monitor به شکل زیر خواهد بود:

Result: 30

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

۵. پارامتر و آرگومان چیست؟

پارامتر و آرگومان مفاهیم مرتبطی هستند، اما دقیقا یکسان نیستند.

  • پارامتر: متغیری است که هنگام تعریف تابع در پرانتز نوشته می شود.
  • آرگومان: مقداری است که هنگام فراخوانی تابع به آن ارسال می کنیم.

مثال:

void blinkLED(int delayTime) {
  digitalWrite(13, HIGH);
  delay(delayTime);

  digitalWrite(13, LOW);
  delay(delayTime);
}

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

void loop() {
  blinkLED(500);
  blinkLED(1000);
}

در این مثال، delayTime پارامتر تابع است. اعداد 500 و 1000 آرگومان هایی هستند که هنگام فراخوانی ارسال می شوند.

در فراخوانی اول، LED با مکث نیم ثانیه روشن و خاموش می شود. در فراخوانی دوم، مدت هر مکث یک ثانیه است.

به این ترتیب، بدون نوشتن دوباره دستورات روشن و خاموش کردن LED، می توانیم عملکرد تابع را با ورودی های مختلف تغییر دهیم.

۶. Sub یا Subroutine چیست؟

اصطلاح Subroutine در بسیاری از زبان های برنامه نویسی به بخشی از برنامه گفته می شود که برای انجام یک کار مشخص تعریف شده و می توان آن را از بخش های دیگر برنامه فراخوانی کرد.

در بعضی زبان ها، به ویژه Visual Basic و خانواده زبان های BASIC، کلمه Sub به طور مشخص برای تعریف زیربرنامه ای استفاده می شود که مقدار بازگشتی ندارد.

برای مثال، در VB.NET می توان نوشت:

Sub TurnOnLED()
    ' دستورات روشن کردن LED
End Sub

اما در آردوینو که بر پایه C++ است، کلمه کلیدی Sub برای تعریف تابع وجود ندارد.

معادل رایج یک Sub بدون مقدار بازگشتی در آردوینو، تابعی است که با void تعریف می شود:

void turnOnLED() {
  digitalWrite(13, HIGH);
}

بنابراین، اگر در یک آموزش گفته شود «یک Sub بنویسید»، معمولا منظور این است که یک زیربرنامه برای انجام کاری مشخص تعریف کنید. در آردوینو این کار را با تعریف تابع انجام می دهیم.

۷. تفاوت Function و Sub

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

ویژگیFunctionSub در زبان هایی مانند VB.NET
کاربردانجام یک کار و در صورت نیاز برگرداندن نتیجهاجرای مجموعه ای از دستورات
مقدار بازگشتیمعمولا می تواند مقدار برگرداندبه صورت مستقیم مقدار بازگشتی ندارد
تعریف در آردوینوبا نوع خروجی مانند int یا voidکلمه کلیدی Sub وجود ندارد
معادل رایج در آردوینوتابع با نوع خروجی مناسبتابعی با نوع void
فراخوانیبا نوشتن نام تابع و آرگومان هادر زبان های دارای Sub با نام زیربرنامه

نکته مهم: در زبان C++، از جمله برنامه نویسی آردوینو، «Function» اصطلاح عمومی برای این سازوکار است. تابعی که با void تعریف می شود نیز همچنان یک Function است، حتی اگر مقدار بازگشتی نداشته باشد.

در نتیجه، در آردوینو تفاوت مستقلی میان Function و Sub به عنوان دو دستور زبان وجود ندارد؛ بلکه Sub معمولا نامی عمومی برای زیربرنامه یا نامی اختصاصی در بعضی زبان های دیگر است.

۸. استفاده از چند تابع در یک پروژه

فرض کنید می خواهیم LED را روشن کنیم، خاموش کنیم و وضعیت آن را از طریق Serial Monitor گزارش دهیم.

const int ledPin = 13;

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

void loop() {
  turnOnLED();
  reportStatus("LED is ON");
  delay(1000);

  turnOffLED();
  reportStatus("LED is OFF");
  delay(1000);
}

void turnOnLED() {
  digitalWrite(ledPin, HIGH);
}

void turnOffLED() {
  digitalWrite(ledPin, LOW);
}

void reportStatus(const char* message) {
  Serial.println(message);
}

در این برنامه سه تابع تعریف شده است:

  • turnOnLED() چراغ را روشن می کند.
  • turnOffLED() چراغ را خاموش می کند.
  • reportStatus() پیام دریافتی را در Serial Monitor نمایش می دهد.

این روش باعث می شود هر تابع فقط یک مسئولیت مشخص داشته باشد. در نتیجه، اگر بخواهیم روش گزارش وضعیت را تغییر دهیم، لازم نیست دستورات اصلی روشن و خاموش کردن LED را تغییر دهیم.

۹. یک مثال کاربردی: خواندن ولتاژ باتری

تابع ها در پروژه های واقعی آردوینو نیز بسیار کاربردی هستند. برای مثال، می توانیم خواندن ولتاژ باتری را در یک تابع جداگانه قرار دهیم.

کد زیر یک نمونه ساده برای خواندن ولتاژ از طریق تقسیم ولتاژ است:

const int batteryPin = A0;
const float referenceVoltage = 5.0;
const float dividerRatio = 2.0;

float readBatteryVoltage() {
  int adcValue = analogRead(batteryPin);

  float pinVoltage =
    adcValue * referenceVoltage / 1023.0;

  return pinVoltage * dividerRatio;
}

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

void loop() {
  float batteryVoltage = readBatteryVoltage();

  Serial.print("Battery Voltage: ");
  Serial.print(batteryVoltage, 2);
  Serial.println(" V");

  delay(1000);
}

تابع readBatteryVoltage() مقدار ولتاژ اندازه گیری شده را برمی گرداند. در تابع loop()، نتیجه در متغیر batteryVoltage ذخیره و نمایش داده می شود.

در این نمونه فرض شده است که برد از ADC ده بیتی و مرجع ۵ ولت استفاده می کند و نسبت تقسیم ولتاژ ۲ است. برای بردهایی مانند ESP32 باید ولتاژ مرجع، مشخصات ADC، محدوده ورودی و کالیبراسیون متناسب با سخت افزار واقعی بررسی شوند.

۱۰. مزایای استفاده از تابع

استفاده درست از تابع ها چند مزیت مهم دارد:

  • جلوگیری از تکرار کد: یک بار دستورات را می نویسیم و چند بار از آن ها استفاده می کنیم.
  • خوانایی بیشتر: نام تابع مشخص می کند هر بخش از برنامه چه کاری انجام می دهد.
  • عیب یابی آسان تر: می توانیم هر بخش را جداگانه بررسی کنیم.
  • توسعه ساده تر: تغییر یک عملکرد معمولا به تغییر بخش های کمتری از برنامه نیاز دارد.
  • استفاده مجدد: می توانیم تابع های عمومی را در پروژه های دیگر نیز به کار ببریم.

۱۱. اشتباهات رایج

اشتباه اول: تعریف تابع بدون فراخوانی آن

تعریف تابع به تنهایی آن را اجرا نمی کند. باید آن را از بخشی مانند setup() یا loop() یا تابع دیگری فراخوانی کنیم.

اشتباه دوم: ناهماهنگی نوع خروجی

اگر تابع با نوع int تعریف شده باشد، باید مقدار بازگشتی سازگار با این نوع داشته باشد. اگر نیازی به بازگرداندن مقدار نداریم، از void استفاده می کنیم.

اشتباه سوم: فراموش کردن پرانتز هنگام فراخوانی

برای فراخوانی تابع باید پرانتزها را بنویسیم. برای مثال:

turnOnLED();

اشتباه چهارم: انتظار اجرای خودکار تابع

در آردوینو، توابع setup() و loop() توسط چارچوب اجرایی آردوینو فراخوانی می شوند. سایر تابع ها، مانند turnOnLED()، باید از طریق برنامه فراخوانی شوند.

اشتباه پنجم: تصور اینکه Sub دستور جداگانه ای در آردوینو است

در آردوینو کلمه کلیدی Sub نداریم. برای تعریف یک زیربرنامه از قواعد تعریف تابع در C++ استفاده می کنیم.

جمع بندی

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

اگر تابع مقداری برنگرداند، معمولا آن را با void تعریف می کنیم. اگر لازم باشد نتیجه ای مانند عدد یا مقدار اندازه گیری شده برگرداند، نوع خروجی مناسبی مانند int یا float انتخاب می کنیم.

اصطلاح Sub یا Subroutine نیز به یک زیربرنامه اشاره دارد و در برخی زبان ها، مانند VB.NET، برای تعریف زیربرنامه بدون مقدار بازگشتی استفاده می شود. در آردوینو، هر دو مفهوم را با سازوکار تابع های C++ پیاده سازی می کنیم.

پیشنهاد تمرین: برنامه ای بنویسید که سه تابع داشته باشد: یکی LED را روشن کند، یکی آن را خاموش کند و دیگری وضعیت LED را از طریق Serial Monitor نمایش دهد. سپس یک تابع دیگر اضافه کنید که مدت زمان روشن بودن LED را به عنوان پارامتر دریافت کند.

نمونه کد

const int ledPin = 13;

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

void loop() {
  blinkLED(500);
}

void blinkLED(int delayTime) {
  digitalWrite(ledPin, HIGH);
  delay(delayTime);

  digitalWrite(ledPin, LOW);
  delay(delayTime);