آموزش آردوینو

خواندن دکمه با آردوینو: digitalRead، پول‌آپ و دی‌بانس

در این مستند با روش خواندن وضعیت دکمه در آردوینو با استفاده از تابع digitalRead() آشنا می شویم. سپس اتصال دکمه با مقاومت پول‌آپ خارجی و استفاده از مقاومت پول‌آپ داخلی با INPUT_PULLUP را بررسی می کنیم. در ادامه، مفهوم دی‌بانس (Debounce) و روش حذف تغییرات ناخواسته ناشی از لرزش مکانیکی دکمه را با مثال های عملی و کدهای قابل اجرا یاد می گیریم.

سطح مقدماتی

خواندن دکمه با آردوینو: digitalRead، پول‌آپ و دی‌بانس

مقدمه

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

برای خواندن وضعیت یک دکمه در آردوینو، معمولا از تابع digitalRead() استفاده می کنیم. اما برای عملکرد صحیح دکمه باید به دو نکته مهم توجه داشته باشیم:

  • وضعیت پایه ورودی در حالت آزاد دکمه باید مشخص باشد و شناور نماند.
  • تغییر وضعیت دکمه باید بدون ثبت چندباره ناشی از لرزش مکانیکی آن تشخیص داده شود.

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

۱. تابع digitalRead چیست؟

تابع digitalRead() وضعیت منطقی یک پایه دیجیتال را می خواند و یکی از دو مقدار زیر را برمی گرداند:

مقدارمفهوم
HIGHسطح منطقی بالا
LOWسطح منطقی پایین

ساختار کلی تابع:

int state = digitalRead(2);

در این مثال، وضعیت پایه دیجیتال شماره ۲ خوانده و در متغیر state ذخیره می شود.

می توانیم با استفاده از شرط، واکنش برنامه به وضعیت دکمه را مشخص کنیم:

if (digitalRead(2) == HIGH) {
  // اجرای دستور در صورت HIGH بودن پایه
}

نکته: مقدار HIGH همیشه به معنای فشرده بودن دکمه نیست. این موضوع به نحوه اتصال دکمه و مدار پول‌آپ یا پول‌داون بستگی دارد.

۲. اتصال دکمه با مقاومت پول‌داون

در روش پول‌داون (Pull-down)، یک مقاومت بین پایه ورودی و GND قرار می گیرد و دکمه، پایه ورودی را هنگام فشرده شدن به ولتاژ مثبت متصل می کند.

در این روش:

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

مدار اتصال

برای نمونه، از پایه دیجیتال ۲ آردوینو Uno استفاده می کنیم.

  • یک سر دکمه را به 5V متصل کنید.
  • سر دیگر دکمه را به پایه دیجیتال ۲ متصل کنید.
  • یک مقاومت ۱۰ کیلو اهم بین پایه دیجیتال ۲ و GND قرار دهید.
5V
 |
[ دکمه ]
 |
 +---------- D2
 |
[ 10kΩ ]
 |
GND

مقاومت باعث می شود وقتی دکمه رها است، پایه ورودی به GND متصل بماند و مقدار آن به صورت تصادفی تغییر نکند.

کد نمونه

const int buttonPin = 2;

void setup() {
  pinMode(buttonPin, INPUT);
  Serial.begin(9600);
}

void loop() {
  int buttonState = digitalRead(buttonPin);

  if (buttonState == HIGH) {
    Serial.println("Button Pressed");
  }

  delay(100);
}

در این برنامه، هنگام فشردن دکمه پیام Button Pressed در Serial Monitor نمایش داده می شود.

توجه: این مدار به یک مقاومت خارجی نیاز دارد. در بسیاری از بردهای آردوینو، می توان با استفاده از مقاومت پول‌آپ داخلی، اتصال ساده تری ایجاد کرد.

۳. استفاده از مقاومت پول‌آپ داخلی با INPUT_PULLUP

در بسیاری از بردهای آردوینو، مقاومت پول‌آپ داخلی وجود دارد. با تعیین حالت پایه به صورت INPUT_PULLUP، این مقاومت فعال می شود و در حالت عادی پایه را در سطح منطقی بالا نگه می دارد.

برای این روش:

  • یک سر دکمه را به پایه دیجیتال ۲ متصل کنید.
  • سر دیگر دکمه را به GND متصل کنید.
  • در برنامه از pinMode(buttonPin, INPUT_PULLUP) استفاده کنید.

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

منطق عملکرد

وضعیت دکمهمقدار خوانده شده
رهاHIGH
فشردهLOW

این منطق برخلاف مدار پول‌داون است؛ بنابراین هنگام استفاده از INPUT_PULLUP باید شرط برنامه را متناسب با آن بنویسیم.

کد نمونه

const int buttonPin = 2;

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);
  Serial.begin(9600);
}

void loop() {
  int buttonState = digitalRead(buttonPin);

  if (buttonState == LOW) {
    Serial.println("Button Pressed");
  }

  delay(100);
}

این برنامه وضعیت دکمه را می خواند و هنگام فشرده بودن آن پیام مناسب را نمایش می دهد.

نکته مهم: چون حلقه loop() بارها اجرا می شود، تا زمانی که دکمه را نگه داشته باشید ممکن است پیام بارها در Serial Monitor چاپ شود. اگر بخواهیم فقط یک بار در لحظه فشردن دکمه واکنش نشان دهیم، باید تغییر وضعیت دکمه را تشخیص دهیم.

۴. تشخیص لحظه فشردن دکمه

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

برای این کار، وضعیت فعلی دکمه را با وضعیت قبلی آن مقایسه می کنیم.

در مدار INPUT_PULLUP:

  • وضعیت رها: HIGH
  • وضعیت فشرده: LOW
  • لحظه فشردن: تغییر از HIGH به LOW

کد تشخیص لبه فشاری

const int buttonPin = 2;

int previousState = HIGH;
int currentState;

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);
  Serial.begin(9600);
}

void loop() {
  currentState = digitalRead(buttonPin);

  if (previousState == HIGH && currentState == LOW) {
    Serial.println("Button Pressed!");
  }

  previousState = currentState;
  delay(10);
}

در این مثال، برنامه تغییر وضعیت از HIGH به LOW را تشخیص می دهد.

اما یک مشکل باقی مانده است: دکمه های مکانیکی هنگام فشرده شدن یا رها شدن ممکن است در مدت کوتاهی چند بار بین دو وضعیت جابه جا شوند. این پدیده می تواند باعث ثبت چندباره یک فشار شود.

۵. دی‌بانس (Debounce) چیست؟

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

این پدیده را لرزش کنتاکت (Contact Bounce) می نامند.

برای مثال، ممکن است برنامه به جای مشاهده یک تغییر تمیز، چنین توالی ای را بخواند:

HIGH → LOW → HIGH → LOW → HIGH → LOW

در حالی که کاربر فقط یک بار دکمه را فشرده است.

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

دی‌بانس روشی برای نادیده گرفتن این تغییرات سریع و ثبت وضعیت دکمه پس از پایدار شدن آن است.

دو روش متداول برای دی‌بانس عبارت اند از:

  • دی‌بانس نرم افزاری: استفاده از زمان و بررسی پایداری وضعیت در برنامه.
  • دی‌بانس سخت افزاری: استفاده از قطعاتی مانند مقاومت و خازن یا مدارهای مخصوص حذف لرزش.

در پروژه های معمول آردوینو، دی‌بانس نرم افزاری اغلب کافی است.

۶. دی‌بانس نرم افزاری با millis()

در این روش، زمان آخرین تغییر مشاهده شده ثبت می شود. برنامه فقط زمانی تغییر وضعیت را معتبر می داند که وضعیت جدید برای مدت مشخصی، مثلا ۳۰ میلی ثانیه، پایدار مانده باشد.

استفاده از millis() در مقایسه با delay() مزیت مهمی دارد: برنامه می تواند در فاصله انتظار همچنان به کارهای دیگر خود رسیدگی کند.

کد کامل دی‌بانس

این مثال از دکمه متصل به پایه ۲ و مقاومت پول‌آپ داخلی استفاده می کند. هر بار که دکمه به صورت معتبر فشرده شود، وضعیت LED داخلی برد تغییر می کند.

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

const unsigned long debounceDelay = 30;

int lastReading = HIGH;
int stableState = HIGH;

unsigned long lastChangeTime = 0;

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

void loop() {
  int reading = digitalRead(buttonPin);

  // ثبت زمان مشاهده تغییر اولیه
  if (reading != lastReading) {
    lastChangeTime = millis();
    lastReading = reading;
  }

  // بررسی پایداری وضعیت جدید
  if (millis() - lastChangeTime >= debounceDelay) {
    if (reading != stableState) {
      stableState = reading;

      // تشخیص لحظه فشردن دکمه
      if (stableState == LOW) {
        digitalWrite(ledPin, !digitalRead(ledPin));
      }
    }
  }
}

توضیح کد

۱. تعریف زمان دی‌بانس

const unsigned long debounceDelay = 30;

در این مثال، وضعیت باید حدود ۳۰ میلی ثانیه پایدار بماند. این مقدار برای بسیاری از دکمه های معمولی مناسب است، اما در صورت نیاز می توان آن را تغییر داد.

۲. ثبت آخرین مقدار خوانده شده

if (reading != lastReading) {
  lastChangeTime = millis();
  lastReading = reading;
}

هر بار که مقدار ورودی تغییر کند، زمان تغییر ثبت می شود.

۳. بررسی پایداری

if (millis() - lastChangeTime >= debounceDelay)

این شرط بررسی می کند که از آخرین تغییر مشاهده شده به اندازه کافی زمان گذشته باشد.

۴. تشخیص فشردن دکمه

if (stableState == LOW) {
  digitalWrite(ledPin, !digitalRead(ledPin));
}

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

نکته فنی: در این کد، مقدار reading در هر بار اجرای حلقه دوباره خوانده می شود. چون حلقه بدون delay() اجرا می شود، پس از تشخیص پایداری وضعیت، تغییر واقعی جدید نیز در دور بعدی حلقه بررسی خواهد شد.

۷. روش ساده دی‌بانس با delay()

برای برنامه های کوچک و ساده می توان پس از تشخیص فشردن دکمه، از delay() استفاده کرد تا تغییرات سریع ناشی از لرزش نادیده گرفته شوند.

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

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

void loop() {
  if (digitalRead(buttonPin) == LOW) {
    delay(30);

    if (digitalRead(buttonPin) == LOW) {
      digitalWrite(ledPin, !digitalRead(ledPin));

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

      delay(30);
    }
  }
}

این روش برای تمرین و مدارهای ساده قابل استفاده است، اما دو محدودیت دارد:

  • delay() و حلقه انتظار، اجرای سایر کارهای برنامه را متوقف می کنند.
  • اگر دکمه در زمان اجرای حلقه انتظار رها نشود، برنامه تا زمان رها شدن دکمه در همان بخش باقی می ماند.

برای پروژه هایی که هم زمان نمایشگر، ارتباط شبکه، سنسورها یا چند ورودی را مدیریت می کنند، روش مبتنی بر millis() معمولا انتخاب بهتری است.

۸. مقایسه پول‌آپ و پول‌داون

ویژگیپول‌آپپول‌داون
وضعیت ورودی هنگام رها بودن دکمهHIGHLOW
وضعیت ورودی هنگام فشردن دکمهLOWHIGH
اتصال دکمه در مدار نمونهبین ورودی و GNDبین ورودی و VCC
مقاومتاتصال به VCCاتصال به GND
امکان استفاده از مقاومت داخلیمعمولا بلهبسته به برد؛ اغلب باید مقاومت خارجی به کار برد

در بسیاری از پروژه های آردوینو Uno، استفاده از INPUT_PULLUP راهی ساده برای کاهش تعداد قطعات و سیم کشی است.

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

۹. نکات مهم و خطاهای رایج

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

اگر پایه ورودی نه به سطح منطقی بالا و نه به سطح منطقی پایین متصل باشد، ورودی شناور (Floating Input) ایجاد می شود. در این حالت ممکن است برنامه مقادیر غیرقابل پیش بینی بخواند.

راه حل: از مقاومت پول‌آپ یا پول‌داون استفاده کنید؛ برای نمونه در آردوینو Uno می توانید از INPUT_PULLUP بهره ببرید.

خطای دوم: برعکس نوشتن شرط

در مدار INPUT_PULLUP، وضعیت دکمه هنگام فشردن LOW است، نه HIGH.

راه حل: منطق برنامه را با نوع اتصال دکمه هماهنگ کنید.

خطای سوم: استفاده از شرط بدون تشخیص تغییر وضعیت

اگر فقط بنویسید:

if (digitalRead(buttonPin) == LOW) {
  counter++;
}

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

راه حل: تغییر وضعیت را تشخیص دهید و از دی‌بانس استفاده کنید.

خطای چهارم: فرض اینکه delay(30) همیشه کافی است

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

راه حل: در صورت مشاهده عملکرد نامناسب، زمان دی‌بانس را آزمایش و تنظیم کنید.

خطای پنجم: اتصال مستقیم دکمه به ولتاژ نامناسب

حداکثر ولتاژ مجاز ورودی در بردهای مختلف یکسان نیست. برای مثال، ورودی های ESP32 معمولا برای منطق ۳٫۳ ولت هستند و نباید بدون بررسی طراحی به ۵ ولت متصل شوند.

راه حل: ولتاژ منطقی و مشخصات ورودی برد را بررسی کنید. برای ESP32 معمولا اتصال دکمه بین GPIO و GND همراه با INPUT_PULLUP داخلی، در صورت پشتیبانی پایه از این قابلیت، روش مناسبی است.

خطای ششم: استفاده از پایه نامناسب

برخی پایه ها در بردهای مختلف کاربردهای ویژه دارند یا محدودیت هایی برای ورودی و خروجی دارند.

راه حل: پیش از اتصال دکمه، راهنمای پایه های همان برد را بررسی کنید.

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

برای تثبیت مطالب، تمرین های زیر را انجام دهید:

  1. دکمه ای به پایه ۲ متصل کنید و وضعیت آن را در Serial Monitor نمایش دهید.
  1. برنامه ای بنویسید که با هر بار فشردن دکمه، LED داخلی آردوینو روشن یا خاموش شود.
  1. یک شمارنده بسازید که با هر فشار معتبر دکمه فقط یک واحد افزایش یابد.
  1. برنامه دی‌بانس را یک بار با delay() و بار دیگر با millis() پیاده سازی کنید و تفاوت آنها را بررسی کنید.
  1. در صورت استفاده از دو دکمه، یکی را برای روشن کردن LED و دیگری را برای خاموش کردن آن در نظر بگیرید.

جمع بندی

برای خواندن دکمه در آردوینو از تابع digitalRead() استفاده می کنیم، اما عملکرد قابل اعتماد به نحوه اتصال ورودی نیز وابسته است.

  • INPUT_PULLUP مقاومت داخلی پول‌آپ را فعال می کند و در مدار متداول آن، دکمه بین پایه ورودی و GND قرار می گیرد.
  • در مدار پول‌داون، مقاومت پایه ورودی را در حالت آزاد به GND متصل نگه می دارد.
  • با مقایسه وضعیت فعلی و قبلی می توان لحظه فشردن دکمه را تشخیص داد.
  • دی‌بانس از ثبت چندباره یک فشار به علت لرزش مکانیکی جلوگیری می کند.
  • برای برنامه های ساده، delay() می تواند کافی باشد؛ اما در برنامه های چندوظیفه ای، روش مبتنی بر millis() معمولا مناسب تر است.

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

نمونه کد:

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

const unsigned long debounceDelay = 30;

int lastReading = HIGH;
int stableState = HIGH;
unsigned long lastChangeTime = 0;

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

void loop() {
  int reading = digitalRead(buttonPin);

  if (reading != lastReading) {
    lastChangeTime = millis();
    lastReading = reading;
  }

  if (millis() - lastChangeTime >= debounceDelay) {
    if (reading != stableState) {
      stableState = reading;

      if (stableState == LOW) {
        digitalWrite(ledPin, !digitalRead(ledPin));
      }
    }
  }
}