ورودی دیجیتال — خواندن دکمهٔ فشاری
خواندن ورودی دیجیتال روی آردوینو؛ تفاوت 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) نام دارد و پایهٔ هر رابط کاربری است:
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
}توضیح خطبهخط:
pinMode(BUTTON_PIN, INPUT_PULLUP);— پین ۲ را ورودی با مقاومت داخلی تنظیم میکند؛ رها یعنیHIGH، فشرده یعنیLOW.lastButtonوضعیت قبلی را نگه میدارد؛ این متغیر سراسری لازم است چون مقدارش باید میان دورهایloop()بماند.- شرط
reading == LOW && lastButton == HIGHفقط لحظهٔ «شروع فشار» را میگیرد، نه ادامهٔ نگهداشتن را — به این ترتیب نگهداشتن دکمه، حالت را مدام عوض نمیکند. - داخل شرط،
ledStateبرعکس میشود، به LED اعمال میشود و یک پیام هم چاپ میشود. 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 سر جایش میماند:
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 و شمارش رویداد.
با ورودی دیجیتال، آردوینو حالا هم میشنود هم حرف میزند؛ گام بعدی دنیای ورودیهای پیوسته است: خواندن ولتاژ واقعی سنسورها روی پایههای آنالوگ.