База знаний · Язык скетчей
void setup() и void loop(): структура программы Arduino
Почему setup() выполняется один раз, а loop() вечно, что значит слово void и откуда берётся ошибка redefinition.
В любом скетче Arduino есть две функции, и обе обязательны: setup() выполняется один раз при включении, а loop() — по кругу, пока есть питание. Это не соглашение и не привычка: так устроено само ядро Arduino, и обойти это нельзя.
Кто на самом деле запускает вашу программу
Настоящая точка входа лежит не в скетче, а в ядре — в файле cores/arduino/main.cpp. Там и живёт функция main(), привычная всем, кто писал на C++. Она вызывает init() (ядро настраивает таймеры, ШИМ и АЦП), потом ровно один раз ваш setup(), а дальше входит в вечный цикл for (;;), где вызывает loop() и служебный serialEventRun().
Отсюда сразу три практических вывода. Первый: пустой скетч не бывает нулевого размера — мы собрали его и получили 444 байта флеша и 9 байт ОЗУ. Это цена самого ядра: таблица векторов прерываний, стартовый код, main() с init() внутри и обработчик прерывания счётчика миллисекунд — на него одного уходит 148 байт из 444, а девять байт ОЗУ целиком заняты тремя переменными этого счётчика. Второй: «перезапустить» setup() из программы нельзя, настоящий повторный запуск бывает только при сбросе или подаче питания. Третий: пустой loop() не даёт процессору отдохнуть — он крутит вечный цикл на полной тактовой частоте, в спящий режим плата сама не уходит и энергию не экономит.
Что означает слово void
void — не тип переменной, а пометка «функция ничего не возвращает тому, кто её вызвал». Ядро объявляет обе функции именно так, поэтому подпись менять нельзя. Попробуйте написать int setup() — сборка остановится:
error: ambiguating new declaration of 'int setup()'
int setup() {
^~~~~
Arduino.h:154:6: note: old declaration 'void setup()'
void setup(void);Строка со словом note: показывает, где ядро уже объявило вашу функцию. Это же объясняет, почему опечатка в подписи (например void loop(int n)) даёт совсем другую ошибку: для компилятора это просто другая функция, а нужной ядру loop() в скетче не оказывается.
Что будет, если удалить setup() или loop()
Скетч не соберётся, но ошибка придёт не от компилятора, а от компоновщика — и покажет чужой файл, что пугает новичков:
main.cpp:43: undefined reference to `setup' ← нет функции setup()
main.cpp:46: undefined reference to `loop' ← нет функции loop()
collect2.exe: error: ld returned 1 exit status
Ошибка во время сборки: exit status 1Строки 43 и 46 — это те самые места в main.cpp ядра, где вызываются ваши функции. Лечение простое: обе функции должны быть в скетче, пусть даже с пустым телом.
Ошибка redefinition of 'void setup()'
Самая частая причина — два скетча в одной папке. Все файлы .ino из папки склеиваются в один, и получается две функции с одинаковым именем:
vtoroy.ino:1:6: error: redefinition of 'void setup()'
void setup() {
^~~~~
sketch.ino:1:6: note: 'void setup()' previously defined hereЧитайте обе строки: строка с error: называет файл, который приклеился вторым (обычно именно его и надо унести в отдельную папку), а строка с note: — где функция была объявлена изначально. Подробнее про то, как среда склеивает файлы скетча, — на странице «Скетчи Arduino».
Свои функции: зачем и как
Когда loop() разрастается, куски кода выносят в отдельные функции. Их две разновидности: void — «сделай и ничего не возвращай», и функция с типом — «сделай и верни результат» через return. Этот скетч собран, на Uno он занимает 2194 байта:
const byte LED = 13;
void setup() {
pinMode(LED, OUTPUT);
Serial.begin(9600);
blink(3, 200); // мигнуть три раза по 200 мс
Serial.println(summa(2, 3)); // напечатать 5
}
void loop() {
}
// Функция без ответа: слово void означает «ничего не возвращаю».
void blink(int times, int pause) {
for (int i = 0; i < times; i++) {
digitalWrite(LED, HIGH);
delay(pause);
digitalWrite(LED, LOW);
delay(pause);
}
}
// Функция с ответом: тип int означает «верну целое число».
int summa(int a, int b) {
return a + b;
}
Обратите внимание: blink() вызывается в setup() выше, чем описана сама функция. В обычном файле C++ это ошибка, а в .ino работает: перед сборкой среда сама дописывает объявления всех функций в начало файла. Как только тот же код переносят в собственный файл .cpp, он перестаёт собираться с сообщением was not declared in this scope — там прототип нужно писать руками.
return внутри loop()
return; в loop() не останавливает программу. Он завершает текущий проход — и ядро тут же вызывает loop() снова. Приём удобен как «сторож»: проверили условие и вышли, не углубляясь в остальной код. А вот привычное по учебникам return 0; внутри void-функции — ошибка языка, но Arduino собирает такой скетч молча: в настройках плат стоят сразу два послабления. Флаг -fpermissive понижает эту ошибку до предупреждения, а -w прячет и само предупреждение.
Почему счётчик в loop() всегда равен единице
Классическая ловушка: переменная, объявленная внутри loop(), создаётся заново на каждом проходе. Компилятор понимает это настолько буквально, что выбрасывает из прошивки саму арифметику. Лечится словом static — переменная создаётся один раз и живёт между проходами:
void setup() {
Serial.begin(9600);
}
void loop() {
// static: переменная создаётся один раз и живёт между проходами loop().
// Без этого слова счётчик каждый проход начинался бы заново с нуля.
static int count = 0;
count++;
Serial.println(count);
delay(1000);
}
Такой скетч занимает 1880 байт флеша и 190 байт ОЗУ. Важная деталь для плат с 2 КБ памяти: static не экономит ОЗУ — переменная занимает те же байты, что и глобальная, и держит их всё время работы. Разница только в том, что имя видно лишь внутри функции.
И обратная ловушка: переменная, объявленная в setup(), в loop() уже не существует. Компилятор сообщит 'pin' was not declared in this scope и иногда «предложит» вместо вашей переменной математический синус sin — не пугайтесь, просто перенесите объявление выше setup().
Проверено на нашей сборке
Все тексты ошибок и размеры получены 21 августа 2026 года: arduino-cli 1.5.2-rc.1, ядро arduino:avr 1.8.8, плата Arduino Uno. Порядок запуска приведён по исходнику ядра — файл cores/arduino/main.cpp, строки 33–51. Поведение во время работы (мигание, счёт в мониторе) мы не измеряли на живой плате, а доказали кодом ядра.
Частые вопросы
Что такое void в Arduino?
void — не тип переменной, а пометка «функция ничего не возвращает». Она стоит перед именем функции: void setup() значит «выполни и ничего не отдавай назад». Если написать int setup(), сборка упадёт с ошибкой ambiguating new declaration: ядро уже объявило setup() как void в файле Arduino.h.
Сколько раз выполняется setup()?
Ровно один раз — при подаче питания или после нажатия RESET. Это видно в исходнике ядра: файл main.cpp вызывает setup() один раз, а затем входит в бесконечный цикл с loop(). Вызвать setup() вручную из loop() можно, скетч соберётся, но это обычный вызов функции, а не перезагрузка платы.
Можно ли обойтись без loop()?
Нет. Если убрать loop(), скетч не соберётся: компоновщик выдаст undefined reference to `loop' со ссылкой на строку 46 файла main.cpp самого ядра. То же самое с setup() — там строка 43. Обе функции обязаны быть в скетче, даже пустые.
Почему счётчик в loop() всегда равен единице?
Потому что переменная, объявленная внутри loop(), создаётся заново на каждом проходе. Чтобы она помнила значение, нужно слово static (static int count = 0;) или объявление до setup(). Прошивка при этом получается одинаковая: static-переменная занимает столько же памяти, сколько глобальная.
Читайте также
Обновлено: 21 августа 2026
