آردوینومتوسط۹ دقیقه مطالعهArduino IDE 2.x / Uno R3

ورودی دیجیتال — خواندن دکمهٔ فشاری

خواندن ورودی دیجیتال روی آردوینو؛ تفاوت INPUT و INPUT_PULLUP، مشکل پین شناور، سیم‌کشی دکمهٔ فشاری، لرزش کلید و حذفش با debounce، و ساخت کلید تغییر حالت LED.

آنچه در این درس می‌آموزید

  • ورودی دیجیتال را با digitalRead بخوانید و حالت HIGH و LOW را تفسیر کنید
  • مشکل پین شناور را توضیح دهید و با INPUT_PULLUP حلش کنید
  • دکمهٔ فشاری را به‌درستی سیم‌کشی کنید و لرزش کلید را با تأخیر ساده حذف کنید
  • یک کلید تغییر حالت (Toggle) بسازید که با هر فشار، LED را روشن یا خاموش کند
نویسنده: تیم داکنامه · بازبینی: ۲۰۲۶-۱۰-۳۱
فهرست مطالب

تا اینجا آردوینو فقط حرف زد: LED روشن کرد و پیام چاپ کرد. اما دستگاه واقعی باید گوش هم بدهد؛ و ساده‌ترین چیزی که می‌شنود، یک دکمهٔ فشاری (Push Button) است. در این درس ورودی دیجیتال را یاد می‌گیرید: پین فقط دو حالت می‌بیند — HIGH یا LOW — و برنامهٔ شما باید بداند هر فشار دکمه چه ولتاژی و در چه لحظه‌ای به پین می‌رساند. در میانهٔ راه دو مفهوم مهم الکترونیک هم می‌آید: «پین شناور» (Floating Pin) و «لرزش کلید» (Contact Bounce).

ابزارهای مورد نیاز

قطعه مقدار / مشخصات تعداد
برد آردوینو Uno ATmega328P ۱
دکمهٔ فشاری چهار پایه، لحیم‌نشده ۱
برد سرهم‌بندی حداقل ۱۷۰ سوراخ ۱
سیم رابط نری به نری ۲
مقاومت ۱۰ کیلواهم فقط برای حالت پول‌داون (اختیاری) ۱

LED لازم نیست؛ LED داخلی برد (پین ۱۳) کار ما را می‌کند.

ورودی دیجیتال و پین شناور

خواندن ورودی دو گام دارد: در setup() پین را با pinMode(pin, INPUT) ورودی کنید و در loop() با digitalRead(pin) بخوانید؛ نتیجه HIGH (حدود ۵ ولت) یا LOW (حدود صفر ولت) است.

اما نکتهٔ ظریف این‌جاست: پین ورودی همیشه باید به یک ولتاژ مشخص وصل باشد. اگر پین هیچ‌جا وصل نباشد — یا ورودیِ آن لحظه قطع باشد — «شناور» می‌شود: آنتن کوچکی است که نویز محیط را می‌خواند و digitalRead روی آن بی‌نظم بین HIGH و LOW می‌پرد. راه‌حل، «مقاومت کششی» (Pull-up Resistor) است: مقاومتی که پین را به‌آرامی به ۵ ولت می‌چسباند تا وقتی دکمه وصل نشده، پین حالت معلومی داشته باشد. آردوینو این مقاومت را داخل خودش دارد و لازم نیست قطعه‌ای اضافه کنید.

INPUT_PULLUP: حالت پیشنهادی

پین را با حالت INPUT_PULLUP تنظیم کنید تا مقاومت داخلی (حدود ۲۰ تا ۵۰ کیلواهم در ATmega328P) پین را به ۵ ولت بکشد:

  • دکمه رها — پین از راه مقاومت داخلی به ۵ ولت وصل است؛ digitalRead مقدار HIGH می‌دهد.
  • دکمه فشرده — پایهٔ دیگر دکمه به زمین است و پین را مستقیم به صفر ولت می‌برد؛ digitalRead مقدار LOW می‌دهد.

پس منطق برعکسِ شهود است: فشرده یعنی LOW. این «منطق معکوس» را در کد به‌خاطر بسپارید؛ بیشتر باگ‌های دکمه از انتظارِ برعکس می‌آید. مزیت این حالت، صفر قطعهٔ اضافه است — فقط دو سیم.

سیم‌کشی دکمه

دکمهٔ چهار‌پایهٔ معمولی در واقع دو جفت‌پایهٔ داخلی دارد: دو پایهٔ رو‌به‌روی هم از قبل به هم وصل‌اند. پس پایه‌های قطر مقابل همیشه در مدار شرکت می‌کنند و همان‌ها را انتخاب کنید:

از به توضیح
پین ۲ آردوینو یک پایهٔ دکمه ورودی برنامه
پایهٔ مقابل (قطر مقابل) دکمه پین GND آردوینو مسیر جریان هنگام فشار

هیچ اتصالی به ۵ ولت لازم نیست؛ مقاومت داخلی INPUT_PULLUP همان کار را می‌کند. اگر دکمه را با دو پایهٔ هم‌سو بزنید، فشردن هیچ تغییری نمی‌دهد چون آن دو از قبل متصل‌اند — علامت کلاسیک سیم‌کشی اشتباه.

لرزش کلید و debounce

دکمه قطعهٔ مکانیکی است: لحظهٔ تماس، صفحات فلزی چند بار در چند میلی‌ثانیه می‌لرزند و پین، چند بار بین HIGH و LOW می‌پرد. میکروکنترلر آن‌قدر سریع است که این لرزش را چند «فشار جدا» می‌شمارد. راه‌حل ساده، «حذف لرزش» (Debounce) است: پس از دیدن نخستین تغییر، چند ده میلی‌ثانیه صبر کنید تا لرزش بگذرد و فقط آن یک فشار را بشمارید. تأخیر ۵۰ میلی‌ثانیه برای دکمه‌های معمولی کافی است و از دید کاربر هیچ تأخیری حس نمی‌شود.

اسکچ: دکمه، LED را تغییر حالت می‌دهد

هدف: هر فشارِ دکمه، LED داخلی را از حالت قبلی برعکس کند — روشن که هست خاموش شود و برعکس. این الگو «تغییر حالت» (Toggle) نام دارد و پایهٔ هر رابط کاربری است:

button-toggle.ino
const int BUTTON_PIN = 2;  // Push button to GND on pin 2
const int LED_PIN = LED_BUILTIN;

int ledState = LOW;       // Current LED state
int lastButton = HIGH;    // Previous reading: HIGH = not pressed

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);  // Built-in pull-up: pressed = LOW
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(9600);
}

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

  // A new press: HIGH to LOW edge while the button was released before
  if (reading == LOW && lastButton == HIGH) {
    if (ledState == LOW) {
      ledState = HIGH;
      Serial.println("LED on");
    } else {
      ledState = LOW;
      Serial.println("LED off");
    }
    digitalWrite(LED_PIN, ledState);
    delay(50);  // Debounce: ignore contact bounce
  }

  lastButton = reading;  // Remember for the next loop
}

توضیح خط‌به‌خط:

  1. pinMode(BUTTON_PIN, INPUT_PULLUP); — پین ۲ را ورودی با مقاومت داخلی تنظیم می‌کند؛ رها یعنی HIGH، فشرده یعنی LOW.
  2. lastButton وضعیت قبلی را نگه می‌دارد؛ این متغیر سراسری لازم است چون مقدارش باید میان دورهای loop() بماند.
  3. شرط reading == LOW && lastButton == HIGH فقط لحظهٔ «شروع فشار» را می‌گیرد، نه ادامهٔ نگه‌داشتن را — به این ترتیب نگه‌داشتن دکمه، حالت را مدام عوض نمی‌کند.
  4. داخل شرط، ledState برعکس می‌شود، به LED اعمال می‌شود و یک پیام هم چاپ می‌شود.
  5. delay(50) لرزش کلید را می‌پوشاند؛ بعد از آن lastButton برای دور بعدی به‌روز می‌شود.

خروجی سریال مانیتور (بادریت 9600) پس از دو بار فشردن دکمه قطعی و چنین است:

خروجی کد

LED on LED off

هر فشار دقیقاً یک خط چاپ می‌کند و LED با هر خط تغییر حالت می‌دهد.

حالت پول‌داون با مقاومت خارجی

اشتباهات رایج و عیب‌یابی

نشانه علت رایج راه‌حل
digitalRead بی‌نظم و تصادفی می‌پرد پین شناور: دکمه به زمین وصل است ولی INPUT_PULLUP تنظیم نشده pinMode را به INPUT_PULLUP تغییر دهید؛ فقط INPUT برای پینی به‌کار می‌رود که همیشه به ولتاژ معلوم وصل است
دکمه برعکس کار می‌کند (رها = روشن) انتظار منطق معکوس: با INPUT_PULLUP فشرده یعنی LOW شرط را روی reading == LOW بگذارید یا مقایسه‌ها را با آگاهی از منطق معکوس بازنویسی کنید
یک فشار چند بار ثبت می‌شود لرزش کلید: پین در چند میلی‌ثانیه چند بار تغییر می‌کند پس از ثبت هر فشار delay(50) بگذارید؛ برای کارهای دقیق‌تر الگوی debounce با millis() را در ارجاع رسمی ببینید
فشردن هیچ اثری ندارد دو پایهٔ هم‌سوی دکمه را بسته‌اید (از قبل متصل‌اند) یا سیم GND جدا افتاده پایه‌های قطر مقابل را استفاده کنید و اتصال زمین را چک کنید
LED پر‌می‌زند و آرام نمی‌گیرد نبود حالت قبلی: شرط فقط reading == LOW است و نگه‌داشتن دکمه مدام تریگر می‌شود متغیر lastButton را مثل اسکچ بالا اضافه کنید تا فقط لبهٔ شروع فشار شمرده شود

تمرین‌ها

تمرین

تمرین ۱ — شمارندهٔ فشارها

اسکچ را طوری تغییر دهید که LED داخلی به‌جای تغییر حالت، با هر فشار دقیقاً ۲۰۰ میلی‌ثانیه چشمک کوتاه بزند و سریال مانیتور تعداد کل فشارها را چاپ کند.

نمایش راه‌حل

به‌جای برعکس‌کردن ledState، در هر فشار یک چشمک کوتاه می‌زنیم و شمارنده را یکی زیاد می‌کنیم؛ منطق لبه و debounce سر جایش می‌ماند:

cpp
const int BUTTON_PIN = 2;
const int LED_PIN = LED_BUILTIN;

int pressCount = 0;     // Total number of presses
int lastButton = HIGH;  // Previous reading: HIGH = not pressed

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  int reading = digitalRead(BUTTON_PIN);
  if (reading == LOW && lastButton == HIGH) {
    pressCount = pressCount + 1;
    Serial.print("Presses: ");
    Serial.println(pressCount);

    digitalWrite(LED_PIN, HIGH);
    delay(200);
    digitalWrite(LED_PIN, LOW);

    delay(50);  // Debounce: ignore contact bounce
  }
  lastButton = reading;
}

اگر دکمه را سه بار فشار دهید، خروجی سریال مانیتور قطعی و چنین است:

خروجی کد

Presses: 1 Presses: 2 Presses: 3

چشمک ۲۰۰ میلی‌ثانیه‌ای درون شرط اجرا می‌شود؛ در این ۲۵۰ میلی‌ثانیه (۲۰۰ چشمک + ۵۰ debounce) برنامه هیچ فشاری را نمی‌شمارد — برای این پروژه مشکلی نیست، اما در برنامه‌های هم‌زمان واقعی به‌جای delay از الگوی millis() استفاده می‌کنیم. ::: :::

خلاصه

  • ورودی دیجیتال با pinMode و digitalRead خوانده می‌شود و فقط دو مقدار HIGH و LOW دارد.
  • پین ورودی باید همیشه به ولتاژ معلوم وصل باشد؛ پین شناور مقادیر تصادفی می‌دهد.
  • INPUT_PULLUP مقاومت داخلی را روشن می‌کند: رها یعنی HIGH و فشرده یعنی LOW — منطق معکوس.
  • کلید مکانیکی می‌لرزد؛ تأخیر ۵۰ میلی‌ثانیه بعد از لبهٔ فشار، چندشمارشی را حذف می‌کند.
  • الگوی «لبه» با مقایسهٔ حالت فعلی و قبلی، فقط شروع فشار را ثبت می‌کند — پایهٔ toggle و شمارش رویداد.

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

منابع و ارجاع‌ها

درس‌های مرتبط