База знаний · Язык скетчей
pinMode, digitalWrite, analogRead: команды ввода-вывода
Пять команд, которыми плата говорит с миром: три режима вывода, десять бит АЦП, ШИМ вместо напряжения и ловушка map().
Пять команд — это весь разговор платы с внешним миром. Всё остальное, от датчика температуры до Wi-Fi, библиотеки строят поверх них. Стоит разобраться, что каждая делает на самом деле, и половина загадочных поломок объясняется сама.
| Команда | Что делает | Где применяется |
|---|---|---|
pinMode(вывод, режим) | Назначает выводу роль: выход, вход или вход с подтяжкой | Один раз в setup() — до всего остального |
digitalWrite(вывод, HIGH/LOW) | Подаёт на вывод 5 В или 0 В | Светодиод, реле, транзистор, сигнал другой плате |
digitalRead(вывод) | Возвращает HIGH или LOW — что сейчас на выводе | Кнопка, концевик, датчик с цифровым выходом |
analogRead(A0…A5) | Число 0…1023 — насколько напряжение близко к опорному | Потенциометр, фоторезистор, датчик с аналоговым выходом |
analogWrite(вывод, 0…255) | Включает ШИМ: быстрое мигание с заданной долей включённого времени | Яркость светодиода, скорость мотора |
pinMode: три режима одного вывода
pinMode не «настраивает программу» — он щёлкает настоящими ключами внутри микросхемы. Вывод устроен так: к нему ведут два ключа (один к питанию, другой к земле) и отдельный резистор подтяжки. Режим решает, что из этого включено.
Самое частое недоразумение новичка — режим INPUT. Кажется, что «просто вход» и есть нормальное состояние, но такой вывод ни к чему не подключён. Напряжения на нём нет вовсе, и digitalRead возвращает то, что нанесло наводками: от соседнего провода, от руки, от сети 50 Гц. Показания скачут, и это не поломка платы.
Почему именно скачут, а не читаются нулём. Вход в этом состоянии — это сопротивление порядка сотни мегаом, то есть заряду просто некуда стечь. А граница между нулём и единицей — не точка: при питании 5 В всё, что между 1,5 и 3,0 вольта, лист данных считает неопределённым.
Одно исключение всё же есть — вывод 13 на Nano, Pro Mini и старых платах: там встроенный светодиод с резистором припаян прямо к выводу и тянет его вниз, так что читается почти всегда LOW. На Uno этого эффекта нет: светодиод развязан буфером на операционном усилителе, и вывод 13 ведёт себя как любой другой.
INPUT_PULLUP включает внутри резистор между выводом и питанием. В листе данных ATmega328/P он назван RPU, и его номинал — от 20 до 50 кОм: разброс большой, потому что это не отдельная деталь, а сопротивление открытого транзистора. Для кнопки этого достаточно с запасом.
Проверить разницу можно за минуту. Скетч ниже читает один и тот же вывод, к которому нарочно ничего не подключено, сначала как обычный вход, потом с подтяжкой:
const int PIN = 8; // к этому выводу НАРОЧНО ничего не подключено
void setup() {
Serial.begin(9600);
}
void loop() {
// Читаем один и тот же вывод дважды: сначала обычным входом,
// потом с включённой внутренней подтяжкой.
pinMode(PIN, INPUT);
delay(5);
int floating = digitalRead(PIN);
pinMode(PIN, INPUT_PULLUP);
delay(5);
int pulled = digitalRead(PIN);
Serial.print("Без подтяжки: ");
Serial.print(floating);
Serial.print(" С подтяжкой: ");
Serial.println(pulled);
delay(500);
}
В мониторе порта левое число прыгает между нулём и единицей, правое — всегда единица. Отсюда и способ подключать кнопку двумя проводами: одна нога к выводу, вторая к земле, никаких резисторов снаружи. Логика при этом переворачивается — нажатая кнопка читается как LOW:
const int BUTTON = 2;
void setup() {
// INPUT_PULLUP включает внутренний резистор: кнопку можно подключить
// просто между выводом и землёй, без лишних деталей.
pinMode(BUTTON, INPUT_PULLUP);
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(9600);
}
void loop() {
// С подтяжкой логика перевёрнута: нажата — LOW, отпущена — HIGH.
bool pressed = (digitalRead(BUTTON) == LOW);
digitalWrite(LED_BUILTIN, pressed ? HIGH : LOW);
if (pressed) {
Serial.println("Кнопка нажата");
delay(200); // простая защита от дребезга контактов
}
}
Историческая деталь, которая иногда встречается в старом коде: digitalWrite(вывод, HIGH) на выводе в режиме INPUT тоже включает подтяжку. Так писали до 2012 года, пока не появился INPUT_PULLUP. Причина не в программе, а в железе: уровнем выхода и подтяжкой управляет один и тот же бит внутри чипа. Код рабочий, но читается плохо — команда «записать» на самом деле ничего не записывает.
Из этого же общего бита растёт неприятная ловушка. Вывод, который был INPUT_PULLUP, при переводе в OUTPUT сразу становится единицей: бит-то остался установленным. Если снаружи к этому выводу подключено что-то, тянущее линию к земле, получится короткое замыкание. Лечится одной строкой, но поставить её надо до смены режима: digitalWrite(вывод, LOW), пока вывод ещё вход, гасит подтяжку, и только потом идёт pinMode(вывод, OUTPUT). Наоборот делать нельзя — между двумя строками вывод успеет выдать единицу.
Сколько тока можно взять с вывода
Здесь путаются чаще всего, потому что в интернете гуляет одно число — 40 мА. Оно есть в листе данных, но это абсолютный максимум: значение, за которым производитель обещает необратимую порчу чипа. Рабочее — вдвое меньше.
| Что ограничено | Предел | Пояснение |
|---|---|---|
| Один вывод, предел, за которым чип портится | 40 мА | Абсолютный максимум из листа данных. Это не рабочее значение |
| Один вывод, на что рассчитаны характеристики | 20 мА при 5 В | Условия испытаний. За ними напряжение на выводе уже не гарантируется |
| Группа выводов (на Uno это 5–13 вместе) | 100 мА | Групп три: 5–13, отдельно A0–A5, отдельно 0–4 со сбросом |
| Весь чип, через выводы питания и земли | 200 мА | Тоже абсолютный максимум: за ним чип портится, а не «просто не даёт» |
| Вывод 3,3 В на плате Uno | 50 мА | Это уже не вывод чипа, а отдельный источник на плате |
Забавная деталь: официальная документация Arduino сама себе противоречит. Учебная страница про цифровые выводы до сих пор пишет 40 мА, а спецификация платы Uno R3 — 20 мА. Верить надо второй: 40 — это предел разрушения из листа данных микросхемы.
И ещё одно, о чём почти не говорят: «единица» на выводе — это не ровно 5 вольт. Лист данных гарантирует не меньше 4,2 В при токе 20 мА. Чем больше тока вы берёте, тем сильнее просаживается выходное напряжение — отсюда и тусклые светодиоды в цепочках, собранных без транзистора.
Практический вывод: обычный светодиод с резистором — это 10–15 мА, и таких можно повесить несколько. А вот мотор, реле, лента адресных светодиодов и даже мощный зуммер с вывода не питаются: им нужен отдельный источник, а вывод только управляет транзистором или готовым драйвером. Подробнее — в разборе питания Arduino.
digitalWrite медленнее, чем кажется
Выглядит команда как одно действие, но внутри ядро успевает многое: трижды заглянуть в таблицы соответствия «номер вывода — порт, бит, таймер», проверить, не занят ли вывод ШИМ, и на всякий случай выключить ШИМ, а потом на время записи запретить прерывания.
Мы измерили это, собрав два одинаковых скетча и разобрав готовую прошивку: функция digitalWrite занимает 144 байта и 66 команд процессора. Прямая запись в регистр порта — PORTB |= (1 << PB0) — превращается в одну команду. Вся прошивка при этом весит 734 байта против 450.
Для мигалки разница не важна: 66 команд на 16 мегагерцах — это считаные микросекунды. Она становится важной, когда вывод дёргают десятки тысяч раз в секунду: программная передача данных, генерация сигнала, опрос ленты светодиодов. Скетч ниже покажет разницу на вашей собственной плате. Он рассчитан на Uno, Nano и Pro Mini: только там вывод 8 — это тот самый нулевой бит порта B. На Mega выводы разложены иначе, и сравнение получится не про то:
const int PIN = 8; // это ножка PB0 — нулевой бит порта B
void setup() {
Serial.begin(9600);
pinMode(PIN, OUTPUT);
}
void loop() {
// Замер 1: тысяча переключений обычной командой.
unsigned long start = micros();
for (int i = 0; i < 1000; i++) {
digitalWrite(PIN, HIGH);
digitalWrite(PIN, LOW);
}
unsigned long slow = micros() - start;
// Замер 2: то же самое прямой записью в регистр порта.
// Годится для Uno, Nano и Pro Mini: там вывод 8 — это как раз PB0.
start = micros();
for (int i = 0; i < 1000; i++) {
PORTB |= (1 << PB0);
PORTB &= ~(1 << PB0);
}
unsigned long fast = micros() - start;
Serial.print("digitalWrite: ");
Serial.print(slow);
Serial.print(" мкс напрямую: ");
Serial.print(fast);
Serial.println(" мкс");
delay(2000);
}
Вывод простой: начинать надо с digitalWrite. Она понятная, переносится между платами, сама гасит ШИМ и не даст промахнуться мимо вывода. Прямая запись — приём для того редкого случая, когда упёрлись в скорость и точно знаете, какой бит какого порта дёргаете.
Кстати, про запрет прерываний внутри digitalWrite часто думают наоборот. Он нужен не для защиты вашего кода, а потому что ядро читает-меняет-записывает регистр через указатель, и эту тройку прерывание разорвать может. Одиночная команда sbi, в которую превращается прямая установка бита, неделима сама по себе и ни в какой защите не нуждается.
Побочный эффект, о котором стоит знать заранее: digitalRead тоже выключает ШИМ. Прочитали вывод 9 после analogWrite(9, 128) — яркость погасла. А вот pinMode ШИМ не трогает, хотя интуиция подсказывает обратное. Обе строчки видны в исходнике ядра.
analogRead: десять бит и сто микросекунд
analogRead не измеряет вольты. Он сравнивает напряжение на входе с опорным (на Uno это питание, 5 В) и возвращает, какая это доля, числом от 0 до 1023. Уровней 1024, поэтому одна ступенька — 4,883 мВ, а вольты считают так: значение умножить на 5 и разделить на 1024. В примерах Arduino часто делят на 1023 — разница меньше одной ступеньки, но в листе данных формула именно с 1024.
void setup() {
Serial.begin(9600);
}
void loop() {
int raw = analogRead(A0); // 0…1023 — что «видит» плата
// map() переводит один диапазон в другой: удобнее думать в процентах.
int percent = map(raw, 0, 1023, 0, 100);
Serial.print("Сырое значение: ");
Serial.print(raw);
Serial.print(" Это ");
Serial.print(percent);
Serial.println("%");
delay(300);
}
Замер не мгновенный. Преобразование занимает 13 тактов самого АЦП, а ядро специально замедляет АЦП в 128 раз — до 125 кГц, потому что точность гарантируется только в диапазоне 50–200 кГц. Это записано прямо в исходнике ядра. Итого около 104 микросекунд на одно измерение: примерно девять тысяч замеров в секунду, и то если больше ничего не делать. Самое первое измерение после включения АЦП идёт дольше — 25 тактов вместо 13.
Ещё одна тонкость из листа данных: АЦП рассчитан на источники с сопротивлением примерно до 10 кОм — с такими о времени выборки можно вообще не думать. Внутри АЦП есть конденсатор, который надо успеть зарядить; если сигнал приходит через резистор в сотню килоом, зарядиться тот не успевает, и показания уползают в сторону предыдущего замера. Отсюда народный приём «читать датчик дважды и брать второе значение» — он лечит следствие, а не причину.
И про Nano отдельно: выводы A6 и A7 у него подключены только к АЦП. Они не умеют быть цифровыми, digitalRead и pinMode для них не работают, а подтяжки у них нет вовсе.
analogWrite — это не напряжение
Название сбивает с толку дважды. Во-первых, никакого аналогового напряжения плата не выдаёт: analogWrite включает ШИМ — вывод продолжает переключаться между нулём и пятью вольтами, меняется только доля времени, когда он наверху. Во-вторых, к аналоговым выводам A0–A5 и к analogRead эта команда отношения не имеет вовсе — так и написано в официальной справке.
Светодиод, мотор, нагреватель усредняют это сами: глаз не успевает за сотнями переключений в секунду, а мотор — за счёт инерции. А вот вход другой платы, аналоговая схема или ЦАП такое не примут: им нужен фильтр. Мультиметр покажет что-то среднее только потому, что усредняет он сам.
const int LED = 9; // вывод с поддержкой ШИМ (значок ~ на плате)
void setup() {
pinMode(LED, OUTPUT);
}
void loop() {
// analogWrite умеет не только «включено/выключено», а 256 уровней яркости.
for (int level = 0; level <= 255; level++) {
analogWrite(LED, level);
delay(5);
}
for (int level = 255; level >= 0; level--) {
analogWrite(LED, level);
delay(5);
}
}
Выводов с ШИМ на Uno всего шесть, и на этом обычно заканчиваются попытки сделать цветную подсветку. Выход — адресные светодиоды: там один вывод управляет хоть сотней ламп, и у каждой свой цвет. Как это устроено, разобрано в статье «Адресные светодиоды WS2812B».
ШИМ умеют не все выводы, а частота у них разная — и это не мелочь, если вы фильтруете сигнал или управляете мотором. Частоты не взяты из воздуха: они вычисляются из настроек таймеров, которые ядро делает при старте.
| Выводы Uno | Кто их качает | Частота | Кто ещё на них претендует |
|---|---|---|---|
| 5 и 6 | таймер 0 | 976,56 Гц | Он же считает millis() и delay() |
| 9 и 10 | таймер 1 | 490,20 Гц | Его забирает библиотека Servo |
| 3 и 11 | таймер 2 | 490,20 Гц | Его забирает tone() |
Откуда именно эти числа. Таймер 0 ядро настраивает в режим быстрого ШИМ с предделителем 64: 16 000 000 / (64 × 256) = 976,5625 Гц. Таймеры 1 и 2 работают в режиме с точной фазой, где полный цикл вдвое длиннее: 16 000 000 / (64 × 510) = 490,196 Гц. Обе строчки видны в файле ядра wiring.c.
Если вызвать analogWrite на выводе без ШИМ, ошибки не будет — и это главная ловушка. Ядро молча подставит digitalWrite: всё, что меньше 128, погасит вывод, всё, что от 128 и выше, включит его на полную. Плавной яркости не получится, а причину искать негде.
Два края обрабатываются особо. analogWrite(вывод, 0) и analogWrite(вывод, 255) вообще не включают ШИМ, а вызывают обычный digitalWrite. Полезное следствие: между 254 и 255 разница не «одна ступенька», а «ШИМ и постоянное питание». И ещё одна приятная мелочь — analogWrite сам делает pinMode(вывод, OUTPUT), так что отдельно объявлять вывод не обязательно.
Кто ещё претендует на таймеры
Таймеров всего три, а желающих больше. tone() берёт таймер 2 и на время звука ломает ШИМ на выводах 3 и 11. Библиотека Servo берёт таймер 1 — прощай ШИМ на 9 и 10. Таймер 0 трогать нельзя вовсе: на нём держатся millis() и delay().
const int LED = 11; // ШИМ на этом выводе качает таймер 2
const int BUZZER = 8;
void setup() {
Serial.begin(9600);
pinMode(LED, OUTPUT);
}
void loop() {
analogWrite(LED, 128);
Serial.println("Только свет: светодиод горит вполнакала");
delay(2000);
// tone() забирает себе таймер 2 — тот самый, что обслуживает выводы 3 и 11.
tone(BUZZER, 440);
Serial.println("Пошёл звук: яркость на выводе 11 сбилась");
delay(2000);
// После noTone яркость сама не вернётся — её нужно задать заново.
noTone(BUZZER);
analogWrite(LED, 128);
Serial.println("Звук выключен, яркость задана заново");
delay(2000);
}
Обратите внимание на две вещи. Первая: зуммер в скетче висит на выводе 8, а ломается ШИМ на выводе 11 — таймер занимается целиком, независимо от того, где именно играет звук. Вторая: после noTone() яркость сама не возвращается, её нужно задать заново. Это не ошибка библиотеки — таймер просто отдали обратно в том состоянии, в котором забрали.
Про tone() полезно знать ещё две вещи. Одновременно на Uno может звучать только один тон: вызов на втором выводе не сработает, пока не сделан noTone(). И ниже 31 герца звук не получится — дальше не хватает делителей таймера.
Самый неприятный случай — когда в проекте есть и сервопривод, и звук. Тогда заняты таймеры 1 и 2, и под analogWrite остаются только выводы 5 и 6.
map(): две ловушки
map() переводит число из одного диапазона в другой, и это честно написано в справке. Не написано другое: считает он целыми числами и остаток отбрасывает, а результат не ограничивает.
void setup() {
Serial.begin(9600);
}
void loop() {
int raw = analogRead(A0); // 0…1023
// Ловушка первая: map() считает целыми числами и остаток отбрасывает.
int percent = map(raw, 0, 1023, 0, 100);
// Ловушка вторая: map() НЕ ограничивает результат. Стоит датчику выйти
// за 200…800, и проценты уедут в минус или за сотню.
int narrow = map(raw, 200, 800, 0, 100);
int safe = constrain(narrow, 0, 100);
Serial.print(raw);
Serial.print(" -> ");
Serial.print(percent);
Serial.print("% узкий диапазон: ");
Serial.print(narrow);
Serial.print(" после constrain: ");
Serial.println(safe);
delay(300);
}
Первое проявляется, когда диапазоны не делятся нацело: 1023 ступеньки, разложенные на 100 процентов, дают проценты, которые перескакивают. Второе куда опаснее. Если вы сузили диапазон под свой датчик — скажем, 200…800, — а датчик выдал 150, map() послушно вернёт отрицательное число. Дальше оно уедет в analogWrite, и яркость прыгнет вместо того чтобы погаснуть. Лечится обёрткой constrain.
На других платах не всё так же
Команды называются одинаково, но за ними разное железо. Самое коварное — LED_BUILTIN на ESP8266: там светодиод подключён наоборот, и digitalWrite(LED_BUILTIN, LOW) его зажигает.
| Плата | analogRead | analogWrite | LED_BUILTIN |
|---|---|---|---|
| Arduino Uno, Nano | 0…1023 (10 бит), шесть входов | 0…255, шесть выводов | 13 |
| Arduino Mega 2560 | 0…1023 (10 бит), шестнадцать входов | 0…255, пятнадцать выводов (2–13 и 44–46) | 13 |
| ESP8266 (NodeMCU, Wemos) | 0…1023, но вход один и всего до 3,2 В | 0…255 в ядрах 3.x, раньше было 0…1023 | 2, и горит он от LOW |
| ESP32 | 0…4095 (12 бит), шкала неровная | 0…255 в новых ядрах, раньше был ledcWrite | чаще 2, зависит от платы |
У ESP8266 аналоговый вход всего один, и сам чип принимает не больше одного вольта — на готовых платах вроде NodeMCU перед ним стоит делитель, поэтому подавать можно около 3,2 В. У ESP32 входов много, но шкала заметно неровная, а половина входов перестаёт работать при включённом Wi-Fi. Про это подробно — на странице ESP8266.
Частые ошибки
| Что видно | Причина | Что делать |
|---|---|---|
| Кнопка «нажимается» сама, показания скачут | Вход без подтяжки | pinMode(вывод, INPUT_PULLUP) и кнопку между выводом и землёй |
| Светодиод горит еле-еле, хотя резистор правильный | Забыт pinMode(вывод, OUTPUT) — светодиод питается через подтяжку | Через подтяжку идут десятые доли миллиампера. Добавить pinMode в setup() |
| Плата греется или перезагружается под нагрузкой | С вывода берут больше 20 мА | Мощную нагрузку — через транзистор или готовый драйвер |
| analogWrite не даёт яркость: свет либо погас, либо на полную | Вывод без значка ~ — ШИМ на нём нет, и ядро молча зовёт digitalWrite | Перенести на 3, 5, 6, 9, 10 или 11 |
| Вывод скакнул в HIGH и закоротил цепь при переводе в OUTPUT | Он был INPUT_PULLUP — бит подтяжки остался и стал уровнем выхода | Сначала digitalWrite(вывод, LOW), и только потом pinMode(вывод, OUTPUT) |
| Проценты уходят в минус или за сотню | map() не ограничивает результат | Обернуть в constrain(значение, 0, 100) |
| Заиграл зуммер — сбилась яркость на выводе 3 или 11 | tone() забрал таймер 2 | Перенести светодиод на 5, 6, 9 или 10 |
| digitalRead(A6) на Nano всегда возвращает одно и то же | A6 и A7 у Nano — только аналоговые входы | Взять любой другой вывод или читать через analogRead |
Все скетчи из этой статьи собраны компилятором и лежат в каталоге готового кода. Если команда подчёркивается в редакторе красным или сборка ругается на незнакомое слово — загляните в разбор ошибок компиляции.
Частые вопросы
Чем INPUT_PULLUP отличается от INPUT?
INPUT_PULLUP включает внутри микросхемы резистор между выводом и питанием. По листу данных ATmega328/P его номинал — от 20 до 50 кОм. Без подтяжки вход висит в воздухе и digitalRead возвращает случайные значения: наводок хватает, чтобы вывод «дребезжал» сам по себе. С подтяжкой логика переворачивается: отпущенная кнопка читается как HIGH, нажатая на землю — как LOW.
Сколько тока можно взять с вывода Arduino?
Лист данных ATmega328/P даёт 40 мА как абсолютный максимум одного вывода — это значение, за которым чип портится, а не рабочее. Рабочим считается ток условий испытаний: 20 мА при питании 5 В. Есть и второй предел: сумма токов группы выводов не должна превышать 100 мА, а через выводы питания и земли всего чипа — 200 мА. Мотор, реле и лента светодиодов питаются не от вывода, а от отдельного источника.
Почему analogWrite не даёт настоящее напряжение?
Потому что это не цифро-аналоговый преобразователь, а ШИМ: вывод очень быстро переключается между 0 и 5 вольтами, и меняется только доля времени наверху. Светодиод, мотор и нагреватель усредняют это сами, а вольтметр покажет что-то среднее лишь потому, что усредняет он. Питать от такого вывода вход другой платы или аналоговую схему нельзя — нужен фильтр или настоящий ЦАП.
Какие выводы Arduino Uno умеют ШИМ и с какой частотой?
Шесть: 3, 5, 6, 9, 10 и 11 — на плате они помечены значком ~. Частоты разные, и это видно прямо в исходнике ядра: выводы 5 и 6 качает таймер 0 в режиме быстрого ШИМ с предделителем 64, отсюда 16 000 000 / (64 × 256) = 976,56 Гц. Выводы 9, 10 и 3, 11 работают от таймеров 1 и 2 в режиме с точной фазой: 16 000 000 / (64 × 510) = 490,20 Гц.
Почему analogRead возвращает 0…1023?
У ATmega328/P десятибитный АЦП: он делит измеряемый диапазон на 1024 ступеньки, поэтому наибольшее число — 1023, а шаг при опоре 5 В равен примерно 4,9 мВ. Одно измерение занимает 13 тактов АЦП, а ядро ставит АЦП предделитель 128, то есть 125 кГц — около 104 микросекунд на замер. У ESP32 другой АЦП: 12 бит и диапазон 0…4095.
Почему map() возвращает не то, что ожидалось?
У map() две ловушки. Первая: он считает целыми числами и дробную часть отбрасывает, поэтому 1023 ступеньки, разложенные на 100 процентов, дают заметные скачки. Вторая: map() не ограничивает результат. Если вы сжали диапазон датчика, скажем, до 200…800, а датчик выдал 150, то на выходе получится отрицательное число. Лечится обёрткой constrain.
Почему после tone() сбилась яркость светодиода?
Потому что tone() на Uno забирает себе таймер 2 — тот самый, который делает ШИМ на выводах 3 и 11. Пока играет звук, analogWrite на этих двух выводах работать не будет, а после noTone() яркость нужно задать заново. Такая же история у библиотеки Servo: она занимает таймер 1 и ломает ШИМ на выводах 9 и 10.
Читайте также
Обновлено: 25 августа 2026
