Циклы

Робот редко выполняет действие только один раз. Он снова читает датчики, уточняет обстановку и обновляет команды исполнительным устройствам. Чтобы не копировать одну и ту же строку десятки раз, в C++ используют циклы — конструкции для управляемого повторения блока кода.
Цикл повторяет команды, пока действует заданное правило. for удобен, когда число повторов известно; while — когда продолжение зависит от условия; do...while гарантирует хотя бы одно выполнение.
Зачем это нужно
Представим робота с линейкой из четырёх светодиодов. Для проверки монтажа нужно включить каждый канал по очереди. Четыре почти одинаковых вызова можно записать вручную, но при двенадцати каналах код станет длинным и неудобным. Цикл меняет только номер пина и выполняет общую команду для всей последовательности.
Повторение встречается и в других задачах:
- собрать несколько показаний датчика и усреднить их;
- сделать ограниченное число шагов калибровки;
- ждать отпускания кнопки;
- перебрать элементы массива;
- плавно изменить ШИМ-команду;
- повторять сигнальную последовательность заданное число раз.
Цикл полезен, когда повтор действительно имеет общее правило. Если при каждой итерации выполняется совсем другая операция, насильно объединять их не стоит.
Главная идея
У любого цикла есть две части: условие продолжения и тело. Тело содержит повторяемые команды, а условие решает, запускать ли следующую итерацию.
проверка условия → выполнение тела → изменение состояния → новая проверка
Одна итерация — один проход через тело. Состояние должно меняться так, чтобы цикл когда-нибудь завершился, если он не задуман как постоянный.

На схеме внешняя дуга обозначает повторение этапов «прочитать — решить — воздействовать», а не провод или траекторию. Питание датчиков и драйвера намеренно опущено: изображение показывает программный порядок обмена.
Цикл for: известное число повторов
for объединяет инициализацию счётчика, условие и изменение счётчика:
const int ledPins[] = {3, 4, 5, 6};
void setup() {
for (int i = 0; i < 4; i++) {
pinMode(ledPins[i], OUTPUT);
}
}
void loop() {
for (int i = 0; i < 4; i++) {
digitalWrite(ledPins[i], HIGH);
delay(120);
digitalWrite(ledPins[i], LOW);
}
}
Разберём заголовок for (int i = 0; i < 4; i++):
| Часть | Когда выполняется | Роль |
|---|---|---|
int i = 0 | один раз перед стартом | создаёт счётчик |
i < 4 | перед каждой итерацией | разрешает или запрещает проход |
i++ | после каждой итерации | увеличивает счётчик на единицу |
Индексы массива начинаются с нуля, поэтому допустимы 0, 1, 2, 3. Условие i <= 4 попробовало бы обратиться к несуществующему пятому элементу. Такая ошибка называется выходом за границы массива и может привести к непредсказуемой работе.
Если размер массива должен вычисляться автоматически, можно записать:
const int ledPins[] = {3, 4, 5, 6};
const int ledCount = sizeof(ledPins) / sizeof(ledPins[0]);
void setup() {
for (int i = 0; i < ledCount; i++) {
pinMode(ledPins[i], OUTPUT);
}
}
void loop() {
}
Теперь добавление ещё одного пина в массив не требует вручную исправлять границу цикла.
Цикл while: повторение по условию
while сначала проверяет условие и только затем выполняет тело. Если условие ложно с самого начала, тело не выполнится ни разу.
const int buttonPin = 7;
const int ledPin = 9;
void setup() {
pinMode(buttonPin, INPUT_PULLUP);
pinMode(ledPin, OUTPUT);
}
void loop() {
while (digitalRead(buttonPin) == LOW) {
digitalWrite(ledPin, HIGH);
delay(40);
}
digitalWrite(ledPin, LOW);
}
При INPUT_PULLUP нажатая кнопка, соединяющая вход с GND, читается как LOW. Пока кнопка удерживается, светодиод включён. Однако задержка и сам цикл временно не позволяют программе заняться другой логикой. Для робота с несколькими параллельными задачами лучше хранить состояние и выполнять короткую проверку в loop(), не оставаясь внутри долгого while.
Цикл называют блокирующим, если программа надолго остаётся внутри него и не обслуживает остальные задачи. Он не обязательно ошибочен, но его последствия нужно учитывать.
do...while: хотя бы один проход
do...while проверяет условие после тела. Поэтому тело выполняется минимум один раз:
int sample = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
do {
sample = analogRead(A0);
Serial.println(sample);
delay(20);
} while (sample < 100);
delay(500);
}
Такой вариант подходит для первичного измерения с последующей проверкой. Но пример намеренно простой: реальные диапазоны analogRead() зависят от платы, разрешения АЦП и опорного напряжения. Порог 100 нужно подбирать по измерениям конкретного датчика.
Как выбрать конструкцию
| Ситуация | Подходящая конструкция | Почему |
|---|---|---|
| включить 8 каналов по очереди | for | число итераций известно |
| повторять, пока кнопка нажата | while | продолжение задаёт состояние входа |
| сначала измерить, потом решить, повторять ли | do...while | нужен минимум один проход |
| постоянно обслуживать робота | короткий loop() | среда Arduino уже повторно вызывает loop() |
loop() сам по себе — часть среды выполнения Arduino: после setup() он вызывается снова и снова. Поэтому дополнительный бесконечный while (true) внутри loop() обычно не нужен и может затруднить работу библиотек или фоновых механизмов конкретной платы.
Управление временем без длинного delay
В учебном мигании delay() делает последовательность понятной, но полностью приостанавливает основной поток программы. Робот в это время не проверяет кнопку и не обновляет датчик. Для параллельных действий удобно проверять прошедшее время через millis():
const int ledPin = 9;
unsigned long previousToggle = 0;
bool ledState = false;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
const unsigned long now = millis();
if (now - previousToggle >= 250) {
previousToggle = now;
ledState = !ledState;
digitalWrite(ledPin, ledState ? HIGH : LOW);
}
// Здесь могут находиться короткие опросы датчиков и кнопок.
}
Вычитание now - previousToggle корректнее прямого сравнения будущего времени: беззнаковая арифметика позволяет пережить переполнение счётчика millis(), если интервалы намного меньше полного периода переполнения.
Цикл опроса робота
Робот, следующий по линии, может выполнять короткий повтор:
- прочитать левый датчик;
- прочитать правый датчик;
- выбрать команду;
- передать её драйверу моторов;
- быстро вернуться к первому шагу.
Сам повтор обеспечивает loop(), а не вложенный бесконечный цикл:
const int leftSensorPin = A0;
const int rightSensorPin = A1;
void updateRobot(int leftValue, int rightValue) {
// Здесь будет выбор команды драйверу по двум измерениям.
}
void setup() {
}
void loop() {
const int leftValue = analogRead(leftSensorPin);
const int rightValue = analogRead(rightSensorPin);
updateRobot(leftValue, rightValue);
}
Этот каркас не содержит порогов и команд моторам: их нужно выбрать по плате, датчикам и драйверу. Главное здесь — короткая итерация без долгого ожидания.
Типичные ошибки
| Ошибка | Что происходит | Как проверить |
|---|---|---|
| счётчик не меняется | цикл может не завершиться | найти строку, меняющую состояние условия |
| неверный знак в границе | лишняя или пропущенная итерация | выписать допустимые значения счётчика |
точка с запятой после while (...) | получается пустое тело | проверить фигурные скобки и форматирование |
длинный delay() в каждой итерации | робот поздно реагирует | измерить период реакции через Serial или светодиод |
| изменение массива за его пределами | непредсказуемое состояние | связать границу с реальным размером массива |
Что запомнить о циклах
- Одна итерация — один проход тела.
- Условие должно соответствовать реальной цели повторения.
forпоказывает счётчик и границу в одной строке.whileможет не выполниться ни разу,do...whileвыполнится хотя бы раз.- Бесконечный или долгий блокирующий цикл мешает роботу обслуживать другие события.
loop()уже создаёт постоянный верхнеуровневый повтор программы Arduino.
Практика
Задание 1
Соберите массив из пяти допустимых цифровых пинов своей платы. Напишите цикл настройки всех пинов как выходов и последовательного включения. Не копируй вызовы вручную.
Задание 2
Измените последовательность так, чтобы счётчик шёл от последнего элемента к первому. Запишите допустимые значения индекса до загрузки скетча.
Задание 3
Создайте неблокирующее мигание через millis() и добавьте одновременный опрос кнопки. Проверьте, реагирует ли кнопка во время мигания.
Задание 4
Снимите десять показаний аналогового датчика циклом for, вычислите среднее и выведите только итог. Подумайте, какой тип нужен для суммы, чтобы она не переполнилась.
Проверьте себя
- Что называют одной итерацией?
- Какие три части находятся в заголовке
for? - Почему для массива из четырёх элементов условие часто выглядит как
i < 4? - Чем
whileотличается отdo...while? - Из-за чего цикл становится бесконечным случайно?
- Почему длинный
delay()мешает мобильному роботу? - Когда
loop()достаточно без дополнительного бесконечного цикла? - Для чего сравнивают разность значений
millis()?
Сначала ответьте без подсказки. Ответ можно считать полным, если вы:
- формулируете основную мысль своими словами;
- называете важные условия, ограничения или меры безопасности;
- для схемы, кода или расчёта показываете ход решения и ожидаемый результат.
Если один из пунктов объяснить не получается, найдите соответствующую главу статьи, перечитайте её и повторите ответ.
Словарь статьи
- Цикл — конструкция для повторного выполнения блока кода.
- Итерация — один проход тела цикла.
- Тело цикла — команды, выполняемые при каждом проходе.
- Условие — логическое выражение, разрешающее следующую итерацию.
- Счётчик — переменная, меняющая номер текущей итерации.
- Блокирующий код — код, который надолго не возвращает управление остальной программе.
- Граница массива — предел допустимых индексов его элементов.
millis()— функция Arduino, возвращающая число миллисекунд с момента запуска программы в пределах типа результата.
Связанные темы
- Переменные — как хранить счётчик и состояние.
- Конструкция ветвления — как выбирать действие внутри итерации.
- Функции — как вынести повторяемые операции в отдельные блоки.
- analogRead: обработка датчика — как собирать и фильтровать измерения.
Источники
- Arduino Language Reference — структура языка и функции среды Arduino.
- Arduino CLI: Getting started — минимальный скетч с
setup()иloop(). - Arduino: for — синтаксис цикла
for. - Arduino: while — синтаксис цикла
while. - Arduino Built-in Examples — примеры Blink Without Delay и Smoothing.
- C++ reference: statements — стандартные управляющие конструкции C++.