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

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

PWM-выход управляет яркостью светодиода

Цифровой выход умеет быстро переключаться между включённым и выключенным состояниями. Если менять долю времени во включённом состоянии, светодиод воспринимается ярче или тусклее. На большинстве классических Arduino-плат analogWrite() использует именно такой метод — широтно-импульсную модуляцию, или PWM.

Коротко

На PWM-пине analogWrite() обычно создаёт прямоугольные импульсы. Аргумент задаёт коэффициент заполнения — долю периода, когда сигнал активен. Это не то же самое, что постоянное промежуточное напряжение DAC.

Зачем это нужно

Плавное изменение команды применяют не только для света. PWM используют для:

  • регулирования средней яркости индикатора;
  • задания управляющего входа драйвера коллекторного мотора;
  • мощности нагревателя через подходящий силовой ключ;
  • изменения уровня подсветки;
  • формирования простых звуковых и временных эффектов.

GPIO не должен напрямую питать мотор или мощную ленту. Он передаёт управляющий сигнал подходящему драйверу, а энергия поступает по отдельной силовой цепи. В уроке нагрузка — один маломощный LED с последовательным резистором.

Схема подключения PWM-светодиода

Светодиод на PWM-пине Arduino UNO в Tinkercad

Схема похожа на обычный цифровой выход: Arduino UNO, макетная плата, резистор и светодиод. Отличие в выбранном пине: для analogWrite() на классической UNO нужно использовать пин с поддержкой PWM, например 9.

УзелКуда подключитьЗачем
PWM-пин 9к LED-цепи через резисторформирует импульсы разной ширины
резисторпоследовательно со светодиодомограничивает ток
GNDк общей шине макетной платыдаёт общий возврат тока

Главная идея

PWM-период состоит из активной и неактивной частей:

коэффициент заполнения = активное время / полный период

В показанной активной-высокой LED-цепи активной частью является HIGH; для инвертирующего драйвера активным может быть LOW. Если активная часть короткая, среднее воздействие на нагрузку мало. Если она занимает почти весь период, воздействие больше. Светодиод переключается физически, но при достаточно высокой частоте глаз обычно объединяет импульсы в устойчивую яркость.

Коэффициент заполненияСигнал в течение периодаОжидаемое восприятие LED
0%всё время неактивенвыключен
около 25%активен четверть периодатусклый
около 50%активен половину периодасредняя яркость
100%всё время активенмаксимальная для этой цепи

Яркость человеческого зрения нелинейна, поэтому численно равные шаги PWM не обязательно выглядят как равные шаги яркости.

Одна LED-цепь и последовательные участки PWM разной ширины

Нижняя линия показывает последовательные примеры с малым, средним и большим коэффициентом заполнения. Штриховые разделители — границы примеров, не электрические проводники. Верхняя цепь остаётся одной: выход, резистор, LED и общий возврат.

analogWrite() не всегда аналоговый выход

Название историческое и может сбить с толку. На типичных PWM-пинах функция не устанавливает постоянное напряжение между LOW и HIGH. Она формирует импульсы. Мультиметр может показать усреднённое значение, но осциллограф обнаружит переключения.

Некоторые платы имеют настоящий DAC на отдельных контактах. На таких контактах analogWrite() или другая функция ядра может формировать реальный аналоговый уровень — поведение нужно проверять по документации именно этой платы и её Arduino core.

ПризнакPWMDAC
мгновенный сигналпереключается между уровнямиудерживает промежуточный уровень
основной параметркоэффициент заполнениякод выходного напряжения
типичные применения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-диапазону. Зафиксируй метод калибровки, но не вписывай универсальные границы.

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

  1. Что изменяет коэффициент заполнения PWM?
  2. Почему analogWrite() на обычном PWM-пине не является DAC?
  3. От чего зависит допустимый диапазон аргумента?
  4. Почему равные числовые шаги могут выглядеть неравными по яркости?
  5. Зачем LED нужен резистор при PWM?
  6. Как оценить время плавного перехода?
  7. Почему map() часто используют вместе с constrain()?
  8. Когда нагрузке нужен отдельный драйвер?
Ориентиры для самопроверки

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

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

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

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

  • PWM (ШИМ) — модуляция, которая меняет долю активного времени импульсного сигнала и тем самым регулирует среднее воздействие на нагрузку.
  • Коэффициент заполнения — доля периода, когда сигнал активен.
  • Период — время одного полного повторения PWM.
  • Частота — число периодов в секунду.
  • DAC — цифро-аналоговый преобразователь, формирующий аналоговый уровень.
  • Разрешение — число различимых кодов управления.
  • Fade — постепенное изменение яркости.
  • Драйвер нагрузки — силовой узел между GPIO и устройством с большим током.

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

Источники