Перейти к содержимому
Метки:

Доброго здравия, уважаемые друзья и читатели!

Насколько мы знаем из технического паспорта (перевод на русский язык), ESP32 имеет два программно-аппаратных комплекса для формирования сигналов с переменной широтно-импульсной модуляцией:

  • LED PWM Controller (LEDC). Относительно простой контроллер ШИМ без обратной связи, в основном предназначенный для управления яркостью светодиодов. Тем не менее, его легко можно использовать и для простого управления электродвигателями (без обратной связи), а так же для формирования различных звуков через пассивный зуммер, подключенный к ESP32. Этот контроллер имеет 16 независимых каналов ШИМ с разрешающей способностью до 20 бит и автоматическим аппаратным затуханием.
  • Motor control PWM. Два ШИМ-контроллера с более широкими возможностями для управления двигателями с аппаратной синхронизаций и различными дополнительными модулями синхронизации для измерения скорости вращения двигателя.

В данной статье поговорим о более простом варианте – LEDC PWM.

 


Теория

Контроллер LED PWM в первую очередь предназначен для управления яркостью светодиодов, хотя его можно использовать для генерации сигналов ШИМ и для других целей. Он имеет 16 каналов, которые могут генерировать независимые сигналы, которые в свою очередь могут использоваться, например, для управления RGB-светодиодами или электродвигателями. 

Каналы LEDC разделены на две группы по 8 каналов в каждой. Одна группа каналов LEDC работает в высокоскоростном режиме. Этот режим реализован аппаратно и обеспечивает автоматическое и плавное изменение рабочего цикла ШИМ. Другая группа каналов работает в низкоскоростном режиме, в нем рабочий цикл ШИМ должен изменяться драйвером программно. Каждая группа каналов также может использовать разные источники тактового сигнала.

Высокоскоростной контроллер имеет возможность автоматически постепенно увеличивать или уменьшать скважность ШИМ-сигнала, что позволяет выполнять плавное нарастание яркости или затухание без вмешательства процессора. Для повышения разрешения скважность ШИМ-сигнала также может “дрожать” между двумя значениями, когда настроено дробное значение заполнения.

На рисунке ниже представлено схематическое изображение контроллера LED, который содержит восемь высокоскоростных и восемь низкоскоростных каналов. В этой статье они будут называться hschn и lschn соответственно. Эти каналы управляются четырьмя независимыми таймерами, которые будут обозначены h_timerx и l_timerx.

Для высокоскоростных каналов имеется четыре высокоскоростных тактовых модуля, из которых можно выбрать один h_timerx. Для низкоскоростных каналов имеется также четыре низкоскоростных тактовых модуля, из которых можно выбрать один l_timerx.

На рисунке ниже показан один из высокоскоростных каналов ШИМ и связанный с ним высокоскоростной таймер.

Таймеры

Каждый из высокоскоростных таймеров LEDC_CLKx имеют два источника опорной тактовой частоты: REF_TICK или APB_CLK. Опорная таковая частота вначале делится делителем, коэффициент деления которого указывается в регистре LEDC_CLK_DIV_NUM_HSTIMERx, который содержит число с фиксированной точкой: старшие 10 бит представляют целую часть A, в то время как младшие восемь бит содержат дробную часть B. Эффективный коэффициент деления LEDC_CLK_DIVx вычисляется по формуле:

Коэффициент деления варьируется от 1 до 1023.

Когда дробная часть B не равна 0, входные и выходные импульсы делителя показаны на рисунке ниже:

Из 256 выходных импульсов, B делятся на (A+1), тогда как оставшиеся (256-B) делятся на A. Выходные импульсы, деленные на (A+1), равномерно распределены в общем количестве выходных импульсов. Выходные импульсы делителя являются базовыми входными сигналами для счетчика, который будет считать до значения, указанного в  регистре LEDC_HSTIMERx_DUTY_RES. Прерывание переполнения счетчика будет сгенерировано, как только значение счета достигнет 2LEDC_HSTIMERx_DUTY_RES − 1, после чего счетчик перезапустится с 0. Кроме того, можно в любой момент сбросить, приостановить и считать значения счетчика с помощью программного API.

Выходной сигнал таймера — это 20-битное значение, генерируемое счетчиком. Период цикла этого сигнала определяет частоту сигналов любых ШИМ каналов, подключенных к этому таймеру. Частота выходного ШИМ-сигнала  sig_outn зависит от частоты источника тактовой частоты таймера LEDC_CLKx, коэффициента деления делителя LEDC_CLK_DIVx, а также разрешения коэффициента заполнения (ширины счетчика) LEDC_HSTIMERx_DUTY_RES:

На основе приведенной выше формулы желаемое разрешение коэффициента заполнения duty можно рассчитать следующим образом:

В таблице ниже перечислены наиболее часто используемые частоты ШИМ и соответствующие им максимальные разрешения.

Примечания:

  1. Наибольшее разрешение вычисляется, когда делитель тактовой частоты LEDC_CLK_DIVx равен 1 и округляется в меньшую сторону. Если наибольшее разрешение, вычисленное по формуле, больше ширины счетчика 20 бит, то наибольшее разрешение должно быть 20 бит.
  2. Наименьшее разрешение вычисляется, когда делитель тактовой частоты LEDC_CLK_DIVx равен 1023 + 255/256 и округляется в большую сторону. Если наименьшее разрешение, вычисленное по формуле, меньше 0, то наименьшее разрешение должно быть 1.

 

Низкоскоростные таймеры l_timerx отличаются от высокоскоростных таймеров h_timerx в двух аспектах:

  1. Если источник тактовых сигналов для высокоскоростного таймера может быть выбран от REF_TICK или APB_CLK, то низкоскоростные таймеры получают тактовые сигналы либо от REF_TICK, либо от SLOW_CLOCK. Источником SLOW_CLOCK может быть либо APB_CLK (80 МГц), либо частота 8 МГц, что может быть выбрано с помощью  регистра LEDC_APB_CLK_SEL.
  2. Высокоскоростной счетчик и делитель работают без сбоев, что означает, что если ваше программное обеспечение изменяет максимальное значение счетчика или делителя, обновление вступит в силу сразу же после следующего прерывания по переполнению. Напротив, низкоскоростной счетчик и делитель обновят эти значения только при установке регистра LEDC_LSTIMERx_PARA_UP.

 

Каналы

Контроллер канала считывает 20-битное значение из счетчика выбранного высокоскоростного таймера и сравнивает его с набором из двух значений, чтобы установить выходной уровень сигнала. Первое значение, с которым сравнивается счетчик, является содержимым LEDC_HPOINT_HSCHn; если эти два значения совпадают, на выходе канала будет установлен высокий уровень. Второе значение является суммой LEDC_HPOINT_HSCHn и LEDC_DUTY_HSCHn[24..4]. Когда это значение достигнуто счетчиков, выход переключается на низкий уровень. Используя эти два значения, можно установить относительную фазу и заполнения рабочего цикл ШИМ канала. Рисунок ниже иллюстрирует этот механизм.

LEDC_DUTY_HSCHn — это регистр с фиксированной точкой с четырьмя дробными битами. Как упоминалось ранее, когда LEDC_DUTY_HSCHn[24..4] используется непосредственно в расчете ШИМ, LEDC_DUTY_HSCHn[3..0] может использоваться для “размывания” выходного сигнала. Если это значение не равно нулю, со статистической вероятностью LEDC_DUTY_HSCHn[3..0]/16 фактический импульс ШИМ будет на один цикл длиннее. Это эффективно увеличивает разрешение генератора ШИМ до 25 бит, но ценой небольшого “дрожания” в рабочем цикле.

Каналы также имеют возможность автоматически плавно переходить от одного значения рабочего цикла к другому. Эта функция включается установкой регистра LEDC_DUTY_START_HSCHn. Когда этот бит установлен, контроллер ШИМ автоматически увеличивает или уменьшает значение в LEDC_DUTY_HSCHn в зависимости от того, установлен или очищен бит LEDC_DUTY_INC_HSCHn соответственно. Скорость изменения заполнения рабочего цикла определяется следующим образом: каждый раз, когда выполняется цикл LEDC_DUTY_CYCLE_HSCHn, содержимое LEDC_DUTY_HSCHn[24..4] увеличивается или уменьшается на значение LEDC_DUTY_SCALE_HSCHn. Длительность затухания можно ограничить установкой LEDC_DUTY_NUM_HSCHn: затухание будет длиться только заданное количество циклов перед завершением. Завершенное затухание также генерирует прерывание.

Рисунок ниже демонстрирует это:

В этой конфигурации LEDC_DUTY_NUM_HSCHn увеличивается на LEDC_DUTY_SCALE_HSCHn для каждого LEDC_DUTY_CYCLE_HSCHn такта, что отражается в рабочем цикле выходного сигнала.

Примечания:

  • Когда LEDC находится в режиме затухания, не поддерживается выполнение операций над процессом (например приостановка) или настройка следующих регистров:
    • LEDC_HPOINT_H/LSCHn
    • LEDC_DUTY_H/LSCHn
    • LEDC_DUTY_START_H/LSCHn
    • LEDC_DUTY_INC_H/LSCHn
    • LEDC_DUTY_NUM_H/LSCHn
    • LEDC_DUTY_CYCLE_H/LSCHn
    • LEDC_DUTY_SCALE_H/LSCHn
  • Настройка второго затухания возможна только после генерации прерывания LEDC_DUTY_CHNG_END_HSCHn или LEDC_DUTY_CHNG_END_LSCHn.
  • Когда LEDC находится в режиме декрементного затухания и LEDC_DUTY_HSCHn равен 2LEDC_HSTIMERx_DUTY _RES, LEDC_DUTY_SCALE_HSCHn не может быть установлен в 1. 

 


LED Control API

Прежде чем контроллер PWM сможет вырабатывать ШИМ-сигнал, его необходимо настроить. Настройка выполняется в два отдельных этапа:

  1. Настроить таймер, задав частоту ШИМ-сигнала и максимальное разрешение рабочего цикла.
  2. Настроить канал, связав его с настроенным ранее таймером и GPIO, на который и будет выведен ШИМ-сигнал.

При этом справочная система настоятельно рекомендует нам сначала настроить таймер(ы) (вызвав ledc_timer_config()), а только потом настраивать канал(ы) (вызвав ledc_channel_config()). Это гарантирует, что частота выходного ШИМ-сигнала будет иметь необходимое значение до изменения уровня на выходном GPIO.

 

Настройка таймера

Настройка таймера осуществляется путем вызова функции ledc_timer_config():

esp_err_t ledc_timer_config(const ledc_timer_config_t *timer_conf)

с передачей ей структуры данных ledc_timer_config_t, содержащей следующие параметры конфигурации:

  • ledc_timer_t timer_num – номер таймера, который вы желаете настроить – от LEDC_TIMER_0 до LEDC_TIMER_3
  • ledc_mode_t speed_mode – режим работы таймера LEDC_HIGH_SPEED_MODE или LEDC_LOW_SPEED_MODE (напомню, это определяет источник тактового сигнала таймера и режим обновления его параметров)
  • uint32_t freq_hz – здесь мы должны указать выходную частоту ШИМ-сигнала в Герцах
  • ledc_timer_bit_t duty_resolution – задаем максимальное разрешение таймера от LEDC_TIMER_1_BIT до LEDC_TIMER_20_BIT, а следовательно и разрешение ШИМ-сигнала
  • ledc_clk_cfg_t clk_cfg – с помощью этого параметра указываем источник опорного тактового сигнала, например LEDC_USE_APB_CLK или LEDC_USE_RC_FAST_CLK или LEDC_USE_REF_TICK. А можно просто указать LEDC_AUTO_CLK и оставить этот выбор на совести разработчиков API.
  • bool deconfigure – этот флаг нужно взвести, если таймер вам больше не нужен вы хотите “разнастроить”.

Частота ШИМ и разрешение рабочего цикла взаимозависимы. Чем выше частота ШИМ, тем ниже доступное разрешение рабочего цикла, и наоборот. Эта взаимосвязь может быть важна, если вы планируете использовать этот API для целей, отличных от изменения интенсивности светодиодов. 

Исходный опорный тактовый сигнал также может ограничивать частоту ШИМ. Чем выше частота опорного тактового сигнала, тем выше может быть настроена максимальная частота ШИМ.

Например, частота ШИМ 5000 Гц может иметь максимальное разрешение рабочего цикла 13 бит. Это означает, что заполнение рабочего цикла может быть установлено от 0 до 100% с разрешением ~ 0,012% (213 = 8192 дискретных уровня интенсивности свечения светодиода). Однако следует помнить, что эти параметры также зависят от тактового сигнала, тактирующего таймер контроллера светодиодов ШИМ, который, в свою очередь, тактирует канал.

В таблице ниже перечислены некоторые наиболее часто используемые частоты ШИМ и соответствующие им максимальные разрешения.

LEDC API может использоваться для генерации ШИМ-сигналов на гораздо более высоких частотах, которых достаточно для синхронизации других устройств, например, модуля цифровой камеры. В этом случае максимальная доступная частота составляет 40 МГц с разрешением по скважности 1 бит. Это означает, что скважность такого сигнала фиксирована на уровне 50% и не может быть изменена.

API LEDC выдаст сообщения об ошибке при попытке установить частоту и разрешение рабочего цикла, которые превышают диапазон, поддерживаемый оборудованием LEDC. Например, попытка установить частоту 20 МГц и разрешение рабочего цикла 3 бита приводит к следующей ошибке, сообщаемой на последовательном мониторе:

E (196) ledc: requested frequency and duty resolution cannot be achieved, try reducing freq_hz or duty_resolution. div_param=128

В такой ситуации необходимо уменьшить либо разрешение изменения заполнения рабочего цикла, либо частоту. Например, установка разрешения на 2 бита решает эту проблему и позволяет устанавливать заполнение рабочего цикла с шагом 25%, т. е. на 0%, 25%, 50%, 75% или 100%.

Драйвер LEDC также сообщает о попытках настроить комбинации разрешения частоты/рабочего цикла, которые ниже поддерживаемого минимума, например:

E (196) ledc: requested frequency and duty resolution cannot be achieved, try increasing freq_hz or duty_resolution. div_param=128000000

 

Что-бы не мучаться с выбором частоты и соответствующего ей максимального разрешения, можно воспользоваться функцией ledc_find_suitable_duty_resolution() которая вернет максимально возможное разрешение таймера, учитывая его исходную тактовую частоту и выбранную частоту ШИМ-сигнала.

 

Таймером можно управлять в процессе его работы – перенастраивать, приостанавливать, сбрасывать:

esp_err_t ledc_timer_set(ledc_mode_t speed_mode, ledc_timer_t timer_sel, uint32_t clock_divider, uint32_t duty_resolution, ledc_clk_src_t clk_src);
esp_err_t ledc_timer_rst(ledc_mode_t speed_mode, ledc_timer_t timer_sel);
esp_err_t ledc_timer_pause(ledc_mode_t speed_mode, ledc_timer_t timer_sel);
esp_err_t ledc_timer_resume(ledc_mode_t speed_mode, ledc_timer_t timer_sel);
esp_err_t ledc_set_freq(ledc_mode_t speed_mode, ledc_timer_t timer_num, uint32_t freq_hz);

 


Конфигурация канала

После того, как таймер настроен, можно приступать к настройке канала. Это делается путем вызова функции ledc_channel_config().

esp_err_t ledc_channel_config(const ledc_channel_config_t *ledc_conf)

Здесь мы также должны заполнить и передать соответствующую структуру ledc_channel_config_t:

  • int gpio_num – номер GPIO, на который мы будем выводить ШИМ-сигнал, можно использовать любой GPIO, способный работать в качестве выхода
  • ledc_timer_t timer_sel – таймер, выбранный в качестве источника сигнала от LEDC_TIMER_0 до LEDC_TIMER_3, разумеется таймер уже должен быть настроен
  • ledc_mode_t speed_mode – скоростной режим LEDC_HIGH_SPEED_MODE или LEDC_LOW_SPEED_MODE, должен соответствовать режиму таймера, который мы приаттачили в предыдущем параметре, иначе ничего не выйдет
  • ledc_channel_t channel – номер канала, который мы хотим задействовать для генерации ШИМ-сигнала, от LEDC_CHANNEL_0 до LEDC_CHANNEL_7
  • ledc_intr_type_t intr_type – настройка прерывания по завершению плавного нарастания или затухания, может принимать значения LEDC_INTR_DISABLE и LEDC_INTR_FADE_END.
  • uint32_t duty – начальный уровень заполнения рабочего цикла, должен быть в диапазоне от 0 до 2duty_resolution
  • int hpoint – точка смещения относительной фазы, то есть значение счетчика, при котором уровень сигнала на выходе изменяется с низкого на высокий; если это 0 (по умолчанию) – то выходной импульс начинается с началом каждого нового рабочего цикла, сдвига фазы нет
  • unsigned int flags.output_invert – бит, отвечающий за инвертирование выходного сигнала, может быть полезен в некоторых случаях.

 

Если вам потребуется “переобуться на ходу”, не обязательно переконфигурировать весь канал, API предусматривает несколько специальных функций, позволяющих изменять отдельные настройки канала, например:

esp_err_t ledc_bind_channel_timer(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_timer_t timer_sel);
esp_err_t ledc_set_pin(int gpio_num, ledc_mode_t speed_mode, ledc_channel_t channel);

 


Изменение заполнения рабочего цикла / скважности ШИМ сигнала

После того, как мы выполнили настройку канала, ШИМ-сигнал уже должен появиться на выбранном нами выводе GPIO с выбранным во время настройки значением duty. Кстати, если таки не появился и ошибок нет – проверьте “согласованность” режимов таймера и канала – я однажды наступил на эти грабли по невнимательности.

Но нам ведь необходимо “регулировать” это самое duty – заполнение рабочего цикла (или еще иногда говорят “скважность импульсов”, хотя строго говоря это не одно и то же: заполнение = 1 / скважность и наоборот).

Делается это очень просто, с помощью вызова функции ledc_set_duty():

esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty);

 

Если нужно изменить заполнение рабочего цикла одновременно с изменением фазы, используйте другую функцию ledc_set_duty_with_hpoint():

esp_err_t ledc_set_duty_with_hpoint(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty, uint32_t hpoint);

 

Но обе эти функции не меняют режим работы ШИМ немедленно, он обновится только после вызова ledc_update_duty():

esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel);

Этого уже достаточно, чтобы написать простенькую программку регулирования яркости светодиода, примеры вы найдете ниже.

 

Внимание! ledc_set_duty, ledc_set_duty_with_hpoint и ledc_update_duty не являются потокобезопасными функциями, не вызывайте их для управления одним и тем же каналом LEDC из разных задач!

Потокобезопасная версия APIledc_set_duty_and_update():

esp_err_t ledc_set_duty_and_update(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty, uint32_t hpoint)

К тому же она сразу же выполняет два действия – set и update.

 

Если по каким-то причинам необходимо считать текущее значение duty, то можно воспользоваться другими функциями:

uint32_t ledc_get_duty(ledc_mode_t speed_mode, ledc_channel_t channel);
int ledc_get_hpoint(ledc_mode_t speed_mode, ledc_channel_t channel);

 

Чтобы полностью отключить ШИМ-канал, можно воспользоваться функцией ledc_stop():

esp_err_t ledc_stop(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t idle_level);

В качестве последнего аргумента передается логический уровень на GPIO, который должен быть установлен на выходе, например 0.

 


Аппаратное нарастание яркости или затухание fade

Контроллер ШИМ имеет возможность настроить плавное затухание – fade без участия вашей программы, с использованием встроенных аппаратных возможностей. Для этого нам вначале необходимо задействовать этот сервис, используя функцию ledc_fade_func_install().

esp_err_t ledc_fade_func_install ( int intr_alloc_flags );

Это функция установит внутренний обработчик прерываний для LEDC.

После этого можно воспользоваться одной из перечисленных ниже функций:

  • ledc_set_fade() – настроить затухание (или нарастание) с указанием начального duty, направления изменения, шага приращения и количеством тиков на каждый шаг изменения
  • ledc_set_fade_with_time() – настроить затухание (или нарастание) до заданного конечного duty за определенное время
  • ledc_set_fade_with_step() – настроить затухание (или нарастание) до заданного конечного duty с заданным шагом 

 

Особенности использования fade api

Прежде чем рассмотреть их поподробнее, стоить сразу отметить несколько особенностей всех этих функций.

В любом из режимов затухания следующий запуск затухания или фиксированного заполнения рабочего цикла возможно не раньше, чем закончится последнее запущенное затухание. Так же из-за аппаратных ограничений нет никакой возможности остановить затухание до того, как оно достигнет заданного целевого duty.

Затухание может работать в блокирующем или неблокирующем режиме. В блокирующем режиме все просто – программа “двинется дальше”, только после того, как закончится выполнение затухания. Что бы “перехватить” окончание затухания в неблокирующем режиме, нужно зарегистрировать функцию обратного вызова окончания затухания для каждого канала, вызвав  ledc_cb_register() после установки службы затухания.

esp_err_t ledc_cb_register(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_cbs_t *cbs, void *user_arg);

Callback-функция конца затухания – это обычный обработчик прерываний, ее прототип определен в ledc_cb_t, где вы должны вернуть логическое значение из функции обратного вызова, указывающее, пробуждена ли эта функция обратного вызова высокоприоритетной задачей.

typedef bool (*ledc_cb_t)(const ledc_cb_param_t *param, void *user_arg);

Так как callback-функция является обработчиком прерываний, то она должна быть помещена в IRAM, так как процедура обслуживания прерываний находится в IRAM. 

 

Способ 1: закат Солнца вручную

Для того, чтобы запустить затухание (или нарастание), можно воспользоваться функцией ledc_set_fade():

esp_err_t ledc_set_fade(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty, ledc_duty_direction_t fade_direction, uint32_t step_num, uint32_t duty_cycle_num, uint32_t duty_scale);

где:

  • speed_mode — укажите группу каналов LEDC с необходимым режимом LEDC_HIGH_SPEED_MODE или LEDC_LOW_SPEED_MODE.
  • channel — канал LEDC, который необходим настроить LEDC_CHANNEL_0 до LEDC_CHANNEL_7
  • duty — задает начало градиентного режима, диапазон должен соответствовать разрешению, то есть  [0, 2duty_resolution]
  • fade_direction — задает направление градиента – LEDC_DUTY_DIR_DECREASE (затухание) или LEDC_DUTY_DIR_INCREASE (нарастание)
  • step_num — задает общее количество шагов изменения duty
  • duty_cycle_num — задает количество тиков LEDC за каждый шаг изменения duty, то есть количество импульсов ШИМ с одинаковой скважностью на каждом шаге
  • duty_scale — задает размер изменения duty на каждом шаге изменения градиента

Дабы затухание, настроенное с помощью ledc_set_fade(), вступило в свою законную силу, вызовите уже знакомую нам функцию ledc_update_duty().

// Нарастание от 0 до 255 с шагом 1 по 100 тиков на каждый шаг
ledc_set_fade(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, 0, LEDC_DUTY_DIR_INCREASE, 255, 100, 1);
ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0);

Эта функция не является потокобезопасной, поэтому не стоит использовать её из разных задач.

 

Способ 2. затухание за заданное время

Если необходимо соблюсти время, за которое необходимо изменить яркость светодиода, можно воспользоваться альтернативной версией ledc_set_fade_with_time():

esp_err_t ledc_set_fade_with_time(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t target_duty, int max_fade_time_ms);

где:

  • speed_mode — укажите группу каналов LEDC с необходимым режимом LEDC_HIGH_SPEED_MODE или LEDC_LOW_SPEED_MODE.
  • channel — канал LEDC, который необходим настроить LEDC_CHANNEL_0 до LEDC_CHANNEL_7
  • target_duty – целевое (конечное) значение заполнения рабочего цикла, которого необходимо достичь за…
  • max_fade_time_ms – заданное время затухания в миллисекундах

Прежде чем это начнет работать, необходимо вызвать функцию запуска fade, но уже  ledc_fade_start():

esp_err_t ledc_fade_start(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_fade_mode_t fade_mode);

где мы должны указать режим работы fade_mode – не блокирующий LEDC_FADE_NO_WAIT  (для определения окончания необходимо использовать callback-функцию прерывания) или блокирующий LEDC_FADE_WAIT_DONE (функция вернет управление только после того, как fade будет выполнено полностью) .

// Нарастание от текущего значения до 255 за время не более 1 секунды
ledc_set_fade_with_time(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, 255, 1000);
// Выполняем в блокирующем режиме
ledc_fade_start(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, LEDC_FADE_WAIT_DONE);

 

Эти функции не является потокобезопасными, поэтому не стоит их использовать из разных задач. Существует потокобезопасная версия данной функции:

esp_err_t ledc_set_fade_time_and_start(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t target_duty, uint32_t max_fade_time_ms, ledc_fade_mode_t fade_mode)

 

Способ 3. Затухание с заданным шагом

Ещё один способ – воспользоваться функцией ledc_set_fade_with_step():

esp_err_t ledc_set_fade_with_step(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t target_duty, uint32_t scale, uint32_t cycle_num);

где:

  • speed_mode — укажите группу каналов LEDC с необходимым режимом LEDC_HIGH_SPEED_MODE или LEDC_LOW_SPEED_MODE.
  • channel — канал LEDC, который необходим настроить LEDC_CHANNEL_0 до LEDC_CHANNEL_7
  • target_duty – целевое (конечное) значение заполнения рабочего цикла, которого необходимо достичь 
  • scale – задает размер изменения duty на каждом шаге изменения градиента
  • cycle_num – задает количество тиков LEDC за каждый шаг изменения duty, то есть количество импульсов ШИМ с одинаковой скважностью на каждом шаге
// Нарастание от текущего значения до 255 с шагом 1 по 100 тиков на шаг
ledc_set_fade_with_step(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, 255, 1, 100);
// Выполняем в блокирующем режиме
ledc_fade_start(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, LEDC_FADE_WAIT_DONE);

 

Эти функции не является потокобезопасными, поэтому не стоит их использовать из разных задач. Существует потокобезопасная версия данной функции:

esp_err_t ledc_set_fade_step_and_start(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t target_duty, uint32_t scale, uint32_t cycle_num, ledc_fade_mode_t fade_mode);

 


Примеры использования

1. Настройка таймера и канала

Настройка таймера:

ledc_timer_config_t ledc_timer;
ledc_timer.duty_resolution = LEDC_TIMER_8_BIT;  // Разрешение таймера 8 бит: это значения от 0 до 255
ledc_timer.freq_hz = 1000;                      // Частота ШИМ - 1 кГц
ledc_timer.speed_mode = LEDC_HIGH_SPEED_MODE;   // Выбираем высокоскоростную группу таймеров и каналов
ledc_timer.timer_num = LEDC_TIMER_0;            // Пусть будет первый таймер из 4 возможных
ledc_timer.clk_cfg = LEDC_AUTO_CLK;             // Выбор опорной частоты таймера оставим на совести производителя
ledc_timer.deconfigure = 0;                     // Расконфигурацию делать не нужно
// Настраиваем таймер
ledc_timer_config(&ledc_timer);         

Настройка канала:

ledc_channel_config_t ledc_channel;
ledc_channel.channel = LEDC_CHANNEL_0;          // Канал 0
ledc_channel.gpio_num = 5;                      // Для вывода сигнала выберем GPIO5
ledc_channel.intr_type = LEDC_INTR_DISABLE,     // Прерывание не требуется
ledc_channel.speed_mode = LEDC_HIGH_SPEED_MODE; // Выбираем высокоскоростную группу таймеров и каналов
ledc_channel.timer_sel = LEDC_TIMER_0;          // Используем ранее настроенный таймер 0 (той же группы)
ledc_channel.duty = 0;                          // Начальное заполнение цикла - 0%
ledc_channel.hpoint = 0;                        // Относительная фаза
ledc_channel.flags.output_invert = 1;           // Инвертирование сигнала на выходе
// Настраиваем канал
ledc_channel_config(&ledc_channel);

 

2. Плавное изменение яркости светодиода

Никто и ничто не мешает нам управлять скважностью ШИМ-сигнала “вручную”, например в цикле:

for (uint16_t i = 0; i < 256; i++) 
{
    ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, i);
    ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0);
    vTaskDelay(500 / portTICK_PERIOD_MS);
}

 

Но наверное правильнее возложить эти обязанности на аппаратные возможности, как это было указано выше:

// Нарастание от текущего значения до 255 с шагом 1 по 100 тиков на шаг
ledc_set_fade_with_step(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, 255, 1, 100);
// Выполняем в блокирующем режиме
ledc_fade_start(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, LEDC_FADE_WAIT_DONE);

или

// Нарастание от текущего значения до 255 за время не более 1 секунды
ledc_set_fade_with_time(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, 255, 1000);
// Выполняем в блокирующем режиме
ledc_fade_start(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, LEDC_FADE_WAIT_DONE);

 

3. Генерация звука с помощью ШИМ-канала

Те, кто работал с Arduino-кодом, возможно помнят про функцию tone(), с помощью которой можно генерировать сигналы различной тональности на пассивной “пищалке”-зуммере. На ESP32 можно легко приспособить для этой цели аппаратные возможности LEDC-генерации, да еще и громкость можно регулировать за счет изменения duty.

Настройки LEDC выполнены в виде макросов:

#define CONFIG_BEEP_TIMER    LEDC_TIMER_0          // timer index (0-3)
#define CONFIG_BEEP_CHANNEL  LEDC_CHANNEL_0        // channel index (0-7)
#define CONFIG_BEEP_DRES     LEDC_TIMER_8_BIT      // resolution of PWM duty
#define CONFIG_BEEP_MODE     LEDC_HIGH_SPEED_MODE  // timer mode

Для управления звуком используется следующая структура:

typedef struct {
  uint16_t frequency1; // Частота тона 1
  uint16_t frequency2; // Частота тона 2 (для сирены) или 0
  uint16_t duration;   // Длительность одного тона
  uint8_t  count;      // Количество смен тональностей
  uint8_t  duty;       // Заполнение цикла (0~255)
} beep_data_t;

 

Для того, чтобы “принимать” команды на управление звуком, запущена отдельная задача, никакой особой работы она не выполняет.

Код задачи довольно прост:

void beepTaskExec(void *pvParameters)
{
  // Инициализация LEDC
  {
    ledc_timer_config_t ledc_timer;
    memset(&ledc_timer, 0, sizeof(ledc_timer));
    ledc_timer.duty_resolution = CONFIG_BEEP_DRES;
    ledc_timer.freq_hz = CONFIG_BEEP_FREQ;           
    ledc_timer.speed_mode = CONFIG_BEEP_MODE;
    ledc_timer.timer_num = CONFIG_BEEP_TIMER;
    ledc_timer_config(&ledc_timer);
  };

  {
    ledc_channel_config_t ledc_channel;
    memset(&ledc_channel, 0, sizeof(ledc_channel));
    ledc_channel.channel = CONFIG_BEEP_CHANNEL;
    ledc_channel.duty = 0;
    ledc_channel.gpio_num = _pinBuzzer;
    ledc_channel.speed_mode = CONFIG_BEEP_MODE;
    ledc_channel.timer_sel = CONFIG_BEEP_TIMER;
    ledc_channel_config(&ledc_channel);
  };

  static beep_data_t data;
  static TickType_t xLastWakeTime;
  while (1) {
    if (xQueueReceive(_beepQueue, &data, portMAX_DELAY) == pdPASS) {
      xLastWakeTime = xTaskGetTickCount();
      for (uint8_t i = data.count; i > 0; i--) {
        // Воспроизводим частоту 1
        ledc_set_freq(CONFIG_BEEP_MODE, CONFIG_BEEP_TIMER, data.frequency1);
        ledc_set_duty(CONFIG_BEEP_MODE, CONFIG_BEEP_CHANNEL, data.duty);
        ledc_update_duty(CONFIG_BEEP_MODE, CONFIG_BEEP_CHANNEL);
        vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(data.duration));

        // Воспроизводим частоту 2 или отключаем звук
        if (data.frequency2 > 0) {
          ledc_set_freq(CONFIG_BEEP_MODE, CONFIG_BEEP_TIMER, data.frequency2);
          ledc_set_duty(CONFIG_BEEP_MODE, CONFIG_BEEP_CHANNEL, data.duty);
        } else {
          ledc_set_duty(CONFIG_BEEP_MODE, CONFIG_BEEP_CHANNEL, 0);
        };
        ledc_update_duty(CONFIG_BEEP_MODE, CONFIG_BEEP_CHANNEL);
        vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(data.duration));
      };
      // Серия закончена, отключаем звук
      ledc_set_duty(CONFIG_BEEP_MODE, CONFIG_BEEP_CHANNEL, 0);
      ledc_update_duty(CONFIG_BEEP_MODE, CONFIG_BEEP_CHANNEL);
    };
  };

  vTaskDelete(NULL);
}

 


Ссылки

  1. Техническое справочное руководство ESP32 > глава Контроллер светодиодов ШИМ.
  2. LED Control (LEDC) – справочная система ESP-IDF

 


Пожалуйста, оцените статью:
[ 5 из 5, всего 6 оценок ]

-= Каталог статей (по разделам) =-   -= Архив статей (подряд) =-

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *