Курс Arduino с нуля · Управляем светом
Урок 8. Плавная яркость и живая свеча
Разбираемся, как из «включено-выключено» получается плавный свет: команда analogWrite, разгорание, дыхание и имитация пламени свечи.
Что понадобится
- Плата Uno из набора
- Кабель USB
- Макетная плата
- Светодиод (для свечи — три разных цветов)
- Резистор 470 Ом (для свечи — три)
- 3 провода «папа-папа»
Все детали есть в обучающем наборе, по которому идёт курс.
До сих пор светодиод знал только два состояния: горит или не горит. А как заставить его светить вполсилы? Или медленно разгораться, как рассвет? Сегодня мы получим плавную яркость — и это, пожалуй, самый неожиданный урок курса: окажется, что плата по-прежнему умеет только «включено» и «выключено», а плавность — красивая иллюзия, которую мы устроим для собственных глаз.
В конце соберём живую свечу: светодиод будет трепетать, как настоящее пламя.
Задача: получить «половину»
Вспомните третий урок. Мы уменьшали паузу в мигании и на delay(5) увидели странное: мигание пропало, а светодиод стал светить ровно, но тускло. Тогда мы отмахнулись — «глаз не успевает». А ведь это и есть решение задачи. Проверим его осознанно:
const int LED = 10;
void setup() {
pinMode(LED, OUTPUT);
}
void loop() {
// Мигаем так быстро, что глаз не различает вспышки и видит
// ровное свечение вполсилы.
digitalWrite(LED, HIGH);
delay(5);
digitalWrite(LED, LOW);
delay(5);
}
Прошейте — светодиод светит примерно вполсилы. Половину времени напряжение есть, половину нет, и глаз усредняет: получается «наполовину включено». Мы сделали то, что хотели!
Но так жить нельзя. Программа занята только миганием: она сидит в бесконечных delay(5) и не может ни опросить кнопку, ни посчитать время, ни зажечь второй светодиод по-другому. А если светодиодов пять и у каждого своя яркость — задача становится безнадёжной.
ШИМ: плата мигает за вас
Инженеры придумали решение: пусть быстрым миганием занимается сама электроника платы, а программа просто говорит, насколько ярко. Этот приём называется ШИМ — широтно-импульсная модуляция. Звучит внушительно, а смысл простой: плата 490 раз в секунду включает и выключает вывод, и меняется только ширина импульсов — какую долю времени напряжение есть.
Ключевая мысль: яркость светодиода не меняется никогда. Он всегда либо полностью включён, либо полностью выключен. Меняется только доля времени — а «яркость» достраивает наш глаз, потому что не успевает за сотнями переключений в секунду.
Где это ещё встречается. Точно так же работает регулировка яркости экрана телефона, диммер лампы в комнате и регулятор оборотов дрели. Всюду — быстрое переключение с разной шириной импульсов. Вы только что разобрались в приёме, на котором держится половина бытовой электроники.
Команда analogWrite
Задать ширину импульсов проще простого: команда analogWrite(вывод, значение). Значение — от 0 (выключен совсем) до 255 (включён постоянно, полная яркость). Вся наша предыдущая программа сворачивается в одну строку:
const int LED = 10;
void setup() {
pinMode(LED, OUTPUT);
}
void loop() {
// 128 — это примерно половина от 255, то есть половина яркости.
analogWrite(LED, 128);
}
Прошейте: светодиод светит вполсилы — так же, как в первом варианте. Но теперь loop() практически пуст: плата сама держит нужную яркость, а программа свободна для других дел.
Почему 255, а не 100? Внутри платы яркость хранится в одном байте — а байт умеет считать от 0 до 255, это 256 разных значений. Отсюда и диапазон. Половина — это 128, четверть — 64, три четверти — 191. Со временем эти числа запоминаются сами.
Кстати, название команды немного обманывает: analog — «аналоговый», то есть плавный. На самом деле никакого плавного напряжения плата не выдаёт: это всё тот же цифровой сигнал, просто результат похож на плавный. Помните об этом, когда встретите слово analogWrite в чужих программах.
Важно: не каждый вывод так умеет
А вот и подвох, на котором спотыкаются все новички. ШИМ умеют не все выводы, а только шесть — те, рядом с которыми на плате напечатана волнистая черта (тильда): ~3, ~5, ~6, ~9, ~10, ~11.
analogWrite() с плавной яркостью.Если написать analogWrite() для обычного вывода (например, 8), программа соберётся и даже загрузится — но плавности не будет: светодиод просто включится или выключится. Ошибки не появится, поэтому такой случай трудно поймать. Проверьте сами: если у вас собран светофор из прошлого урока, попробуйте задать половину яркости зелёному (он на выводе 8) — и убедитесь, что ничего не выйдет. А красному (вывод ~10) и жёлтому (~9) — выйдет.
Схема: та же, что и раньше
Ничего пересобирать не нужно. Достаточно одного светодиода на выводе ~10 — ровно та схема, что была в пятом уроке:
Шаг 1. Четыре ступеньки
Чтобы увидеть уровни своими глазами, прогоним их по очереди с секундной паузой:
const int LED = 10;
void setup() {
pinMode(LED, OUTPUT);
}
void loop() {
analogWrite(LED, 0); // выключен
delay(1000);
analogWrite(LED, 64); // четверть яркости
delay(1000);
analogWrite(LED, 191); // три четверти
delay(1000);
analogWrite(LED, 255); // полная яркость
delay(1000);
}
Прошейте и понаблюдайте. Разница между 0 и 64 бросается в глаза, а вот между 191 и 255 почти незаметна. Это не обман зрения: человеческий глаз устроен так, что к яркому свету он гораздо менее чувствителен, чем к слабому. Поэтому «половина числа» и «половина видимой яркости» — не одно и то же.
Шаг 2. Плавное разгорание
Раз уровней 256, можно пройти по ним подряд — и получится плавный рассвет. Кто пройдёт по всем значениям? Конечно, цикл for из шестого урока:
const int LED = 10;
void setup() {
pinMode(LED, OUTPUT);
}
void loop() {
// Яркость растёт от 0 до 255 по одному шагу за 8 миллисекунд:
// весь путь занимает примерно две секунды.
for (int level = 0; level <= 255; level++) {
analogWrite(LED, level);
delay(8);
}
}
Разберём цикл: переменная level шагает от 0 до 255, на каждом шаге мы задаём эту яркость и ждём 8 миллисекунд. Весь путь занимает примерно две секунды (256 шагов по 8 мс). Дошли до конца — loop() начинается заново, и светодиод резко гаснет, чтобы разгореться снова.
Хотите быстрее или медленнее? Меняйте паузу: delay(3) — стремительный рассвет, delay(20) — медленный и торжественный.
Шаг 3. Дыхание
Резкий сброс в конце выглядит некрасиво. Добавим обратный путь — и получится «дыхание», как у индикатора спящего ноутбука:
const int LED = 10;
void setup() {
pinMode(LED, OUTPUT);
}
void loop() {
// Вдох: яркость растёт.
for (int level = 0; level <= 255; level++) {
analogWrite(LED, level);
delay(6);
}
// Выдох: яркость падает.
for (int level = 255; level >= 0; level--) {
analogWrite(LED, level);
delay(6);
}
delay(400); // короткая пауза перед новым вдохом
}
Второй цикл — зеркальный: начинаем с 255, идём, пока значение не меньше нуля, и каждый раз отнимаем единицу (level--). Точно такой же приём мы использовали для бегущего огонька «туда-обратно» — только там менялся номер светодиода, а здесь яркость одного.
Проверьте себя. Что произойдёт, если во втором цикле написать level > 0 вместо level >= 0? Светодиод не будет гаснуть до конца — остановится на единице. Мелочь, но именно из таких мелочей складывается разница между «почти работает» и «работает как надо».
Шаг 4. Живая свеча
Настоящее пламя не разгорается ровно — оно трепещет непредсказуемо. Значит, нам нужна случайность. В языке для этого есть команда random(от, до): она выдаёт случайное число из указанного промежутка (нижняя граница включается, верхняя — нет).
const int LED = 10;
void setup() {
pinMode(LED, OUTPUT);
}
void loop() {
// random(120, 256) — случайное число от 120 до 255:
// пламя колеблется, но никогда не гаснет полностью.
analogWrite(LED, random(120, 256));
// Промежутки тоже случайные — от 30 до 120 миллисекунд.
delay(random(30, 120));
}
Всего две строки — а эффект узнаваемый. Разберём числа:
random(120, 256)— яркость колеблется от 120 до 255. Нижняя граница не нулевая: настоящее пламя не гаснет полностью, оно лишь притухает.delay(random(30, 120))— и промежутки между изменениями тоже случайные. Если сделать их одинаковыми, глаз уловит ритм, и волшебство пропадёт.
Поиграйте с числами: сузьте промежуток до random(200, 256) — пламя станет спокойным, как у свечи в закрытой комнате. Расширьте до random(40, 256) — задует ветер.
Свеча правдоподобнее: три светодиода
В печатной брошюре набора свеча собрана на трёх светодиодах — и это заметно живее, потому что цвета смешиваются в тёплое дрожащее пятно. Если у вас собран светофор, понадобится одна перестановка: перенесите провод зелёного светодиода с вывода 8 на вывод ~11 (напомним: 8 не умеет ШИМ, а 11 умеет).
const int RED = 10; // красный — основа пламени
const int YELLOW = 9; // жёлтый — блики
const int GREEN = 11; // зелёный светит еле-еле и «согревает» оттенок
void setup() {
pinMode(RED, OUTPUT);
pinMode(YELLOW, OUTPUT);
pinMode(GREEN, OUTPUT);
}
void loop() {
analogWrite(RED, random(120, 256)); // ярко и нестабильно
analogWrite(YELLOW, random(60, 180)); // послабее
analogWrite(GREEN, random(0, 40)); // едва заметно
delay(random(30, 120));
}
Каждый светодиод получает свой диапазон: красный — самый яркий, жёлтый — послабее, зелёный едва тлеет и лишь чуть подкрашивает общий свет. Накройте конструкцию матовым стаканчиком или салфеткой — и сходство станет почти полным.
Не работает? Разбираемся
- Яркость не меняется: светодиод либо горит, либо нет. Первым делом проверьте вывод: есть ли рядом с его номером на плате волнистая черта? Плавность работает только на ~3, ~5, ~6, ~9, ~10, ~11.
- Светодиод еле светит даже на 255. Похоже, стоит резистор 10 кОм вместо 470 Ом — ШИМ тут ни при чём.
- Разгорание идёт рывками. Скорее всего, слишком большая пауза в цикле:
delay(50)при 256 шагах — это почти 13 секунд, ступеньки становятся заметны. Уменьшите паузу. - Среда ругается на значение больше 255. Ошибки не будет, но и смысла тоже: значения выше 255 не дадут «ещё ярче». Диапазон жёсткий — от 0 до 255.
- Свеча мигает слишком резко. Поднимите нижнюю границу яркости (не 0, а 120–150) и увеличьте минимальную паузу — пламя станет спокойнее.
Задания для тренировки
- РазминкаСделайте ночник: светодиод плавно разгорается пять секунд, светит на полную десять секунд, потом плавно гаснет.
- ПосложнееСоберите «маяк с плавным светом»: яркость нарастает быстро (за полсекунды), а спадает медленно (за две секунды) — получится эффект вспышки с послесвечением.
- Со звёздочкой ⭐Оживите светофор из прошлого урока: пусть при смене фаз лампы не щёлкают, а плавно разгораются и угасают. Подсказка: напишите функцию
fadeTo(int pin, int target), которая плавно доводит вывод до нужной яркости, — и вызывайте её вместоdigitalWrite. Учтите, что зелёный придётся перенести на ШИМ-вывод.
Что мы узнали
- Плата не умеет выдавать «половину напряжения» — она очень быстро включает и выключает вывод, а среднюю яркость достраивает наш глаз.
- Этот приём называется ШИМ; частота переключений — около 490 раз в секунду.
analogWrite(вывод, значение)задаёт яркость от 0 до 255 и освобождает программу от ручного мигания.- ШИМ работает только на выводах с волнистой чертой: ~3, ~5, ~6, ~9, ~10, ~11.
random(от, до)даёт случайное число — с ним получаются живые, неровные эффекты вроде пламени.
Пока свет менялся по программе. В следующем уроке к делу подключится трёхцветный светодиод: смешивая три яркости, мы получим любой цвет — от фиолетового до тёплого белого.
Частые вопросы
Что такое ШИМ простыми словами?
Это быстрое переключение вывода между «включено» и «выключено» — около 490 раз в секунду. Меняется доля времени, когда напряжение есть: чем она больше, тем ярче кажется светодиод. Сама яркость не меняется — среднее значение достраивает глаз.
На каких выводах Arduino работает analogWrite?
На выводах, отмеченных волнистой чертой: ~3, ~5, ~6, ~9, ~10 и ~11. На остальных команда сработает, но плавности не даст — светодиод просто включится или выключится, причём без сообщения об ошибке.
Почему яркость задаётся числом от 0 до 255?
Значение хранится в одном байте, а байт вмещает 256 разных значений — от 0 до 255. Ноль означает «выключено», 255 — «включено постоянно», 128 — примерно половину.
Почему разница между 191 и 255 почти не видна?
Так устроено зрение: к слабому свету глаз чувствителен намного сильнее, чем к яркому. Поэтому равные шаги числа дают неравные шаги видимой яркости — нижняя часть диапазона выглядит «богаче» на оттенки.
