Цифровые пины
В уроке про возможности микроконтроллера мы обсуждали такое понятие, как GPIO – входы-выходы общего назначения, которые позволяют читать и выдавать цифровой сигнал. Давайте посмотрим на распиновке, какие пины умеют так делать.
Нумерация пинов
AVR (Arduino Nano)
- Нет прямой нумерации GPIO, всё поделено на порты и пины (не указаны на распиновке), о них мы поговорим в отдельном уроке.
- На плате выведенные GPIO подписаны как Dцифра и Aцифра и в программе мы можем обращаться к ним по этой нумерации:
- К пинам GPIO, подписанным как D (D0-D13), можно обращаться по номеру: D2 – просто 2 .
- К пинам GPIO, подписанным как A (A0-A5), можно обращаться по подписи на плате: A2 – A2 . Также нумерация A пинов продолжает нумерацию D пинов по порядку, то есть A0 это 14 , A1 это 15 .. A5 – 19 .
ESP8266 (Wemos Mini)
- Имеется прямая нумерация GPIO (номера подписаны на распиновке зелёным), по этим номерам можно обращаться к пинам в программе как к цифрам: GPIO5 – просто 5 .
- На плате пины подписаны как Dцифра. Эта нумерация не совпадает с номерами GPIO, но по ней также можно обращаться к пинам в программе: D1 это D1 , и этот же пин – просто 5 , как номер GPIO (см. распиновку выше).
Режимы работы пинов
Цифровой пин может находиться в двух состояниях, вход и выход. В режиме входа пин может считывать напряжение от 0 до напряжения питания МК, а в режиме выхода – выдавать такое же напряжение. Режим работы выбирается при помощи функции pinMode(pin, mode) , где pin это номер пина, а mode это режим:
- INPUT – вход
- OUTPUT – выход
- INPUT_PULLUP – подтянутый к питанию вход
Если со входом/выходом всё понятно, то с подтяжкой давайте разберёмся. В режиме входа пин МК не подключен никуда и ловит из воздуха всякие наводки, получая практически случайное значение. Для задания пину “состояния по умолчанию” используют подтяжку резистором к земле или питанию. Режим INPUT_PULLUP включает встроенную в микроконтроллер подтяжку пина к питанию при помощи внутреннего резистора. Подробнее об этом, со схемами и примерами я рассказывал в начале вот этого видео урока.
По умолчанию (при запуске программы) все пины сконфигурированы как входы (INPUT)
Вывод цифрового сигнала
Цифровой пин в режиме выхода ( OUTPUT ) может генерировать цифровой сигнал, т.е. выдавать напряжение. Так как понятие “цифровой” обычно связано с двумя состояниями, 0 и 1, цифровой пин тоже может выдать 0 или 1, точнее сигнал низкого или высокого уровня:
- Сигнал низкого уровня это 0V, пин подключается к GND микроконтроллера.
- Сигнал высокого уровня подключает пин к VCC микроконтроллера, то есть к питанию.
Если вы вспомните урок по питанию платы, то поймёте, что сигнал высокого уровня на цифровом пине будет варьироваться в зависимости от того, от какого напряжения питается плата. При питании напрямую от источника 5V на пине будет 5V, при питании от USB с потерей на защитном диоде мы получим около 4.7V.
Самый главный момент касательно цифровых пинов: микроконтроллер – логическое устройство, которое создано для управления другими устройствами при помощи логических сигналов. Логическое – означает не силовое, то есть питать от пина МК нельзя ничего мощнее светодиода или слабой микросхемы:
- Для AVR Arduino рекомендуемый ток с пина GPIO – не более 20 мА, максимальный ток – 40 мА.
- Для esp8266 максимальный ток с пина GPIO – не более 12 мА.
Вернёмся к вопросу подачи цифрового сигнала: для этого у нас есть функция digitalWrite(pin, value) :
- pin – пин GPIO (нумерацию смотри выше).
- value – уровень сигнала: HIGH высокий, LOW низкий. Также можно использовать цифры 1 и 0 соответственно.
Для вывода сигнала пин должен быть переведён в режим OUTPUT при помощи pinMode()
Пример, в котором пины инициализируются как выходы и на них подаётся сигнал (на примере Arduino Nano):
void setup() < pinMode(10, OUTPUT); // D10 как выход pinMode(A3, OUTPUT); // A3 как выход pinMode(19, OUTPUT); // A5 как выход (Nano/UNO) digitalWrite(10, HIGH); // высокий сигнал на D10 digitalWrite(A3, 1); // высокий сигнал на A3 digitalWrite(19, 1); // высокий сигнал на A5 >void loop() <>
Чтение цифрового сигнала
Цифровой пин может измерять напряжение, но сообщить он может только о его отсутствии (сигнал низкого уровня, LOW ) или наличии (сигнал высокого уровня, HIGH ), причём отсутствием напряжения считается промежуток от 0 до ~VCC/2 Вольт, а от VCC/2 до VCC микроконтроллер считает за наличие сигнала высокого уровня.
Нельзя подавать на цифровой пин (да и на любой другой пин тоже) напряжение выше напряжения питания микроконтроллера
Для чтения уровня сигнала на пине используется функция digitalRead(pin) , где пин – номер GPIO (нумерацию смотри выше).
Для чтения сигнала пин должен быть переведён в режим INPUT при помощи pinMode()
Следующий код будет выводить в порт сигнал на пине D5. Если подключить его проводом к VCC – получим 1 , если к GND – получим 0 .
void setup() < Serial.begin(9600); >void loop()
Видео
Полезные страницы
- Набор GyverKIT – большой стартовый набор Arduino моей разработки, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
- Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
- Полная документация по языку Ардуино, все встроенные функции и макросы, все доступные типы данных
- Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
- Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
- Поддержать автора за работу над уроками
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])
Аналоговые пины
В прошлом уроке мы разобрали измерение и вывод цифрового сигнала, а в этом разберём аналоговый сигнал. Зачем нужно читать аналоговый сигнал? Микроконтроллер может выступать в роли вольтметра, измерять собственное напряжение питания, например от аккумулятора, может измерять ток через шунт (если вы знаете закон Ома), можно измерять сопротивление, а также работать с потенциометрами (крутильными, линейными, джойстиками), которые являются очень удобными органами управления.
В уроке про возможности микроконтроллера мы обсуждали аналоговые входы, т.е. входы, подключенные к АЦП – аналогово-цифровому преобразователю (ADC). Взглянем на распиновку популярных плат (Arduino Nano и Wemos Mini):
Пины, на которых выведен ADC, могут измерять аналоговый сигнал. На плате Nano это пины, маркированные буквой А (A0–A7), а у esp8266 такой пин всего один – A0.
Чтение сигнала
“Аналоговые” пины могут принимать напряжение от 0V (GND) до опорного напряжения и преобразовывать его в цифровое значение, просто в какие-то условные единицы. АЦП на AVR и esp8266 имеет разрядность в 10 бит, т.е. мы получаем измеренное напряжение в виде числа от 0 до 1023 .
Функция, которая оцифровывает напряжение, называется analogRead(pin) . Она принимает в качестве аргумента номер аналогового пина и возвращает оцифрованное напряжение. Сам пин должен быть сконфигурирован как INPUT (вход). Нумерация:
- Arduino Nano:
- Просто номером А-пина: A0 – 0
- Как на плате: A0 – A0
- Порядковым номером GPIO: А0 – 14 , A1 – 15 .. А7 – 21
- Просто номером А-пина: A0 – 0
- Как на плате: A0 – A0
Пример, опрашивающий пин А0:
int value1 = analogRead(0); // считать напряжение с пина A0 int value2 = analogRead(A0); // считать напряжение с пина A0 int value3 = analogRead(14); // считать напряжение с пина A0
Хранить полученное значение разумно в переменной типа int , потому что значение варьируется от 0 до 1023.
Нельзя подавать на аналоговый пин напряжение выше напряжения питания МК. Через ограничивающий резистор (~10k) – можно, но всё равно не рекомендуется этого допускать.
Потенциометры
Аналоговые пины очень часто используются при работе с потенциометрами (переменный резистор). При помощи полученного значения можно влиять на ход работы программы, менять какие-то настройки и тому подобное. У потенциометра всегда три ноги: две крайние и одна центральная. Всё вместе это представляет собой делитель напряжения, который и позволяет менять напряжение в диапазоне 0-VCC:
К Arduino потенциометр подключается следующим образом: средний вывод на любой A-пин, крайние – на GND и питание. От порядка подключения GND и питания зависит направление изменения значения. Что касается сопротивления, то читай заметку по делителям напряжения ниже в этом уроке. Чаще всего для МК ставят потенциометры с сопротивлением 10 кОм, но диапазон в принципе очень широк: от 1 кОм до 100 кОм. Чем больше, тем более шумным будет приходить сигнал, а если брать меньше – пойдут потери тока в нагрев потенциометра, а это никому не нужно.
Опорное напряжение (для AVR Arduino)
Опорное напряжение играет главную роль в измерении аналогового сигнала, потому что именно от него зависит максимальное измеряемое напряжение и вообще возможность и точность перевода полученного значения 0-1023 в Вольты. Изучим функцию analogReference(mode) , где mode:
- DEFAULT : опорное напряжение равно напряжению питания МК. Активно по умолчанию
- INTERNAL : встроенный источник опорного на 1.1V (для ATmega168 или ATmega328P) и 2.56V (на ATmega8)
- INTERNAL1V1 : встроенный источник опорного на 1.1V (только для Arduino Mega)
- INTERNAL2V56 : встроенный источник опорного на 2.56V (только для Arduino Mega)
- EXTERNAL : опорным будет считаться напряжение, поданное на пин AREF
После изменения источника опорного напряжения (вызова analogReference() ) первые несколько измерений могут быть нестабильными. Значение 1023 функции analogRead() будет соответствовать выбранному опорному напряжению или напряжению выше его.
В режиме DEFAULT мы можем оцифровать напряжение от 0 до напряжения питания VCC. Если напряжение питания 4.5 Вольта, и мы подаём 4.5 Вольт – получим оцифрованное значение 1023. Если подаём 5 Вольт – опять же получим 1023, т.к. выше опорного. Это правило работает и дальше, главное не превышать 5.5 Вольт. Как измерять более высокое напряжение, читайте ниже.
Что касается точности: при питании от 5V и режиме DEFAULT мы получим точность измерения напряжения (5 / 1024) ~4.9 милливольт. Поставив INTERNAL мы можем измерять напряжение от 0V до 1.1V с точностью (1.1 / 1024) ~0.98 милливольт. Весьма неплохо, особенно если баловаться с делителем напряжения.
Что касается внешнего источника опорного напряжения: нельзя подавать напряжение меньше 0V (отрицательное) или выше 5.5V в качестве внешнего опорного в пин AREF. Также при подключении внешнего опорного напряжения нужно вызвать analogReference(EXTERNAL) до первого вызова функции analogRead() (начиная с запуска программы), иначе можно повредить микроконтроллер!
Чтобы “на лету” переключаться между внутренними и внешним опорными, можно подключить его на AREF через резистор на ~5 кОм. Вход AREF имеет собственное сопротивление в 32 кОм, поэтому реальное опорное будет вычисляться по формуле REF = V * 32 / (R + 32), где R – сопротивление резистора (кОм), через которое подключено опорное напряжение V (Вольт). Например для 2.5V получим 2.5 * 32 / (32 + 5) = ~2.2V реальное опорное.
Измерение напряжения
0-5 Вольт
Простой пример, как измерить напряжение на аналоговом пине и перевести его в Вольты. Плата питается от 5V.
float voltage = (float)(analogRead(0) * 5.0) / 1024;
Таким образом переменная voltage получает значение в Вольтах, от 0 до 5. Чуть позже мы поговорим о более точных измерениях при помощи некоторых хаков. Почему мы делим на 1024, а не на 1023 , ведь максимальное значение измерения с АЦП составляет 1023? Ответ можно найти в даташите:
АЦП при преобразовании отнимает один бит, т.е. 5.0 Вольт он в принципе может измерить только как 4.995, что и получится по формуле выше: 1023 * 5 / 1024 == 4.995.. . Таким образом делить нужно на 1024.Сильно больше 5 Вольт
Для измерения постоянного напряжения больше 5 Вольт нужно использовать делитель напряжения на резисторах (Википедия). Схема подключения, при которой плата питается от 12V в пин Vin и может измерять напряжение источника (например, аккумулятора):
Код для перевода значения с analogRead() в Вольты с учётом делителя напряжения:// GND -- [ R2 ] -- A0 -- [ R1 ] -- VIN #define VREF 5.1 // точное напряжение на пине 5V (в данном случае зависит от стабилизатора на плате Arduino) #define DIV_R1 10000 // точное значение 10 кОм резистора #define DIV_R2 4700 // точное значение 4.7 кОм резистора void setup() < float voltage = (float)analogRead(0) * VREF * ((DIV_R1 + DIV_R2) / DIV_R2) / 1024; >void loop() <>
Как выбрать/рассчитать делитель напряжения?
- Согласно даташиту на ATmega, сумма R1 + R2 не рекомендуется больше 10 кОм для достижения наибольшей точности измерения. В то же время через делитель на 10 кОм будет течь ощутимый ток, что критично для автономных устройств (читай ниже). Если девайс работает от сети или от аккумулятора, но МК не используется в режиме сна – ставим делитель 10 кОм и не задумываемся. Также рекомендуется поставить конденсатор между GND и аналоговым пином для уменьшения помех.
- Если девайс работает от аккумулятора и микроконтроллер “спит”: пусть аккумулятор 12V, тогда через 10 кОм делитель пойдёт ток 1.2 мА. Сам микроконтроллер в режиме сна потребляет ~1 мкА, что в тысячу раз меньше! На самом деле можно взять делитель с гораздо бОльшим суммарным сопротивлением (но не больше 20 МОм, внутреннего сопротивления самого АЦП), но обязательно поставить конденсатор на ~0.1 мкФ между аналоговым пином и GND (вот здесь проводили эксперимент). Таким образом например при при R1+R2 = 10 МОм (не забыть про конденсатор) ток через делитель будет 1.2 мкА, что уже гораздо лучше!
- Коэффициент делителя (не тот, который в Википедии) равен (R1 + R2) / R2 . Коэффициент должен быть таким, чтобы при делении на него измеряемого напряжения не получилось больше напряжения питания МК. У меня в примере (10 + 4.7) / 4.7 ~ 3.13 . Я хочу измерять литиевый аккумулятор с максимальным напряжением 12.8 Вольт. 12.8 / 3.13 ~ 4 Вольта – отлично. Например для измерения 36 Вольт я бы взял делитель с плечами 100к и 10к.
- Можно воспользоваться онлайн-калькулятором.
Сильно меньше 5 Вольт
Для более точных измерений маленького напряжения можно подключить пин AREF к источнику низкого опорного напряжения (об этом было выше), чтобы “сузить” диапазон работы АЦП. Источник может быть как внешний, так и внутренний, например изменив опорное на внутреннее 1.1V ( analogReference(INTERNAL) ) можно измерять напряжение от 0 до 1.1 Вольта с точностью 1.1/1024 ~ 1.01 мВ.
Видео
Полезные страницы
- Набор GyverKIT – большой стартовый набор Arduino моей разработки, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
- Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
- Полная документация по языку Ардуино, все встроенные функции и макросы, все доступные типы данных
- Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
- Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
- Поддержать автора за работу над уроками
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])
Arduino INPUT_PULLUP Explained (pinMode)
What is the Arduino INPUT_PULLUP option for the pinMode function?
In this tutorial I will show you different examples, using an Arduino board and a simple push button, to explain what INPUT_PULLUP does, and how to use it in your Arduino programs.
And… Let’s get started!
Table of Contents
Quick recap about pinMode
With Arduino you can use digital pins to either read (binary) data from a sensor, or write (binary) data to an actuator.
It’s quite simple. Either you set the pin as:
You are learning how to use Arduino to build your own projects?
Check out Arduino For Beginners and learn step by step.
- OUTPUT: this is to write data to an actuator, for example an LED.
- INPUT: in this case you’re going to read data from the sensor. The value you’ll get will be HIGH or LOW (binary).
And… There’s also a 3rd option: INPUT_PULLUP. This option is the same as INPUT (you read data from the sensor), but in addition to that, an internal pull up resistor – between 20k and 50k Ohm – is enabled, to keep the signal HIGH by default.
What does that mean?
Well, let’s see with 3 different circuits doing the same thing: reading data from a push button.
The problem: floating pin
Let’s consider this circuit.
This is quite simple: you plug one leg of the push button to the ground (GND), and another one – on the other side of the button – to a digital pin.
Let’s write a simple code to print the push button’s value on the Serial Monitor.
#define BUTTON_PIN 4 void setup() < Serial.begin(9600); pinMode(BUTTON_PIN, INPUT); >void loop()
Nothing fancy here, the 2 importants parts are:
- pinMode(BUTTON_PIN, INPUT); : we set pin 4 to INPUT so we can read data from the button.
- digitalRead(BUTTON_PIN) : this will give us the current state of the button, either LOW or HIGH.
If we run this program, and open the Serial Plotter (Tools > Serial Plotter, or CTRL+SHIFT+L), here is what we get, without pressing the button.
When we press the button, the value is always LOW, but when we release it it’s quite random: sometimes HIGH, sometimes LOW, and it moves a lot.
We see this because the voltage for the button is floating between 0 and 5V.
If the voltage is below a certain amount of V, the Arduino will read LOW. And if it is above a certain amount of V, the Arduino will read HIGH. As there is no internal or external voltage reference for the push button, the value will oscillate a lot in a random way.
And as you can foresee, we can’t rely on this data to take decisions inside our Arduino program.
What we need to do is to “force” the default state (button not pushed) to be close to HIGH or LOW, which will make it quite stable. Then, when we press the button the state will simply go to the opposite of the default state.
Using Arduino INPUT_PULLUP
Let’s use the exact same circuit, but this time with INPUT_PULLUP instead of INPUT for the pinMode function.
#define BUTTON_PIN 4 void setup() < Serial.begin(9600); pinMode(BUTTON_PIN, INPUT_PULLUP); >void loop()
If you run this code and open the Serial Plotter, you’ll see that the default value is 1 (HIGH). When you press the button the state directly goes to 0 (LOW) and comes back to HIGH when you release the button.
Well now it’s much better. Problem solved!
When you set the mode to INPUT_PULLUP, an internal resistor – inside the Arduino board – will be set between the digital pin 4 and VCC (5V). This resistor – value estimated between 20k and 50k Ohm – will make sure the state stays HIGH. When you press the button, the states becomes LOW.
Using an external resistor instead of Arduino INPUT_PULLUP
Pull up resistor
Instead of using the internal pull up resistor from your Arduino board, you could decide to create the circuit yourself and add an external pull up resistor.
Your circuit will look like this.
Here I have simply added a 10k Ohm resistor between one leg of the button (same side as the data side – digital pin 4) and VCC (5V).
Now, with this circuit you don’t need to enable the internal pull up anymore. So, in your program use pinMode(BUTTON_PIN, INPUT); instead of pinMode(BUTTON_PIN, INPUT_PULLUP); .
When you run the program you will have the same result: the default state for the button is HIGH, and when you press it, its states goes to LOW.
Pull down resistor
This is another option you can choose, which is also a quite popular one: add a pull down resistor. Thus, the default button’s state will be LOW, and when you press it it will become HIGH.
Contrary to the pull up resistor, you can’t set this up with just the code, you’ll have to use an external resistor.
Here’s the circuit.
The circuit is quite similar to the previous one, but pay attention to the differences:
- The 10k Ohm resistor is between one leg and the ground (GND).
- The wire for digital pin 4 is on the same side as the ground.
- The other side of the button is connected to VCC (5V) directly.
When you run the program using pinMode(BUTTON_PIN, INPUT); , you’ll get:
Great! Now the default value when the button is not pressed is LOW. And in this example when I pressed the button the state rose to HIGH.
Conclusion – Arduino INPUT_PULLUP recap
In this tutorial you’ve seen how to properly use pull up and pull down resistors for your Arduino sensors, and when to use the INPUT_PULLUP option for the pinMode function.
To recap, you have 3 choices, depending on the default state you want for the button:
- Add an external pull down resistor, so the default state is LOW.
- Add an external pull up resistor, so the default state is HIGH.
- Use the Arduino internal pull up resistor. The behavior will be the same as for option no 2.
There is no better or worse choice, it depends on the available hardware components you have and some requirements specific to your project. Also it’s a matter of preference: do you want the default state (when not pressed) to be LOW or HIGH? – knowing that this can easily be corrected on the software side.
The most important thing to pay attention to is not to have a floating state for any of your component: this will make any measurement wrong.
Did you find this tutorial useful?
Do you want to learn Arduino from scratch?
If yes, this course is for you:
LEARN HOW TO PROGRAM ROBOTS
Did you find this tutorial useful?
Do you want to become better at programming robots, with Arduino, Raspberry Pi, or ROS2?
If yes, subscribe to receive exclusive content and special offers!