Широтно-импульсная модуляция (ШИМ), или Pulse Width Modulation (PWM), является фундаментальной технологией в современной цифровой электронике. Она позволяет эффективно управлять мощностью, подаваемой на нагрузку, путем изменения длительности импульсов при постоянной частоте сигнала. Микроконтроллер ESP32, известный своей высокой производительностью и богатым набором периферийных устройств, предлагает расширенные возможности для генерации ШИМ-сигналов по сравнению с более простыми платформами, такими как Arduino Uno. Ключевое отличие ESP32 заключается в использовании канальной системы управления ШИМ, реализованной через специализированные периферийные модули. Вместо прямого управления ШИМ на конкретном выводе GPIO, как в analogWrite()
на Arduino, в ESP32 необходимо сначала настроить канал ШИМ (с его частотой и разрешением), а затем привязать один или несколько выводов GPIO к этому каналу. Управление скважностью (яркостью, скоростью) осуществляется путем изменения параметров настроенного канала. Данный подход обеспечивает большую гибкость и производительность. В этом урокуе мы подробно рассмотрим особенности ШИМ на ESP32, уделив основное внимание периферийному модулю LEDC, и приведем практические примеры его использования.
Выход ШИМ доступен на всех выводах GPIO микроконтроллера ESP32, за исключением четырех выводов GPIO (обычно GPIO 34, 35, 36, 39), которые сконфигурированы только как входы. Остальные выводы GPIO могут быть использованы для генерации ШИМ-сигналов.
ESP32 оснащен двумя основными периферийными модулями для генерации ШИМ:
Как и большинство контроллеров ШИМ, периферийный модуль LEDC в ESP32 использует внутренний таймер для генерации сигналов. Этот таймер непрерывно считает от нуля до своего максимального значения, после чего сбрасывается в ноль и начинает новый цикл счета. Интервал времени между этими сбросами определяет период ШИМ-сигнала, а обратная ему величина – частоту, измеряемую в Герцах (Гц).
ESP32 способен генерировать ШИМ-сигналы с частотой до 40 МГц (теоретический предел, на практике зависит от разрешения).
Скважность определяет долю периода, в течение которой ШИМ-сигнал находится в высоком логическом состоянии (HIGH). В контексте таймера LEDC, это значение (обычно сохраняемое в регистре сравнения/захвата таймера - CCR) указывает, до какого значения таймер должен досчитать, прежде чем выходной сигнал переключится с высокого уровня на низкий.
Пример: Предположим, нам нужно сгенерировать ШИМ-сигнал с частотой 1000 Гц, 8-битным разрешением и скважностью 75%.
round((2^разрешение - 1) * скважность)
-> round(255 * 0.75) = 191
). В библиотеке Arduino ledcWrite
обычно принимает значения от 0 до 2^разрешение - 1
, поэтому значение 191 будет соответствовать примерно 75% времени в состоянии HIGH.Разрешение ШИМ определяет количество дискретных уровней (шагов), на которые можно разделить диапазон скважности от 0% до 100%. Оно измеряется в битах. Если разрешение ШИМ составляет "n" бит, то таймер считает от 0 до 2ⁿ - 1 перед сбросом.
Более высокое разрешение означает большее количество "шагов" таймера в течение одного и того же периода времени, что приводит к увеличению "гранулярности" или точности установки скважности. Это особенно важно для задач, требующих плавного управления, таких как изменение яркости светодиодов или точное управление скоростью двигателей.
Разрешение ШИМ в ESP32 (для LEDC) можно настроить в диапазоне от 1 до 16 бит.
Вот где проявляется основное отличие ESP32 от простых платформ. Вместо прямого управления ШИМ на выводе, ESP32 использует концепцию каналов. Канал представляет собой логическую единицу, генерирующую уникальный ШИМ-сигнал с заданными параметрами (частота, разрешение, скважность).
Канал | Группа | № Канала в группе | № Таймера в группе |
---|---|---|---|
0 | 0 | 0 | 0 |
1 | 0 | 1 | 0 (тот же таймер, что и у канала 0) |
2 | 0 | 2 | 1 |
3 | 0 | 3 | 1 (тот же таймер, что и у канала 2) |
4 | 0 | 4 | 2 |
5 | 0 | 5 | 2 (тот же таймер, что и у канала 4) |
6 | 0 | 6 | 3 |
7 | 0 | 7 | 3 (тот же таймер, что и у канала 6) |
8 | 1 | 0 (8) | 0 |
9 | 1 | 1 (9) | 0 (тот же таймер, что и у канала 8) |
10 | 1 | 2 (10) | 1 |
11 | 1 | 3 (11) | 1 (тот же таймер, что и у канала 10) |
12 | 1 | 4 (12) | 2 |
13 | 1 | 5 (13) | 2 (тот же таймер, что и у канала 12) |
14 | 1 | 6 (14) | 3 |
15 | 1 | 7 (15) | 3 (тот же таймер, что и у канала 14) |
Практическое применение каналов:
ESP32 способен генерировать ШИМ с частотой до 40 МГц и разрешением до 16 бит. Однако существует компромисс между максимальной частотой и максимальным разрешением. Невозможно одновременно установить частоту 40 МГц и разрешение 16 бит.
Ограничение связано с тактовой частотой источника для таймеров ШИМ. Для генерации ШИМ-сигнала с N
битами разрешения и частотой F_pwm
, тактовая частота источника (F_clk
) должна удовлетворять условию:
F_clk >= F_pwm * 2^N
Согласно документации Espressif, источником тактового сигнала для таймеров LEDC (как минимум для Low-Speed группы) часто является шина APB_CLK, работающая на частоте 80 МГц. Поэтому рекомендуется, чтобы произведение F_pwm * 2^N
оставалось ниже 80 МГц.
Примеры из документации Espressif:
Практический совет: Для большинства задач, таких как плавное управление яркостью светодиодов, частоты в диапазоне 500 Гц - 5 кГц и разрешения 8-10 бит более чем достаточно. Можно начать с частоты 1-5 кГц и разрешения 8 бит, а затем экспериментировать при необходимости. Для сервоприводов обычно используется частота 50 Гц.
Ядро ESP32 для Arduino IDE включает встроенную библиотеку ledc
, которая упрощает работу с ШИМ через периферийный модуль LEDC.
Алгоритм использования библиотеки ledc
:
ledcSetup(channel, freq, resolution_bits)
. Эта функция конфигурирует выбранный канал с указанной частотой и разрешением.ledcAttachPin(pin, channel)
. Эта функция связывает физический вывод GPIO с настроенным логическим каналом ШИМ.ledcWrite(channel, duty_cycle_value)
для установки желаемой скважности на выбранном канале. Значение duty_cycle_value
должно быть в диапазоне от 0 до 2^resolution_bits - 1
.Простой пример, демонстрирующий генерацию ШИМ для плавного зажигания и гашения светодиода.
/* Управление ШИМ (PWM) на ESP32: Полное руководство от основ до практики. https://arduino-tex.ru/news/217/upravlenie-shim-pwm-na-esp32-polnoe-rukovodstvo-ot-osnov.html */ // Константы для настройки ШИМ const int PWM_CHANNEL = 0; // Выбираем канал 0 (из 0-15) const int PWM_FREQ = 5000; // Частота ШИМ 5 кГц (рекомендовано ESP32 примером) const int PWM_RESOLUTION = 8; // Разрешение 8 бит (0-255), как у Arduino Uno // Максимальное значение скважности, зависит от разрешения const int MAX_DUTY_CYCLE = (int)(pow(2, PWM_RESOLUTION) - 1); // Будет равно 255 для 8 бит // Вывод GPIO для подключения светодиода const int LED_OUTPUT_PIN = 18; // Задержка между шагами изменения яркости (в миллисекундах) const int FADE_DELAY_MS = 10; void setup() { // 1. Настройка канала ШИМ // ledcSetup(канал, частота, разрешение); ledcSetup(PWM_CHANNEL, PWM_FREQ, PWM_RESOLUTION); // 2. Привязка вывода GPIO к настроенному каналу // ledcAttachPin(пин, канал); ledcAttachPin(LED_OUTPUT_PIN, PWM_CHANNEL); Serial.begin(115200); // Инициализация серийного порта для отладки (опционально) Serial.println("PWM Fade Example Initialized"); } void loop() { // Плавное увеличение яркости (от 0 до MAX_DUTY_CYCLE) Serial.println("Fading UP..."); for(int dutyCycle = 0; dutyCycle <= MAX_DUTY_CYCLE; dutyCycle++){ // 3. Установка скважности для канала // ledcWrite(канал, значение_скважности); ledcWrite(PWM_CHANNEL, dutyCycle); delay(FADE_DELAY_MS); // Небольшая задержка для плавности } Serial.println("Reached Max Brightness"); // Плавное уменьшение яркости (от MAX_DUTY_CYCLE до 0) Serial.println("Fading DOWN..."); for(int dutyCycle = MAX_DUTY_CYCLE; dutyCycle >= 0; dutyCycle--){ ledcWrite(PWM_CHANNEL, dutyCycle); delay(FADE_DELAY_MS); // Небольшая задержка для плавности } Serial.println("Reached Min Brightness"); }
ledcSetup(0, 5000, 8)
: Настраивает канал 0 на частоту 5000 Гц с 8-битным разрешением.ledcAttachPin(18, 0)
: Связывает вывод GPIO 18 с каналом 0. Теперь сигнал, генерируемый каналом 0, будет выводиться на пин 18.ledcWrite(0, dutyCycle)
: Внутри циклов for
эта функция изменяет скважность канала 0, передавая значения от 0 до 255 и обратно. Это напрямую влияет на яркость светодиода, подключенного к пину 18.Можно вывести один и тот же ШИМ-сигнал на несколько выводов одновременно. Для этого достаточно привязать все нужные выводы к одному и тому же каналу.
/* Управление ШИМ (PWM) на ESP32: Полное руководство от основ до практики. https://arduino-tex.ru/news/217/upravlenie-shim-pwm-na-esp32-polnoe-rukovodstvo-ot-osnov.html */ // Константы для настройки ШИМ (те же, что и в Примере 1) const int PWM_CHANNEL = 0; const int PWM_FREQ = 5000; const int PWM_RESOLUTION = 8; const int MAX_DUTY_CYCLE = (int)(pow(2, PWM_RESOLUTION) - 1); // Выводы GPIO для подключения светодиодов const int LED_1_OUTPUT_PIN = 18; const int LED_2_OUTPUT_PIN = 19; const int LED_3_OUTPUT_PIN = 21; const int FADE_DELAY_MS = 10; void setup() { // 1. Настройка канала ШИМ (только один раз для канала 0) ledcSetup(PWM_CHANNEL, PWM_FREQ, PWM_RESOLUTION); // 2. Привязка НЕСКОЛЬКИХ выводов GPIO к ОДНОМУ каналу ledcAttachPin(LED_1_OUTPUT_PIN, PWM_CHANNEL); // Пин 18 к каналу 0 ledcAttachPin(LED_2_OUTPUT_PIN, PWM_CHANNEL); // Пин 19 к каналу 0 ledcAttachPin(LED_3_OUTPUT_PIN, PWM_CHANNEL); // Пин 21 к каналу 0 Serial.begin(115200); Serial.println("PWM Multi-LED Sync Fade Example Initialized"); } void loop() { // Циклы изменения яркости остаются ТЕМИ ЖЕ, // так как мы управляем только ОДНИМ каналом (PWM_CHANNEL) Serial.println("Fading UP..."); for(int dutyCycle = 0; dutyCycle <= MAX_DUTY_CYCLE; dutyCycle++){ ledcWrite(PWM_CHANNEL, dutyCycle); // Изменяем скважность канала 0 delay(FADE_DELAY_MS); } Serial.println("Reached Max Brightness"); Serial.println("Fading DOWN..."); for(int dutyCycle = MAX_DUTY_CYCLE; dutyCycle >= 0; dutyCycle--){ ledcWrite(PWM_CHANNEL, dutyCycle); // Изменяем скважность канала 0 delay(FADE_DELAY_MS); } Serial.println("Reached Min Brightness"); }
ledcAttachPin()
в setup()
, привязывающий разные пины (18, 19, 21) к одному и тому же каналу (0). Функция loop()
осталась без изменений, так как управление яркостью всех светодиодов осуществляется через изменение скважности единственного канала 0 функцией ledcWrite(0, ...)
.
Этот пример демонстрирует ключевое преимущество канальной системы ESP32: возможность независимого управления ШИМ на разных выводах. Мы будем использовать разные каналы для управления тремя светодиодами, заставляя их изменять яркость асинхронно.
/* Управление ШИМ (PWM) на ESP32: Полное руководство от основ до практики. https://arduino-tex.ru/news/217/upravlenie-shim-pwm-na-esp32-polnoe-rukovodstvo-ot-osnov.html */ // Общие параметры ШИМ (можно использовать одинаковые для простоты) const int PWM_FREQ = 5000; const int PWM_RESOLUTION = 8; const int MAX_DUTY_CYCLE = (int)(pow(2, PWM_RESOLUTION) - 1); // Определяем КАНАЛЫ и ПИНЫ для каждого светодиода const int LED1_PIN = 18; const int LED1_CH = 0; // Канал 0 для LED 1 const int LED2_PIN = 19; const int LED2_CH = 1; // Канал 1 для LED 2 const int LED3_PIN = 21; const int LED3_CH = 2; // Канал 2 для LED 3 // Задержки для управления скоростью "дыхания" каждого светодиода const int FADE_DELAY1_MS = 10; const int FADE_DELAY2_MS = 15; const int FADE_DELAY3_MS = 20; // Переменные для хранения текущей скважности и направления изменения для каждого канала int dutyCycle1 = 0; int dutyCycle2 = MAX_DUTY_CYCLE / 2; // Начнем со средних значений для разнообразия int dutyCycle3 = MAX_DUTY_CYCLE; int fadeDir1 = 1; // 1 = вверх, -1 = вниз int fadeDir2 = -1; int fadeDir3 = -1; unsigned long lastUpdate1 = 0; unsigned long lastUpdate2 = 0; unsigned long lastUpdate3 = 0; void setup() { Serial.begin(115200); Serial.println("PWM Independent Fade Example Initialized"); // Настраиваем КАЖДЫЙ канал ШИМ ledcSetup(LED1_CH, PWM_FREQ, PWM_RESOLUTION); ledcSetup(LED2_CH, PWM_FREQ, PWM_RESOLUTION); ledcSetup(LED3_CH, PWM_FREQ, PWM_RESOLUTION); // Привязываем КАЖДЫЙ пин к своему СОБСТВЕННОМУ каналу ledcAttachPin(LED1_PIN, LED1_CH); ledcAttachPin(LED2_PIN, LED2_CH); ledcAttachPin(LED3_PIN, LED3_CH); } void loop() { unsigned long currentTime = millis(); // Получаем текущее время // --- Управление Светодиодом 1 (Канал 0) --- if (currentTime - lastUpdate1 >= FADE_DELAY1_MS) { lastUpdate1 = currentTime; // Обновляем время последнего изменения dutyCycle1 += fadeDir1; // Изменяем скважность // Проверяем границы и меняем направление if (dutyCycle1 >= MAX_DUTY_CYCLE) { dutyCycle1 = MAX_DUTY_CYCLE; fadeDir1 = -1; // Начать убывание } else if (dutyCycle1 <= 0) { dutyCycle1 = 0; fadeDir1 = 1; // Начать возрастание } // Применяем новую скважность к КАНАЛУ 0 ledcWrite(LED1_CH, dutyCycle1); } // --- Управление Светодиодом 2 (Канал 1) --- if (currentTime - lastUpdate2 >= FADE_DELAY2_MS) { lastUpdate2 = currentTime; dutyCycle2 += fadeDir2; if (dutyCycle2 >= MAX_DUTY_CYCLE) { dutyCycle2 = MAX_DUTY_CYCLE; fadeDir2 = -1; } else if (dutyCycle2 <= 0) { dutyCycle2 = 0; fadeDir2 = 1; } // Применяем новую скважность к КАНАЛУ 1 ledcWrite(LED2_CH, dutyCycle2); } // --- Управление Светодиодом 3 (Канал 2) --- if (currentTime - lastUpdate3 >= FADE_DELAY3_MS) { lastUpdate3 = currentTime; dutyCycle3 += fadeDir3; if (dutyCycle3 >= MAX_DUTY_CYCLE) { dutyCycle3 = MAX_DUTY_CYCLE; fadeDir3 = -1; } else if (dutyCycle3 <= 0) { dutyCycle3 = 0; fadeDir3 = 1; } // Применяем новую скважность к КАНАЛУ 2 ledcWrite(LED3_CH, dutyCycle3); } }
ledcWrite()
.setup()
мы теперь вызываем ledcSetup()
трижды, настраивая каналы 0, 1 и 2 (с одинаковыми параметрами частоты и разрешения для простоты).ledcAttachPin()
трижды, связывая пин 18 с каналом 0, пин 19 с каналом 1 и пин 21 с каналом 2.loop()
используется неблокирующий подход с millis()
для управления скоростью изменения яркости каждого светодиода независимо.ledcWrite(LED1_CH, ...)
, для второго - ledcWrite(LED2_CH, ...)
, для третьего - ledcWrite(LED3_CH, ...)
. Мы управляем разными каналами, что приводит к независимому поведению светодиодов.Дополнительные замечания и рекомендации.
Заключение.
Микроконтроллер ESP32 предоставляет мощную и гибкую систему для генерации ШИМ-сигналов, значительно превосходящую возможности базовых платформ вроде Arduino Uno. Ключевым элементом этой системы является периферийный модуль LEDC с его 16 независимыми каналами. Понимание концепции каналов, их настройки ledcSetup
, привязки к физическим выводам ledcAttachPin
и управления скважностью ledcWrite
является основой для эффективного использования ШИМ на ESP32.
Возможность настраивать частоту и разрешение в широких пределах, а также независимо управлять несколькими ШИМ-сигналами одновременно, делает ESP32 идеальным выбором для проектов, требующих точного управления яркостью светодиодов, скоростью двигателей, генерации звуковых сигналов и многих других задач. Для более сложных приложений управления двигателями следует рассмотреть использование специализированного модуля MCPWM. Освоение ШИМ на ESP32 открывает двери к созданию более продвинутых и функциональных электронных устройств.
Дополнительная информация к данному уроку:
Понравился урок: Управление ШИМ (PWM) на ESP32: Полное руководство от основ до практики? Не забудь поделиться с друзьями в соц. сетях.
А также подписаться на наш канал на YouTube, вступить в группу Вконтакте, в Telegram.
Спасибо за внимание!
Технологии начинаются с простого!
Фотографии к статье
Файлы для скачивания
![]() |
Пример 1 – Плавное изменение яркости одного светодиода.ino | 2 Kb | 23 | Скачать |
![]() |
Пример 2 – Один ШИМ-сигнал на нескольких выводах GPIO.ino | 2 Kb | 19 | Скачать |
![]() |
Пример 3 – Независимое управление яркостью нескольких светодиодов.ino | 3 Kb | 20 | Скачать |
Уроки ESP32 (заметки)
5 мая , 2025
Комментариев:0
Файлов для скачивания:3
Фото:4
Понравилась статья? Нажми
Виджеты для Easy HMI
Читайте также
Мы в соц сетях
Комментарии