analogWrite: плавное мигание светодиодом

Цифровой выход умеет быстро переключаться между включённым и выключенным состояниями. Если менять долю времени во включённом состоянии, светодиод воспринимается ярче или тусклее. На большинстве классических Arduino-плат analogWrite() использует именно такой метод — широтно-импульсную модуляцию, или PWM.
На PWM-пине analogWrite() обычно создаёт прямоугольные импульсы. Аргумент задаёт коэффициент заполнения — долю периода, когда сигнал активен. Это не то же самое, что постоянное промежуточное напряжение DAC.
Зачем это нужно
Плавное изменение команды применяют не только для света. PWM используют для:
- регулирования средней яркости индикатора;
- задания управляющего входа драйвера коллекторного мотора;
- мощности нагревателя через подходящий силовой ключ;
- изменения уровня подсветки;
- формирования простых звуковых и временных эффектов.
GPIO не должен напрямую питать мотор или мощную ленту. Он передаёт управляющий сигнал подходящему драйверу, а энергия поступает по отдельной силовой цепи. В уроке нагрузка — один маломощный LED с последовательным резистором.
Схема подключения PWM-светодиода

Схема похожа на обычный цифровой выход: Arduino UNO, макетная плата, резистор и светодиод. Отличие в выбранном пине: для analogWrite() на классической UNO нужно использовать пин с поддержкой PWM, например 9.
| Узел | Куда подключить | Зачем |
|---|---|---|
PWM-пин 9 | к LED-цепи через резистор | формирует импульсы разной ширины |
| резистор | последовательно со светодиодом | ограничивает ток |
GND | к общей шине макетной платы | даёт общий возврат тока |
Главная идея
PWM-период состоит из активной и неактивной частей:
коэффициент заполнения = активное время / полный период
В показанной активной-высокой LED-цепи активной частью является HIGH; для инвертирующего драйвера активным может быть LOW. Если активная часть короткая, среднее воздействие на нагрузку мало. Если она занимает почти весь период, воздействие больше. Светодиод переключается физически, но при достаточно высокой частоте глаз обычно объединяет импульсы в устойчивую яркость.
| Коэффициент заполнения | Сигнал в течение периода | Ожидаемое восприятие LED |
|---|---|---|
| 0% | всё время неактивен | выключен |
| около 25% | активен четверть периода | тусклый |
| около 50% | активен половину периода | средняя яркость |
| 100% | всё время активен | максимальная для этой цепи |
Яркость человеческого зрения нелинейна, поэтому численно равные шаги PWM не обязательно выглядят как равные шаги яркости.

Нижняя линия показывает последовательные примеры с малым, средним и большим коэффициентом заполнения. Штриховые разделители — границы примеров, не электрические проводники. Верхняя цепь остаётся одной: выход, резистор, LED и общий возврат.
analogWrite() не всегда аналоговый выход
Название историческое и может сбить с толку. На типичных PWM-пинах функция не устанавливает постоянное напряжение между LOW и HIGH. Она формирует импульсы. Мультиметр может показать усреднённое значение, но осциллограф обнаружит переключения.
Некоторые платы имеют настоящий DAC на отдельных контактах. На таких контактах analogWrite() или другая функция ядра может формировать реальный аналоговый уровень — поведение нужно проверять по документации именно этой платы и её Arduino core.
| Признак | PWM | DAC |
|---|---|---|
| мгновенный сигнал | переключается между уровнями | удерживает промежуточный уровень |
| основной параметр | коэффициент заполнения | код выходного напряжения |
| типичные применения | LED, вход драйвера | опорный или аналоговый сигнал |
| наличие | только на подходящих пинах | не на каждой плате |
PWM не превращается в безопасный силовой выход: токовые пределы GPIO сохраняются.
Первый плавный переход
На многих классических платах значение по умолчанию лежит в диапазоне 0…255. Пример нужно запускать только на контакте, поддерживающем analogWrite() для выбранной платы:
const int ledPin = 9;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
for (int brightness = 0; brightness <= 255; brightness++) {
analogWrite(ledPin, brightness);
delay(8);
}
for (int brightness = 255; brightness >= 0; brightness--) {
analogWrite(ledPin, brightness);
delay(8);
}
}
Первая петля повышает команду от минимума до максимума, вторая возвращает её к нулю. Крайнее значение повторяется на границе циклов, что практически незаметно, но при строгом расчёте последовательности это можно учесть.
Диапазон, частота, разрешение и допустимые пины зависят от платы. На платах, поддерживающих analogWriteResolution(), диапазон меняется вместе с выбранным числом бит. Не копируй 255 в проект, пока не проверишь документацию и настройки.
Откуда берётся время перехода
В первом цикле 256 шагов. При задержке 8 мс подъём занимает примерно:
256 × 8 мс ≈ 2048 мс
Спуск занимает ещё примерно столько же. К этому добавляется небольшое время выполнения команд. Если увеличить задержку, переход станет медленнее; если увеличить шаг, переход станет быстрее, но менее гладким.
Вариант с явным шагом:
const int ledPin = 9;
const int minValue = 0;
const int maxValue = 255;
const int stepValue = 5;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
for (int value = minValue; value <= maxValue; value += stepValue) {
analogWrite(ledPin, value);
delay(20);
}
for (int value = maxValue; value >= minValue; value -= stepValue) {
analogWrite(ledPin, value);
delay(20);
}
}
Здесь 255 делится на 5, поэтому крайние точки достигаются точно. Для другого шага нужно следить, чтобы цикл не перескочил границу или не застрял из-за неверного условия.
Неблокирующее изменение яркости
Два цикла с delay() надолго занимают выполнение. Мобильный робот в этот момент поздно отреагирует на препятствие. Состояние плавного перехода можно обновлять небольшими шагами по millis():
const int ledPin = 9;
const unsigned long updateIntervalMs = 10;
unsigned long previousUpdateMs = 0;
int brightness = 0;
int direction = 1;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
const unsigned long now = millis();
if (now - previousUpdateMs >= updateIntervalMs) {
previousUpdateMs = now;
brightness += direction;
if (brightness >= 255) {
brightness = 255;
direction = -1;
} else if (brightness <= 0) {
brightness = 0;
direction = 1;
}
analogWrite(ledPin, brightness);
}
// Опрос датчиков может выполняться при каждом проходе loop().
}
Переменная direction равна 1 при росте и -1 при спаде. Явное ограничение на границах не позволяет значению уйти за допустимый диапазон этого примера.
Управление яркостью по датчику
Показание АЦП и диапазон PWM могут отличаться. Для классического примера с входом 0…1023 и выходом 0…255 значение иногда делят на четыре, но это соотношение не переносится автоматически на другие платы.
Более честный вариант задаёт известные калибровочные границы:
const int sensorPin = A0;
const int ledPin = 9;
const int sensorMin = 180;
const int sensorMax = 820;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
int raw = analogRead(sensorPin);
raw = constrain(raw, sensorMin, sensorMax);
const int brightness = map(raw, sensorMin, sensorMax, 0, 255);
analogWrite(ledPin, brightness);
delay(10);
}
Числа 180 и 820 — демонстрационные. В проекте их определяют по реальным минимальным и максимальным измерениям. map() использует целочисленную арифметику и сама не ограничивает вход, поэтому перед ней применён constrain().
Частота, мерцание и таймеры
Частоту PWM формирует аппаратный таймер или периферийный блок. Она различается между платами и иногда между пинами одной платы. Изменение настроек таймера может повлиять на другие функции или библиотеки, которые используют тот же ресурс.
Для обычного индикатора стандартных настроек часто достаточно. Для камеры, быстрого датчика, мотора или звука нужно учитывать:
- видимое либо снимаемое камерой мерцание;
- акустический шум;
- время реакции нагрузки;
- совместное использование таймеров;
- требования драйвера к частоте и логическим уровням.
Не меняйте регистры таймера по случайному фрагменту кода: сначала найдите документацию микроконтроллера и Arduino core.
Электрические ограничения
Светодиод по-прежнему требует последовательного резистора. analogWrite() не ограничивает ток и не защищает GPIO. Для мощной нагрузки нужен драйвер, рассчитанный на ток, напряжение и частоту переключения.
| Ошибка | Почему возникает | Исправление |
|---|---|---|
| выбран обычный цифровой пин без PWM | функция не даёт ожидаемой модуляции | проверить pinout и справочник платы |
| LED подключён без резистора | ток не ограничен | рассчитать последовательный резистор |
| мотор подключён к GPIO | нагрузка превышает возможности выхода | использовать драйвер и отдельное питание |
| ожидание постоянного напряжения | PWM принят за DAC | проверить осциллографом и документацией |
жёстко задан 255 на другой конфигурации | разрешение отличается | связать диапазон с настройками платы |
Что запомнить о analogWrite
- На PWM-пине
analogWrite()обычно задаёт коэффициент заполнения импульсов. - PWM и DAC — разные способы формирования выхода.
- Пины, диапазон, разрешение и частота зависят от платы.
- Визуальная яркость LED нелинейна относительно числа команды.
delay()упрощает учебный fade, но мешает параллельным задачам.- Мощность для мотора или ленты должна идти через подходящий драйвер.
Практика
Задание 1
Найдите в официальной документации PWM-пины своей платы. Соберите LED с последовательным резистором и проверьте несколько значений команды, включая границы.
Задание 2
Напишите плавный подъём и спад с настраиваемыми шагом и интервалом. Рассчитайте ожидаемое время полного цикла и сравните с измерением.
Задание 3
Перепишите эффект без delay() и добавьте кнопку, которая мгновенно выключает LED независимо от фазы перехода.
Задание 4
Снимите минимальное и максимальное значение датчика в реальных условиях, затем сопоставь их PWM-диапазону. Зафиксируй метод калибровки, но не вписывай универсальные границы.
Проверьте себя
- Что изменяет коэффициент заполнения PWM?
- Почему
analogWrite()на обычном PWM-пине не является DAC? - От чего зависит допустимый диапазон аргумента?
- Почему равные числовые шаги могут выглядеть неравными по яркости?
- Зачем LED нужен резистор при PWM?
- Как оценить время плавного перехода?
- Почему
map()часто используют вместе сconstrain()? - Когда нагрузке нужен отдельный драйвер?
Сначала ответьте без подсказки. Ответ можно считать полным, если вы:
- формулируете основную мысль своими словами;
- называете важные условия, ограничения или меры безопасности;
- для схемы, кода или расчёта показываете ход решения и ожидаемый результат.
Если один из пунктов объяснить не получается, найдите соответствующую главу статьи, перечитайте её и повторите ответ.
Словарь статьи
- PWM (ШИМ) — модуляция, которая меняет долю активного времени импульсного сигнала и тем самым регулирует среднее воздействие на нагрузку.
- Коэффициент заполнения — доля периода, когда сигнал активен.
- Период — время одного полного повторения PWM.
- Частота — число периодов в секунду.
- DAC — цифро-аналоговый преобразователь, формирующий аналоговый уровень.
- Разрешение — число различимых кодов управления.
- Fade — постепенное изменение яркости.
- Драйвер нагрузки — силовой узел между GPIO и устройством с большим током.
Связанные темы
- digitalWrite: мигание светодиодом — два цифровых состояния выхода.
- Циклы — пошаговое изменение команды.
- analogRead: обработка датчика — получение входного значения.
- Драйвер моторов — применение PWM к силовому входу.
Источники
- Arduino: analogWrite() — назначение, синтаксис и различия плат.
- Arduino: analogWriteResolution() — настройка разрешения на поддерживаемых платах.
- Arduino: map() — целочисленное преобразование диапазона.
- Arduino: constrain() — ограничение значения.
- Arduino Built-in Examples — Fading и Analog In, Out Serial.
- Arduino UNO R4 Minima datasheet — пример платы с PWM и отдельным DAC-контактом.