Доброго времени суток! В самом разгаре курс по программированию Arduino и, как и было запланировано в предыдущей части, сегодня будем разбираться с работой порта Ардуино в режиме входа. Для тестирования используем простенькую кнопку, в скетче же будем определять, нажата она или нет. Принцип работы, схема подключения, примеры программ – ко всему этому незамедлительно и переходим 👍
Теоретическая часть.
Arduino. Порты ввода-вывода в режиме входа.
Итак, для того, чтобы все было максимально понятно и логично, будем придерживаться привычной структуры, а значит начнем с теоретических нюансов и электрической схемы. И лишь поняв, что, зачем и как мы будем делать, перейдем к программной части статьи.
Как мы уже выяснили ранее, цифровой пин потому и именуется цифровым, что может оперировать двумя возможными значениями - логическим нулем и единицей. А значит именно эти два варианта мы можем прочитать с входного порта. Остается прочертить взаимосвязь между этими цифровыми значениями и значениями напряжения на входе в Вольтах. А взаимосвязь здесь проста, я нарисовал табличку для наглядной и приятной глазу демонстрации:
Состояние входа | Напряжение |
---|---|
Логический 0 | от -0.5 В до 1.5 В (0.3 * Vcc) |
Логическая 1 | от 3.0 В (0.6 * Vcc) до 5.5 В (Vcc + 0.5 В) |
То есть имеем две области:
- Первая - этот интервал соответствует логическому 0
- Вторая - при этих значениях напряжений со входа гарантированно прочитаем единицу
Сразу бросается в глаза, что данные диапазоны не покрывают все возможные значения напряжений. Есть ряд промежуточных значений, в которых результат будет неоднозначен. Не буду углубляться, чтобы не перегружать эту статью, запомним тот факт, что есть некая "серая" зона, в которой вход может находиться и в одном, и в другом состоянии при одном и том же значении напряжения.
При этом приведенные значения могут варьироваться с изменением температуры, графики можно найти в даташите на контроллер.
В данном уроке я использую плату Arduino Uno, микроконтроллер которой работает с уровнями в 5 В. Именно поэтому данная величина повсеместно и фигурирует. Соответственно, в случае платы с уровнями в 3.3 В, ситуация будет уже другая, верхнее значение будет составлять именно 3.3 В, а не 5, также изменятся и диапазоны. Эти данные можно найти в документации, либо пишите в комментарии, либо на форум, я всегда по возможности стараюсь оперативно ответить.
Вот так вот все просто, займемся принципиальной электрической схемой. Отправной точкой послужит непосредственно кнопка, которую и будем подключать. Рассмотрим несколько распространенных вариантов:
В первом варианте исполнения все максимально просто - при нажатии происходит замыкание двух контактов, пока кнопка в покое - контакты разомкнуты. Второй вариант ничуть не сложнее, суть точно такая же, просто контакты соединены между собой внутри. То есть по умолчанию 1 и 4, а также 2 и 3 соединены между собой, при нажатии кнопки все ровно также, контакт 1 замыкается с контактом 2 (аналогично 3 и 4), по итогу при нажатой кнопке замкнуты будут все. Смотрим на наглядной схеме:
Все это по существу - одно и то же. Перейдем к конкретике, то есть к непосредственному подключению кнопки к Arduino Uno, вариантов может быть несколько:
В первом случае при нажатии кнопки происходит замыкание цифрового пина Ардуино на землю (GND). А это значит, что на входе микроконтроллера будет 0 В - низкий уровень. Напротив, во втором случае ситуация диаметрально противоположная: кнопка замыкает на 5 В, а значит на цифровом пине будет высокий уровень. Казалось бы, на этом разговор об электрической схеме можно заканчивать, но в реальности существует несколько дополнительных моментов, о которых зачастую бывает не сказано, что я категорически не одобряю.
Первый заключается в том, что в тот момент, когда кнопка не нажата, пин платы никуда не подключен (по сути висит в воздухе), а это значит, что сигнал на цифровом входе может приобретать хаотично-случайные значения. Связано это с наводками, которые как раз и приводят к тому, что на неподключенном пине может появиться как низкий, так и высокий уровень.
Решение здесь классическое и простое, а именно использовать подтяжку.
Подтягивающий резистор.
Конечно же, досконально разберем, что это означает. Итак подтяжка осуществляется при помощи дополнительного резистора (подтягивающий резистор), который подключается одним концом к линии, а другой либо к 5 В, либо к GND. Первый вариант - подтяжка вверх, второй - подтяжка вниз, смотрим на наглядном примере:
Возьмем первую из приведенных схем. Пусть кнопка не нажата, тогда получим следующую картину: из-за высокого внутреннего сопротивления входа микроконтроллера ток (I_R) между точками 1-2 очень мал. Из чего следует, что и напряжение на резисторе U_R также мало, поскольку:
U_R = I_R \cdot R_1
А из этого, в свою очередь, вытекает то, что добавленный подтягивающий резистор (R_1 ) гарантирует нам, что на входе будет высокий уровень (5 В). Это и называется подтяжкой вверх. Мысленно нажимаем кнопку и, конечно же, обстановка меняется, на входе получаем стабильные 0 В:
В этом и заключается весь смысл идеи: при наличии подтягивающего резистора на цифровом входе возможны два четких варианта:
- высокий уровень - кнопка не нажата
- низкий уровень - кнопка нажата
Аналогичные процессы протекают и при подтяжке вниз, только в данном случае резистор подключается к GND, а нажатая кнопка замыкает на 5V. И в данном случае:
- высокий уровень - кнопка нажата
- низкий уровень - кнопка не нажата
Таким образом, подтягивающий резистор в данной схеме просто необходим. Номинал (значение сопротивления) не обязательно должен иметь какую-то конкретную величину. Вдаваться в нюансы, когда ставить сопротивление больше, а когда меньше, мы в этой статье не будем, возьмем «традиционное» для такого случая значение в 10 КОм:
R_1 = 10 \medspace КОм
Кроме того, Arduino Uno, а точнее микроконтроллер AVR, на базе которого и построена плата, имеет встроенные подтягивающие резисторы, которые можно легко и непринужденно задействовать (об этом в практической части), что даст возможность сэкономить и место на плате, и затраты на покупку резисторов. Но я лично почти всегда предпочитаю использовать внешние.
Токоограничивающий резистор.
При подключении кнопки есть и еще один нюанс, хотя, казалось бы, операция максимально примитивна. Рассмотрим потенциальную ситуацию…
Допустим по какой-то причине порт оказался настроен не как вход, а как выход (ошибка в скетче), а кнопка подключена по приведенной чуть выше схеме. В скетче на выход подается высокий уровень, при нажатии же кнопки получаем замыкание на землю:
Как видите, в точке 2 потенциал равен 5 В, а в точке 1: 0 В. При этом сопротивление проводов между этими точками мало, а это автоматически означает, что по цепи потечет ток, значение которого будет велико (из-за того, что R_{wire} мало, а оно в знаменателе дроби, закон Ома):
I = \frac{5 \medspace В}{R_{wire}}
Результат максимально прост и непригляден - порт микроконтроллера будет неотвратимо уничтожен. Поэтому для исключения этого печального явления добавляется еще один резистор (R_2 ):
Сопротивление этого резистора выбирается так, чтобы значение тока не превысило максимально допустимое для порта. Допустим, хотим ограничить ток величиной в 20 мА, тогда:
R_2 = \frac{5 \medspace В}{20 \medspace мА} = 250 \medspace Ом
При возникновении описанной выше ситуации с ошибочным скетчем и нажатой кнопкой, получим, что ток в цепи не превысит допустимых пределов, что позволит сохранить контроллер в целости и сохранности. На самом деле, этот резистор ставится достаточно редко, потому что предполагается, что в программе не будет допущено такой ошибки. То есть порт будет настроен как вход (что верно), а не как выход (что ошибочно). Тем не менее это следует держать в уме и при необходимости учитывать 👌
И на этом беседу об электрических аспектах подключения кнопки к Ардуино можно считать успешно завершенной ) Двигаемся дальше.
Схема подключения кнопки к Arduino Uno.
Подводим итог, схема приобрела такой вид:
А при использовании макетной платы коммутация может производиться так:
Практическая часть.
Функция digitalRead().
Для настройки порта на работу в режиме входа используется все та же функция, что мы использовали ранее: pinMode()
. Единственное отличие, что второй аргумент будет отличаться:
pinMode(3, INPUT);
Напомню, что для порта в режиме выхода было так:
pinMode(3, OUTPUT);
И здесь же можно активировать встроенный в микроконтроллер подтягивающий резистор, тогда конфигурация превратится в:
pinMode(3, INPUT_PULLUP);
Что примечательно, имеется возможность задействовать только подтяжку вверх, только так и никак иначе. Первый же аргумент везде по прежнему - номер пина платы Ардуино (в данном случае D3). С этим понятно, осталось понять, как получить значение на входе в скетче. И тут понадобится функция:
digitalRead(pin)
Один аргумент функции - номер пина, значение на входе которого нас интересует. Возвращаемое же значение может иметь два разных значения: LOW
означает, что на входе низкий уровень, HIGH
- высокий. Все очень логично и понятно 👍 Переходим к парочке примеров для закрепления разобранного.
Базовый пример скетча.
В базовом примере мы просто анализируем состояние входа и, если кнопка нажата, зажигаем светодиод, иначе - гасим. Со светодиодом мы разобрались в предыдущей части курса, поэтому просто без колебаний используем уже изученное. С учетом добавившейся кнопки схема трансформируется в:
И код скетча:
// Светодиод подключен к D2 int ledPin = 2; // Кнопка - к D3 int buttonPin = 3; void setup() { // D2 работает в качестве выхода pinMode(ledPin, OUTPUT); // D3 - в качестве входа pinMode(buttonPin, INPUT); } void loop() { // Текущее состояние кнопки int currentButtonState = digitalRead(buttonPin); if (currentButtonState == LOW) { // Кнопка нажата, зажигаем светодиод digitalWrite(ledPin, HIGH); } else { // Кнопка не нажата, гасим светодиод digitalWrite(ledPin, LOW); } }
Здесь все максимально подробно и развернуто, на деле же можно значительно уменьшить размер скетча следующим образом:
// Светодиод подключен к D2 int ledPin = 2; // Кнопка - к D3 int buttonPin = 3; void setup() { // D2 работает в качестве выхода pinMode(ledPin, OUTPUT); // D3 - в качестве входа pinMode(buttonPin, INPUT); } void loop() { // Текущее состояние кнопки int currentButtonState = digitalRead(buttonPin); digitalWrite(ledPin, !currentButtonState); }
Если currentButtonState
равно HIGH
, то соответственно !currentButtonState
= LOW
, таким образом можно считанное из digitalRead()
значение инвертировать и сразу без лишних манипуляций передать в digitalWrite()
. Компилируем и запускаем - все работает по плану: кнопка не нажата - светодиод не горит, кнопка нажата - горит.
Расширенный пример. Подключение кнопки и светодиода к Arduino.
Давайте разберем еще один пример, чуть усложненный, но тоже простой. Пусть при нажатой кнопке светодиод мигает с периодом, равным 1 секунде, а при ненажатой - 250 мс. Для этого добавим переменную currentPeriod
, которая будет соответствовать нужному значению полупериода:
// Светодиод подключен к D2 int ledPin = 2; // Кнопка - к D3 int buttonPin = 3; // Полупериод, если кнопка нажата int halfPeriodPressed = 500; // Полупериод, если кнопка не нажата int halfPeriodNotPressed = 125; // Актуальный период int currentPeriod = halfPeriodNotPressed; void setup() { // D2 работает в качестве выхода pinMode(ledPin, OUTPUT); // D3 - в качестве входа pinMode(buttonPin, INPUT); } void loop() { // Текущее состояние кнопки int currentButtonState = digitalRead(buttonPin); if (currentButtonState == LOW) { // Кнопка нажата currentPeriod = halfPeriodPressed; } else { // Кнопка не нажата currentPeriod = halfPeriodNotPressed; } // Зажигаем светодиод digitalWrite(ledPin, HIGH); delay(currentPeriod); // Гасим светодиод digitalWrite(ledPin, LOW); delay(currentPeriod); }
Пример, конечно, придуман исключительно в целях задействовать все пройденное в предыдущих уроках в одном скетче. На практике применимо не особо, все на примитивных задержках с delay()
, тем не менее со своей демонстрационной задачей пример справляется отлично.
Снова прошиваем микроконтроллер и наблюдаем, что все работает в точности так, как и было запланировано! Поэтому мы имеем полное право завершить сегодняшний урок, до встречи в следующих частях, в ближайшей из которых займемся явлением, связанным с дребезгом контактов 🤝