Перейти к основному содержимому

Циклы

Повторяющийся цикл опроса и управления роботом

Робот редко выполняет действие только один раз. Он снова читает датчики, уточняет обстановку и обновляет команды исполнительным устройствам. Чтобы не копировать одну и ту же строку десятки раз, в 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(), если интервалы намного меньше полного периода переполнения.

Цикл опроса робота

Робот, следующий по линии, может выполнять короткий повтор:

  1. прочитать левый датчик;
  2. прочитать правый датчик;
  3. выбрать команду;
  4. передать её драйверу моторов;
  5. быстро вернуться к первому шагу.

Сам повтор обеспечивает 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, вычислите среднее и выведите только итог. Подумайте, какой тип нужен для суммы, чтобы она не переполнилась.

Проверьте себя

  1. Что называют одной итерацией?
  2. Какие три части находятся в заголовке for?
  3. Почему для массива из четырёх элементов условие часто выглядит как i < 4?
  4. Чем while отличается от do...while?
  5. Из-за чего цикл становится бесконечным случайно?
  6. Почему длинный delay() мешает мобильному роботу?
  7. Когда loop() достаточно без дополнительного бесконечного цикла?
  8. Для чего сравнивают разность значений millis()?
Ориентиры для самопроверки

Сначала ответьте без подсказки. Ответ можно считать полным, если вы:

  • формулируете основную мысль своими словами;
  • называете важные условия, ограничения или меры безопасности;
  • для схемы, кода или расчёта показываете ход решения и ожидаемый результат.

Если один из пунктов объяснить не получается, найдите соответствующую главу статьи, перечитайте её и повторите ответ.

Словарь статьи

  • Цикл — конструкция для повторного выполнения блока кода.
  • Итерация — один проход тела цикла.
  • Тело цикла — команды, выполняемые при каждом проходе.
  • Условие — логическое выражение, разрешающее следующую итерацию.
  • Счётчик — переменная, меняющая номер текущей итерации.
  • Блокирующий код — код, который надолго не возвращает управление остальной программе.
  • Граница массива — предел допустимых индексов его элементов.
  • millis() — функция Arduino, возвращающая число миллисекунд с момента запуска программы в пределах типа результата.

Связанные темы

Источники