Курс Arduino с нуля · Управляем светом

Урок 8. Плавная яркость и живая свеча

Разбираемся, как из «включено-выключено» получается плавный свет: команда analogWrite, разгорание, дыхание и имитация пламени свечи.

Урок 8 из 31⏱ примерно 45 минутОбновлено: 25 июля 2026

Что понадобится

  • Плата 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 раз в секунду включает и выключает вывод, и меняется только ширина импульсов — какую долю времени напряжение есть.

5 В0 ВanalogWrite(0)включён 0% временине горитanalogWrite(64)включён 25% временигорит тусклоanalogWrite(191)включён 75% временигорит почти в полную силуanalogWrite(255)включён 100% временигорит ярко— средний уровень: именно его «видит» глаз. Светодиод при этом мигает 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.

~11~10~9~6~5~3Только у этих шести выводов есть волнистая черта — только они умеют плавную яркость.
Шесть выводов с тильдой — единственные, где работает analogWrite() с плавной яркостью.

Если написать analogWrite() для обычного вывода (например, 8), программа соберётся и даже загрузится — но плавности не будет: светодиод просто включится или выключится. Ошибки не появится, поэтому такой случай трудно поймать. Проверьте сами: если у вас собран светофор из прошлого урока, попробуйте задать половину яркости зелёному (он на выводе 8) — и убедитесь, что ничего не выйдет. А красному (вывод ~10) и жёлтому (~9) — выйдет.

Схема: та же, что и раньше

Ничего пересобирать не нужно. Достаточно одного светодиода на выводе ~10 — ровно та схема, что была в пятом уроке:

зелёный провод — с вывода 10,синий — с GND++abcdefghij15152061012123451Зелёный провод: из гнезда 10 верхней гребёнки — в столбик 6 (ряд a).2Синий провод: из гнезда GND платы — в шину «−» макетки.3Резистор 470 Ом: из столбика 6 — в столбик 10 (ряд b).4Светодиод: длинная ножка — в столбик 10, короткая — в столбик 12.5Перемычка: из столбика 12 — в шину «−».
Один светодиод с резистором 470 Ом на выводе ~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 почти не видна?

Так устроено зрение: к слабому свету глаз чувствителен намного сильнее, чем к яркому. Поэтому равные шаги числа дают неравные шаги видимой яркости — нижняя часть диапазона выглядит «богаче» на оттенки.

← Все уроки курса