Если вы раньше играли с Arduino, то знаете, насколько просто сгенерировать ШИМ-сигнал с по.webpмощью этойanalogWrite() функции — просто укажите используемый вывод и рабочий цикл, и все готово.
Но с ESP32 это похоже на игру на более сложном уровне.Мы получаем больше элементов управления (ура!), но нам также приходится управлять ими с умом (что немного сложнее).ESP32 просит нас уточнить еще несколько вещей, таких как частота ШИМ, разрешение ШИМ, используемый канал и, конечно же, рабочий цикл и номер контакта.Уф, это звучит как много, но не волнуйтесь!
Это руководство научит вас всему, что вам нужно знать о ШИМ на ESP32, от основных концепций до практических примеров.
Контакты ШИМ ESP32
На ESP32 выход ШИМ возможен на всех контактах GPIO, за исключением четырех контактов GPIO, предназначенных только для ввода.Выделенные ниже GPIO поддерживают ШИМ.
ШИМ на ESP32
ESP32 имеет два периферийных устройства ШИМ:периферийное устройство управления светодиодами (LEDC)ипериферийное устройство широтно-импульсного модулятора управления двигателем (MCPWM).
Периферийное устройство MCPWM предназначено для управления двигателем и включает в себя дополнительные функции, такие как мертвая зона и автоматическое торможение.С другой стороны, периферийное устройство LEDC специально разработано для управления светодиодами и включает в себя такие функции, как автоматическое затемнение, а также более продвинутые функции.Однако его можно использовать для генерации сигналов ШИМ для множества других целей.
В этом уроке мы сосредоточимся в первую очередь на периферийных устройствах LEDC.
Частота ШИМ
Периферийное устройство LEDC, как и большинство контроллеров ШИМ, использует таймер для генерации сигналов ШИМ.
Думайте о таймере как о том, что он «тикает», считая до тех пор, пока не достигнет максимального значения, после чего он сбрасывается до нуля, и следующий цикл счета начинается снова.Время между этими сбросами (т. е. время, необходимое для достижения максимального значения) представляетчастоту ШИМи измеряется в герцах (Гц).Например, если мы укажем частоту 1 Гц, таймеру понадобится 1 секунда, чтобы отсчитать от 0 до максимального значения, прежде чем начать следующий цикл.Если мы укажем частоту 1000 Гц, таймеру понадобится всего 1 миллисекунда, чтобы отсчитать от 0 до максимального значения.
ESP32 может генерировать сигнал ШИМ с частотой до 40 МГц.
Разрешение ШИМ
Итак, что же представляет собой это «максимальное» значение?«Максимальное» значение определяется разрешениемШИМ.Если разрешение ШИМ составляет «n» бит, таймер считает от 0 до 2n-1 перед сбросом.Например, если мы настроим таймер с частотой 1 Гц и разрешением 8 бит, таймеру понадобится 1 секунда, чтобы отсчитать от 0 до 255 (28).В случае частоты 1 Гц и разрешения 16 бит таймер все равно будет занимать 1 секунду, но будет считать от 0 до 65 535 (216).
Важно понимать, что когда у нас более высокое разрешение, у нас, по сути, больше «приращений таймера» в течение одного и того же заданного периода времени.Таким образом, мы имеем большую «детальность» во времени.
Разрешение ШИМ ESP32 можно регулировать от 1 до 16 бит.Это означает, что рабочий цикл может быть установлен на 65 536 (216) различных уровнях.Это дает вам точный контроль над такими вещами, как светодиоды, позволяющие им светиться с небольшими изменениями яркости, или моторы, позволяющие им работать с очень точной скоростью.
Рабочий цикл
Далее мы определяемрабочий циклвыхода ШИМ.Рабочий цикл показывает, сколько тактов таймера будет оставаться на высоком уровне на выходе ШИМ, прежде чем он упадет на низкий уровень.Это значение сохраняется в регистре захвата/сравнения таймера (CCR).
Когда таймер сбрасывается, выход ШИМ становится высоким.Когда таймер достигает значения, хранящегося в регистре захвата/сравнения, выходной сигнал ШИМ переходит в низкий уровень.Однако таймер продолжает отсчет.Как только таймер достигает максимального значения, выход ШИМ снова становится высоким, и таймер сбрасывается, чтобы начать отсчет следующего периода.
Например, представьте, что мы хотим сгенерировать сигнал ШИМ с частотой 1000 Гц, разрешением 8 бит и рабочим циклом 75%.Учитывая 8-битное разрешение, максимальное значение таймера будет 255 (28-1).При частоте 1000 Гц таймеру потребуется 1 мс (0,001 с) для отсчета от 0 до 255. Коэффициент заполнения ШИМ установлен на 75 %, это означает, что значение 256 * 75 % = 192 будет храниться в регистр захвата/сравнения.В этом случае при сбросе таймера выход ШИМ будет установлен на высокий уровень.Выход ШИМ будет оставаться высоким до тех пор, пока счетчик не достигнет 192, после чего он переключится на низкий уровень.Как только таймер достигнет значения 255, выход ШИМ снова переключится на высокий уровень, и таймер сбросится, чтобы начать отсчет следующего периода.
ШИМ-каналы
Теперь обратим внимание на понятие канала.Канал представляет собой уникальный выходной сигнал ШИМ.
ESP32 имеет 16 каналов, что означает, что он может генерировать 16 уникальных сигналов ШИМ.Эти каналы разделены на две группы, каждая из которых содержит по 8 каналов: 8 высокоскоростных каналов и 8 низкоскоростных каналов.
Высокоскоростные каналы реализованы аппаратно и поэтому способны обеспечить автоматическое и безотказное изменение рабочего цикла ШИМ.С другой стороны, низкоскоростные каналы лишены этих функций и полагаются на программное обеспечение для изменения своего рабочего цикла.
В каждой группе имеется 4 таймера, совместно используемых 8 каналами, что означает, что каждые два канала используют один и тот же таймер.Поскольку частоту определяет таймер, важно понимать, что мы не можем регулировать частоту каждого канала независимо внутри пары.Однако мы можем управлять рабочим циклом ШИМ каждого канала независимо.
Подводя итог, можно сказать, что ESP32 имеет 16 каналов ШИМ, которые могут работать на восьми различных частотах, и каждый из этих каналов может работать с разным рабочим циклом.
Чтобы сгенерировать сигнал ШИМ на определенном выводе, вы «прикрепляете» этот вывод к каналу.Эта связь сообщает ESP32 о необходимости вывода сигнала ШИМ, сгенерированного каналом, на указанный вывод.К одному и тому же каналу можно подключить несколько контактов, что означает, что все они могут выводить один и тот же сигнал ШИМ.Несмотря на то, что все контакты GPIO поддерживают выход ШИМ, ESP32 имеет только 16 каналов, поэтому одновременно может генерироваться только 16 различных форм сигналов ШИМ.Это не ограничивает количество контактов, которые могут выводить сигналы ШИМ, но ограничивает разнообразие сигналов, которые могут выводиться одновременно.
Фактически, если у вас есть набор светодиодов, которые вы хотите мигать идеально синхронно, вы можете настроить один канал с определенной частотой и рабочим циклом, а затем подключить к нему все соответствующие контакты (которые подключены к светодиодам). канал.Однако при работе с сервоприводами, особенно в таких ситуациях, как роботизированная рука, где каждый сустав (сервопривод) должен управляться независимо, становится выгодным назначать разные контакты разным каналам.
Выбор частоты и разрешения ШИМ
ESP32 может генерировать сигнал ШИМ с частотой до 40 МГц, а разрешение ШИМ можно регулировать от 1 до 16 бит.Но это не значит, что можно одновременно установить частоту 40 МГц и разрешение 16 бит.Это связано с тем, что максимальная частота ШИМ и разрешение зависят от источника синхронизации.
Чтобы проиллюстрировать это, рассмотрим часы (будь то часы процессора или таймер, не имеет значения), работающие на частоте 40 МГц.При этом максимально достижимая частота ШИМ также составляет 40 МГц.Мы не можем генерировать ШИМ-волну быстрее, чем позволяют наши часы.
А что с разрешением?Что ж, разрешение на самом деле зависит от того, насколько точно мы можем разделить один период волны ШИМ на разные рабочие циклы.И вот в чем суть: для разделения волны ШИМ требуется тактовая частота процессора, работающая на частоте PWM_freq * 2PWM_solve.Почему?Потому что для создания этих рабочих циклов вам нужно иметь возможность создавать эти временные интервалы.
Отсюда становятся ясными два важных момента:
- PWM_freq * 2PWM_разрешениене может превышать тактовую частоту.
- Частота ШИМ и разрешение взаимозависимы.Чем выше частота ШИМ, тем ниже разрешение рабочего цикла (и наоборот).
Согласнодокументации Espressif, источником тактовой частоты низкоскоростного таймера LEDC является тактовая частота APB 80 МГц.В качестве общего руководства следует стремиться поддерживать частоту PWM_freq * 2PWM_solveниже 80 МГц.
Кроме того, документация Espressif включает примеры, подтверждающие это:
- Частота ШИМ 5 кГц может иметь максимальное разрешение 13 бит, что дает разрешение ~0,012%, или 213=8192 дискретных уровня.
- Частота ШИМ 20 МГц может иметь максимальное разрешение 2 бита, что дает разрешение 25%, или 22=4 дискретных уровня.
- Частота ШИМ 40 МГц может иметь разрешение всего 1 бит, что означает, что рабочий цикл остается фиксированным на уровне 50% и не может быть отрегулирован.
Если все это не имеет для вас смысла, подумайте вот о чем: Arduino Uno обеспечивает форму ШИМ-сигнала ~490 Гц с разрядностью 8 бит.Этого более чем достаточно для плавного затухания светодиода.Так что всегда можно начать с этого (частота 500 Гц, разрешение 8 бит), а потом поиграться.
Генерация сигнала ШИМ с помощью библиотеки LEDC
Давайте приступим к делу!Ядро ESP32 Arduino включает в себябиблиотеку LEDC, которая упрощает управление широтно-импульсной модуляцией (ШИМ) на ESP32.Хотя библиотека LEDC была разработана для управления светодиодами, ее также можно использовать и для других приложений, где полезны сигналы ШИМ, например, для воспроизведения «музыки» через пьезодинамики и приводные двигатели.
Следующие шаги показывают, как использовать библиотеку LEDC для генерации сигнала ШИМ с помощью ESP32 с использованием Arduino IDE.
- Выбор канала ШИМ: на выбор доступно 16 каналов, пронумерованных от 0 до 15.
- Определите частоту ШИМ. Она может достигать 40 МГц, но для нашего примера затухания светодиода будет достаточно частоты 500 Гц.
- Определите разрешение ШИМ: оно варьируется от 1 до 16 бит.Количество дискретных уровней рабочего цикла определяетсяразрешением2 .Например, установка разрешения в 8 бит дает 256 дискретных уровней рабочего цикла [0–255].С другой стороны, разрешение в 16 бит обеспечивает 65 536 дискретных уровней рабочего цикла [0–65 535].
- Выберите контакты GPIO: выберите один или несколько контактов GPIO на ESP32 для вывода сигнала ШИМ.
- Настройте канал ШИМ: настройте выбранный канал ШИМ с выбранной частотой и разрешением с помощьюфункцииledcSetup (канал, частота, разрешение).
- Прикрепите выводы к каналу: прикрепите выбранные выводы к выбранному каналу с помощью функцииledcAttachPin(pin,channel).
- Установите рабочий цикл: Наконец, установите фактическое значение рабочего цикла для данного канала с помощьюфункцииledcWrite(channel, рабочий цикл).
Пример 1. Затухание светодиода
Вот краткий пример наброска, показывающий, как затухать светодиод — идеально подходит для демонстрации генерации ШИМ на ESP32.
Электропроводка
Схема подключения довольно проста.Возьмите светодиод и токоограничивающий резистор сопротивлением 330 Ом и поместите их на макетную плату, как показано на рисунке ниже.Подключите более длинную ножку светодиода (анод) к контакту GP18 через резистор сопротивлением 330 Ом, а более короткую ножку подключите к контакту заземления вашего ESP32.
Код
Скопируйте приведенный ниже код в свою Arduino IDE.
const int PWM_CHANNEL = 0; // ESP32 has 16 channels which can generate 16 independent waveforms const int PWM_FREQ = 500; // Recall that Arduino Uno is ~490 Hz. Official ESP32 example uses 5,000Hz const int PWM_RESOLUTION = 8; // We'll use same resolution as Uno (8 bits, 0-255) but ESP32 can go up to 16 bits // The max duty cycle value based on PWM resolution (will be 255 if resolution is 8 bits) const int MAX_DUTY_CYCLE = (int)(pow(2, PWM_RESOLUTION) - 1); const int LED_OUTPUT_PIN = 18; const int DELAY_MS = 4; // delay between fade increments void setup() { // Sets up a channel (0-15), a PWM duty cycle frequency, and a PWM resolution (1 - 16 bits) // ledcSetup(uint8_t channel, double freq, uint8_t resolution_bits); ledcSetup(PWM_CHANNEL, PWM_FREQ, PWM_RESOLUTION); // ledcAttachPin(uint8_t pin, uint8_t channel); ledcAttachPin(LED_OUTPUT_PIN, PWM_CHANNEL); } void loop() { // fade up PWM on given channel for(int dutyCycle = 0; dutyCycle <= MAX_DUTY_CYCLE; dutyCycle++){ ledcWrite(PWM_CHANNEL, dutyCycle); delay(DELAY_MS); } // fade down PWM on given channel for(int dutyCycle = MAX_DUTY_CYCLE; dutyCycle >= 0; dutyCycle--){ ledcWrite(PWM_CHANNEL, dutyCycle); delay(DELAY_MS); } }
Тестирование примера
Теперь загрузите код в свой ESP32.Вы увидите плавное изменение яркости светодиода от полностью выключенного до полностью горящего и обратно.
Объяснение кода:
В начале эскиза определены несколько констант для настройки характеристик ШИМ.PWM_CHANNELСначала определяетсяконстанта и устанавливается ее значение 0. ESP32 имеет 16 каналов (от 0 до 15), каждый из которых может генерировать независимые сигналы.
ЗатемPWM_FREQопределяется и устанавливается на 500. Это частота нашего ШИМ-сигнала.Напомним, что Arduino Uno использует ~490 Гц.Этого достаточно для плавного затухания светодиода.
ДалееPWM_RESOLUTIONустановлено значение 8. Это разрешение (в битах) сигнала ШИМ.Хотя мы используем 8 бит (так же, как Arduino Uno), ESP32 может работать до 16 бит.
const int PWM_CHANNEL = 0; const int PWM_FREQ = 500; const int PWM_RESOLUTION = 8;
После этогоMAX_DUTY_CYCLEрассчитывается по формуле 2PWM_RESOLUTION−1.Это значение определяет максимально достижимый рабочий цикл на основе выбранного разрешения.
const int MAX_DUTY_CYCLE = (int)(pow(2, PWM_RESOLUTION) - 1);
После этогоLED_OUTPUT_PINему присвоено значение 18. Это контакт ESP32 GPIO, к которому подключен светодиод.
И, наконец,DELAY_MSопределяется и устанавливается равным 4. Это задержка (в миллисекундах) между приращениями для управления скоростью затухания светодиода.
const int LED_OUTPUT_PIN = 18; const int DELAY_MS = 4;
Во время настройкиledcSetup()вызывается функция для настройки свойств ШИМ с использованием ранее определенных констант.Эта функция принимает три аргумента: канал ШИМ, частоту ШИМ и разрешение ШИМ.
ledcSetup(PWM_CHANNEL, PWM_FREQ, PWM_RESOLUTION);
ДалееledcAttachPin()функция используется для подключения вывода GPIO к каналу ШИМ, отвечающему за генерацию сигнала ШИМ.В этом случае сигнал ШИМ, генерируемыйPWM_CHANNEL, который соответствует каналу 0, появится наLED_OUTPUT_PIN, что соответствует GPIO 16.
ledcAttachPin(LED_OUTPUT_PIN, PWM_CHANNEL);
В цикле первыйforцикл итеративно увеличивает коэффициент заполнения от 0 до максимально возможного значения (MAX_DUTY_CYCLE).Это постепенно увеличивает яркость светодиода.
for(int dutyCycle = 0; dutyCycle <= MAX_DUTY_CYCLE; dutyCycle++){ ledcWrite(PWM_CHANNEL, dutyCycle); delay(DELAY_MS); }
Второйforцикл уменьшает рабочий цикл отMAX_DUTY_CYCLE0: это постепенно уменьшает яркость светодиода.
for(int dutyCycle = MAX_DUTY_CYCLE; dutyCycle >= 0; dutyCycle--){ ledcWrite(PWM_CHANNEL, dutyCycle); delay(DELAY_MS); }
В обоих циклах forledcWrite()функция используется для установки яркости светодиода.Эта функция принимает в качестве аргументов канал, генерирующий сигнал, и рабочий цикл.
ledcWrite(ledChannel, dutyCycle);
Пример 2. Один и тот же сигнал ШИМ на нескольких GPIO.
Вы можете получить один и тот же сигнал ШИМ на нескольких GPIO одновременно.Для этого вам просто нужно подключить эти GPIO к одному и тому же каналу.
Электропроводка
Добавьте в схему еще два светодиода так же, как вы делали это с первым.Подключите их к GPIO 19 и 21.
На изображении ниже показано, как все подключить.
Код
Теперь давайте изменим предыдущий пример, чтобы затухать три светодиода, используя один и тот же сигнал ШИМ из одного и того же канала.
const int PWM_CHANNEL = 0; // ESP32 has 16 channels which can generate 16 independent waveforms const int PWM_FREQ = 500; // Recall that Arduino Uno is ~490 Hz. Official ESP32 example uses 5,000Hz const int PWM_RESOLUTION = 8; // We'll use same resolution as Uno (8 bits, 0-255) but ESP32 can go up to 16 bits // The max duty cycle value based on PWM resolution (will be 255 if resolution is 8 bits) const int MAX_DUTY_CYCLE = (int)(pow(2, PWM_RESOLUTION) - 1); const int LED_1_OUTPUT_PIN = 18; const int LED_2_OUTPUT_PIN = 19; const int LED_3_OUTPUT_PIN = 21; const int DELAY_MS = 4; // delay between fade increments void setup() { // Sets up a channel (0-15), a PWM duty cycle frequency, and a PWM resolution (1 - 16 bits) // ledcSetup(uint8_t channel, double freq, uint8_t resolution_bits); ledcSetup(PWM_CHANNEL, PWM_FREQ, PWM_RESOLUTION); // ledcAttachPin(uint8_t pin, uint8_t channel); ledcAttachPin(LED_1_OUTPUT_PIN, PWM_CHANNEL); ledcAttachPin(LED_2_OUTPUT_PIN, PWM_CHANNEL); ledcAttachPin(LED_3_OUTPUT_PIN, PWM_CHANNEL); } void loop() { // fade up PWM on given channel for(int dutyCycle = 0; dutyCycle <= MAX_DUTY_CYCLE; dutyCycle++){ ledcWrite(PWM_CHANNEL, dutyCycle); delay(DELAY_MS); } // fade down PWM on given channel for(int dutyCycle = MAX_DUTY_CYCLE; dutyCycle >= 0; dutyCycle--){ ledcWrite(PWM_CHANNEL, dutyCycle); delay(DELAY_MS); } }
Тестирование примера
Теперь загрузите код в свой ESP32.Вы увидите, что все три светодиода погаснут одновременно, поскольку все GPIO выдают один и тот же сигнал ШИМ.
Объяснение кода:
Если вы сравните этот эскиз с первым, вы заметите, что они очень похожи, с небольшими отличиями.Давайте посмотрим на эти различия.
В глобальной областиопределены три дополнительные константы с именамиLED_1_OUTPUT_PIN,LED_2_OUTPUT_PINи , которым присвоены значения 18, 19 и 21 соответственно.LED_3_OUTPUT_PINЭто указывает на то, что мы имеем дело с тремя отдельными светодиодами, каждый из которых подключен к своему выводу GPIO на ESP32.
const int LED_1_OUTPUT_PIN = 18; const int LED_2_OUTPUT_PIN = 19; const int LED_3_OUTPUT_PIN = 21;
Затем при настройкеledcAttachPin()функция вызывается три раза, а не один раз, как в предыдущем коде.Каждый вызов функции подключает другой вывод GPIO (LED_1_OUTPUT_PIN,LED_2_OUTPUT_PIN,LED_3_OUTPUT_PIN) с одним и тем же каналом ШИМ (PWM_CHANNEL), что означает, что на все три светодиода будет выводиться один и тот же сигнал ШИМ.
ledcAttachPin(LED_1_OUTPUT_PIN, PWM_CHANNEL); ledcAttachPin(LED_2_OUTPUT_PIN, PWM_CHANNEL); ledcAttachPin(LED_3_OUTPUT_PIN, PWM_CHANNEL);
Обратите внимание: несмотря на добавление большего количества светодиодов, функция не измениласьloop().Это связано с тем, что один и тот же канал ШИМ управляет всеми светодиодами.
Пример 3. Затухание светодиода с помощью потенциометра
В этом примере эскиза показано, как уменьшить яркость светодиода с помощью потенциометра.
Электропроводка
Удалите два дополнительных светодиода, которые вы добавили в схему, и добавьте потенциометр.Подключите один внешний контакт потенциометра к 3,3 В, противоположный внешний контакт к GND, а его средний контакт (движок) к GPIO 34.
На изображении ниже показано, как все подключить.
Код
const int PWM_CHANNEL = 0; // ESP32 has 16 channels which can generate 16 independent waveforms const int PWM_FREQ = 500; // Recall that Arduino Uno is ~490 Hz. Official ESP32 example uses 5,000Hz const int PWM_RESOLUTION = 8; // We'll use same resolution as Uno (8 bits, 0-255) but ESP32 can go up to 16 bits // The max duty cycle value based on PWM resolution (will be 255 if resolution is 8 bits) const int MAX_DUTY_CYCLE = (int)(pow(2, PWM_RESOLUTION) - 1); const int LED_OUTPUT_PIN = 18; const int POT_PIN = 34; const int DELAY_MS = 100; // delay between fade increments void setup() { // Sets up a channel (0-15), a PWM duty cycle frequency, and a PWM resolution (1 - 16 bits) // ledcSetup(uint8_t channel, double freq, uint8_t resolution_bits); ledcSetup(PWM_CHANNEL, PWM_FREQ, PWM_RESOLUTION); // ledcAttachPin(uint8_t pin, uint8_t channel); ledcAttachPin(LED_OUTPUT_PIN, PWM_CHANNEL); } void loop() { int dutyCycle = analogRead(POT_PIN); dutyCycle = map(dutyCycle, 0, 4095, 0, MAX_DUTY_CYCLE); ledcWrite(PWM_CHANNEL, dutyCycle); delay(DELAY_MS); }
Тестирование примера
Теперь попробуйте повернуть потенциометр до упора в одну сторону, затем до упора в другую.Следите за светодиодом;на этот раз вы увидите плавное изменение яркости светодиода: от полного выключения на одном конце ручки потенциометра до полного свечения на другом.
Объяснение кода:
Снова!Между этим эскизом и первым есть лишь несколько отличий.Давайте посмотрим на эти различия.
Дополнительная константа с именемPOT_PINопределяется в глобальной области.Ему присвоено значение 34, что указывает на то, что потенциометр подключен к GPIO 34 на ESP32 и будет использоваться для динамического определения рабочего цикла и, следовательно, яркости светодиода.
const int POT_PIN = 34;
Затем в цикле вместо использования циклов for для постепенного увеличения и уменьшения яркости светодиодаanalogRead(POT_PIN)вызывается функция, которая принимает необработанные показания потенциометра.
int dutyCycle = analogRead(POT_PIN);
Показания потенциометра в диапазоне от 0 до 4095 затем преобразуются в новый диапазон от 0 доMAX_DUTY_CYCLEиспользованияmap()функции.Это сопоставление приводит значения потенциометра в соответствие с допустимыми значениями рабочего цикла ШИМ-сигнала.Это гарантирует возможность изменения яркости светодиода во всем диапазоне.
dutyCycle = map(dutyCycle, 0, 4095, 0, MAX_DUTY_CYCLE);
Наконец,ledcWrite()функция принимает это сопоставленное значение и применяет его непосредственно к сигналу ШИМ, регулируя яркость светодиода в режиме реального времени в зависимости от положения потенциометра.
ledcWrite(PWM_CHANNEL, dutyCycle);