Курс Arduino с нуля · Управляем светом
Урок 6. Бегущий огонёк из пяти светодиодов
Собираем цепочку из пяти светодиодов и знакомимся с циклом for и массивом: программа из сорока строк сжимается до десяти.
Что понадобится
- Плата Uno из набора
- Кабель USB
- Макетная плата
- 5 светодиодов (можно разных цветов)
- 5 резисторов 470 Ом
- 6 проводов «папа-папа»
Все детали есть в обучающем наборе, по которому идёт курс.
Один светодиод слушается — значит, послушаются и пять. Сегодня соберём бегущий огонёк: светящаяся точка побежит по цепочке слева направо, потом обратно. Такие «бегущие» гирлянды вы видели на вывесках, в рекламных табло и на бортах машин из старых фильмов.
Но главное в этом уроке — не огонёк, а приём, который сделает вашу программу втрое короче. Сначала мы честно напишем всё руками и своими глазами увидим, насколько это неудобно. А потом познакомимся с циклом — и сорок строк превратятся в десять.
Собираем цепочку из пяти светодиодов
Схема — это просто пять копий вчерашней цепи. Каждый светодиод получает свой вывод платы и свой резистор, а «земля» у всех общая:
Резистор после светодиода? Да, в этой схеме резистор стоит не до светодиода, а после — и это совершенно правильно. Ток в цепочке везде одинаковый, и неважно, в каком месте стоит «сужение». Так собрано и в брошюре набора. Выбирайте то, что удобнее разместить на макетке.
Отключите USB перед сборкой. Деталей стало больше — тем важнее собирать на обесточенной схеме и проверять всё до включения.
Собирайте по одному каналу за раз и повторяйте четыре знакомых шага: провод с вывода → длинная ножка светодиода в тот же столбик → короткая ножка в соседний столбик → резистор из этого столбика в шину «−». Собрали первый — проверьте глазами, потом принимайтесь за второй.
Перед включением проверьте: все пять светодиодов смотрят длинной ножкой в сторону своего провода (спил на юбке — со стороны резистора); ножки соседних светодиодов не попали в один столбик; синий провод из GND платы идёт в шину «−».
Шаг 1. Пишем «в лоб» — и страдаем
Начнём честно: пять светодиодов — пять команд настройки и пять блоков «зажечь-подождать- погасить». Наберите (или скопируйте) и прошейте:
void setup() {
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
pinMode(9, OUTPUT);
pinMode(10, OUTPUT);
}
void loop() {
digitalWrite(6, HIGH);
delay(150);
digitalWrite(6, LOW);
digitalWrite(7, HIGH);
delay(150);
digitalWrite(7, LOW);
digitalWrite(8, HIGH);
delay(150);
digitalWrite(8, LOW);
digitalWrite(9, HIGH);
delay(150);
digitalWrite(9, LOW);
digitalWrite(10, HIGH);
delay(150);
digitalWrite(10, LOW);
}
Работает! Огонёк бежит. Но посмотрите на программу: больше тридцати строк, и все они почти одинаковые — меняется только номер вывода. А теперь представьте, что светодиодов не пять, а двадцать. Или что нужно поменять скорость: придётся править delay в пяти местах и ни одного не пропустить.
Это верный признак, что пора звать на помощь цикл.
Шаг 2. Цикл for: «повтори это столько-то раз»
Цикл — это способ сказать плате: «выполни вот эти команды несколько раз, каждый раз чуть меняя число». Начнём с самого простого — заменим пять строк настройки:
void setup() {
// Пять раз выполняем одну строку: pin пробегает значения 6, 7, 8, 9, 10.
for (int pin = 6; pin <= 10; pin++) {
pinMode(pin, OUTPUT);
}
}
void loop() {
}
Одна строка вместо пяти. Разберём её по частям — в скобках у for ровно три кусочка, разделённых точкой с запятой:
int pin = 6— с чего начать. Мы завели переменную с именемpinи положили в неё 6. Переменная — это коробочка с именем, в которой лежит число; в отличие отconstиз прошлого урока, её значение можно менять.pin <= 10— условие продолжения. Перед каждым повторением плата проверяет: «значение всё ещё меньше или равно десяти?» Если да — выполняет тело цикла ещё раз. Если нет — выходит из цикла и идёт дальше.pin++— что сделать после каждого повторения. Две плюсика — это короткая запись «прибавь единицу». Так значение шагает 6 → 7 → 8 → 9 → 10.
А в фигурных скобках — тело цикла: команды, которые повторяются. Внутри них слово pin каждый раз означает очередное число. Именно поэтому одна строка pinMode(pin, OUTPUT) настраивает все пять выводов.
Почему это работает. Плата выполняет тело цикла с pin = 6, потом прибавляет единицу, проверяет условие, выполняет с pin = 7… и так до десяти. На одиннадцатом значении условие pin <= 10 становится ложным — цикл заканчивается. Никакой магии, просто очень быстрый повтор.
Шаг 3. Тот же приём — для самого огонька
Теперь свернём и основную часть. Вся программа целиком:
void setup() {
for (int pin = 6; pin <= 10; pin++) {
pinMode(pin, OUTPUT);
}
}
void loop() {
// Огонёк бежит слева направо: 6 → 7 → 8 → 9 → 10.
for (int pin = 6; pin <= 10; pin++) {
digitalWrite(pin, HIGH); // зажгли очередной
delay(150);
digitalWrite(pin, LOW); // и погасили, прежде чем взяться за следующий
}
}
Двенадцать строк вместо тридцати с лишним — и делают они ровно то же самое. Прошейте и сравните с первым вариантом: огонёк бежит точно так же. Только теперь, чтобы изменить скорость, достаточно поправить одно число.
Шаг 4. Массив: список выводов
У цикла со счётчиком есть слабое место: он умеет только «по порядку» — 6, 7, 8, 9, 10. А если светодиоды воткнуты в выводы 3, 5, 6, 9, 11? Или нужно, чтобы огонёк бежал не по порядку, а вразнобой?
Для таких случаев есть массив — пронумерованный список значений под одним именем:
// Список выводов: теперь порядок огонька задаётся здесь, а не в цикле.
const int LEDS[] = {6, 7, 8, 9, 10};
const int COUNT = 5; // сколько светодиодов в списке
void setup() {
for (int i = 0; i < COUNT; i++) {
pinMode(LEDS[i], OUTPUT);
}
}
void loop() {
for (int i = 0; i < COUNT; i++) {
digitalWrite(LEDS[i], HIGH);
delay(150);
digitalWrite(LEDS[i], LOW);
}
}
Что нового в этой программе:
const int LEDS[] = {6, 7, 8, 9, 10};— квадратные скобки после имени означают «это список». В фигурных скобках — сами значения через запятую.LEDS[0]— способ достать значение из списка. В скобках — номер ячейки, и вот здесь главная ловушка новичка: нумерация начинается с нуля. Первый элемент —LEDS[0](это 6), второй —LEDS[1](это 7), а последний, пятый —LEDS[4](это 10).- Поэтому и цикл теперь считает от 0 до 4:
for (int i = 0; i < COUNT; i++). Условие «строго меньше пяти» — как раз про пять ячеек с номерами 0…4. Буквуiдля счётчика используют по традиции (от англ. index — «номер»).
Теперь порядок огонька задаётся одной строкой сверху. Попробуйте переставить числа местами, например {10, 6, 9, 7, 8}, и прошить: огонёк побежит вразнобой, а сам цикл вы не тронете. Это и есть сила массива.
Шаг 5. Туда и обратно
Настоящий бегущий огонёк обычно возвращается. Добавим второй цикл, который считает в обратную сторону:
const int LEDS[] = {6, 7, 8, 9, 10};
const int COUNT = 5;
void setup() {
for (int i = 0; i < COUNT; i++) {
pinMode(LEDS[i], OUTPUT);
}
}
void loop() {
// Вперёд: от первого к последнему.
for (int i = 0; i < COUNT; i++) {
digitalWrite(LEDS[i], HIGH);
delay(120);
digitalWrite(LEDS[i], LOW);
}
// Назад: от предпоследнего к первому (крайние не мигают дважды подряд).
for (int i = COUNT - 2; i > 0; i--) {
digitalWrite(LEDS[i], HIGH);
delay(120);
digitalWrite(LEDS[i], LOW);
}
}
Второй цикл читается так же, только наоборот: начинаем с COUNT - 2 (это 3 — предпоследний светодиод), идём, пока номер больше нуля, и каждый раз отнимаем единицу: i--. Почему не с последнего и не до нулевого? Чтобы крайние светодиоды не мигали дважды подряд на развороте — иначе огонёк будет «запинаться» на краях. Уберите - 2 и посмотрите сами: разница видна сразу.
Эксперименты
- Скорость. Поставьте
delay(60)— огонёк превратится в стремительную комету. Поставьтеdelay(400)— в неторопливый маячок. - Волна. Уберите гашение из цикла — светодиоды будут зажигаться один за другим и оставаться гореть, пока не загорятся все. Потом погасим их так же по очереди:
const int LEDS[] = {6, 7, 8, 9, 10};
const int COUNT = 5;
void setup() {
for (int i = 0; i < COUNT; i++) {
pinMode(LEDS[i], OUTPUT);
}
}
void loop() {
// Заполняем: каждый следующий зажигается, предыдущие продолжают гореть.
for (int i = 0; i < COUNT; i++) {
digitalWrite(LEDS[i], HIGH);
delay(200);
}
// Гасим в том же порядке.
for (int i = 0; i < COUNT; i++) {
digitalWrite(LEDS[i], LOW);
delay(200);
}
}
Такой эффект — «заполнение» — используют в индикаторах загрузки и шкалах уровня. Кстати, вы уже почти собрали шкалу: в следующих уроках она пригодится для показаний датчика.
Не работает? Разбираемся
- Один светодиод не участвует. Дело в его канале, а не в программе: проверьте полярность именно этого светодиода, его резистор и то, что провод сидит в нужном гнезде платы. Быстрая проверка — поменяйте местами два светодиода: если «немой» остался на месте, виновата сборка на макетке.
- Горят все сразу и не бегут. Скорее всего, потерялась строка гашения
digitalWrite(..., LOW)или она стоит вне тела цикла. - Бежит, но пропускает крайний. Проверьте условие цикла:
i < COUNTдля массива (номера 0…4) иpin <= 10для счётчика по номерам выводов. Знаки<и<=здесь принципиально разные. - Среда ругается на строку с массивом. Проверьте, что скобки не перепутаны: квадратные
[]— после имени, фигурные{}— вокруг значений, и в конце строки точка с запятой. - Плата ведёт себя странно, светодиоды мерцают хаотично. Проверьте, что ни один провод не воткнут в выводы 0 и 1, и что все пять цепочек приходят в общую шину «−».
Задания для тренировки
- РазминкаЗаставьте огонёк бежать в обратную сторону — справа налево, — поменяв только строку с массивом.
- ПосложнееСделайте «мигалку»: все пять светодиодов зажигаются одновременно, гаснут, и так по кругу. Подсказка: в теле цикла может стоять только зажигание, а пауза и гашение — отдельным циклом после него.
- Со звёздочкой ⭐Сделайте огонёк с «хвостом»: пусть одновременно горят два соседних светодиода, и эта пара бежит по цепочке. Попробуйте сами — разбор ниже.
Показать разбор задания «огонёк с хвостом»
Идея простая: на каждом шаге зажигаем очередной светодиод, но гасим не его, а предыдущий. Тогда в каждый момент горят двое. Сложность одна: когда мы в самом начале списка, «предыдущего» ещё нет — номер получается отрицательным. Значит, надо перескочить в конец списка:
const int LEDS[] = {6, 7, 8, 9, 10};
const int COUNT = 5;
void setup() {
for (int i = 0; i < COUNT; i++) {
pinMode(LEDS[i], OUTPUT);
}
}
void loop() {
for (int i = 0; i < COUNT; i++) {
digitalWrite(LEDS[i], HIGH); // зажигаем очередной
// Гасим тот, что был два шага назад: горящих всегда двое.
int tail = i - 1;
if (tail < 0) {
tail = tail + COUNT; // с начала списка переходим в конец
}
digitalWrite(LEDS[tail], LOW);
delay(150);
}
}
Строка if (tail < 0) — это условие: команда внутри выполняется, только если проверка верна. Мы этим приёмом ещё не пользовались — им подробно займёмся в уроке про кнопку, а пока просто примите как рабочий рецепт для «зацикливания» списка.
Что мы узнали
- Пять одинаковых цепочек собираются одинаково: свой вывод, свой резистор, общая земля.
- Резистор можно ставить как до светодиода, так и после — ток в цепи один.
- Цикл
forповторяет команды, меняя переменную: три части заголовка — с чего начать, пока какое условие верно, что делать после каждого повтора. - Массив хранит список значений под одним именем; нумерация ячеек начинается с нуля.
- Хорошая программа не повторяет одинаковые строки — она повторяет действия циклом.
Дальше — светофор: те же светодиоды, но уже не эффект, а настоящее устройство с осмысленными состояниями и выдержками времени.
Частые вопросы
Почему нумерация массива начинается с нуля?
Так устроен язык: номер ячейки — это смещение от начала списка, поэтому у первого элемента оно нулевое. В списке из пяти значений ячейки нумеруются 0, 1, 2, 3, 4 — последний элемент имеет номер 4, а не 5. Это самая частая причина ошибок у новичков.
Чем цикл for отличается от простого повторения строк?
Результат одинаковый, но цикл записывает повторяющееся действие один раз. Программа становится короче и понятнее, а изменения (скорость, число светодиодов) вносятся в одном месте вместо пяти.
Резистор нужно ставить до светодиода или после?
Можно и так, и так — ток в последовательной цепи везде одинаков, поэтому «сужение» работает в любом месте. Главное, чтобы резистор был: без него светодиод выйдет из строя.
Сколько светодиодов можно подключить к плате одновременно?
Каждый вывод Uno выдерживает около 20 мА, а суммарно все выводы — до 200 мА. Пять светодиодов через резисторы 470 Ом — это примерно 50 мА, с большим запасом. Для десятков светодиодов нужны транзисторы или отдельное питание.
