С момента своего появления микроконтроллер ESP32 получил заслуженную любовь своих поклонников и активно применяется во множестве самоделок. Основной причиной такой любви фанатов является его «упакованность» всевозможными функциями и беспроводными способами подключения в том числе. В этой же статье мы поговорим о такой полезной функции, как ШИМ — «широтно-импульсная модуляция».

ШИМ — это метод, который позволяет, включая и выключая питающее напряжение, подающееся к потребителю, регулировать его функционирование. Например, если мы говорим об электродвигателе, то с помощью ШИМ-контроля возможно управлять скоростью его вращения.

Однако этот метод не ограничивается только регулировкой электродвигателей. С его помощью можно управлять яркостью светодиодов, лампочек.

Здесь мы подходим к определению коэффициента заполнения ШИМ. Если совсем по-простому, то он показывает соотношение времени, в течение которого на систему подаётся напряжение, ко времени, когда система выключена.

image
Картинка

В официальной документации espressif способы ШИМ-контроля разделены на 2 подпункта:

  • управление двигателями,
  • LED источники света.

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





Конечно, это всё достаточно условно, и всё может быть взаимозаменяемыми LED-контроль можно использовать для управления двигателями и наоборот.

Однако для двигателей там имеется специализированный API, предназначенный для использования из-под среды ESP-IDF.

Кстати сказать, в широко распространённых в интернете мануалах в основном задействуется именно управление с помощью LED-подхода, в рамках которого система предоставляет нам 16 независимых каналов, каждый из которых может быть настроен независимо от других.

Все 16 каналов тактируются генератором шины APB с частотой 80 МГц, 8 из которых имеет возможность выбрать тактовую частоту в 8 МГц. На мой взгляд, эта функция предназначена для создания энергоэффективных систем с малым потреблением.

В таблице ниже приводится официальная информация о наиболее частых конфигурациях выходного ШИМ сигнала:



Таким образом, из таблицы мы видим, что имеется некая взаимосвязь между выходной частотой, исходной тактовой частотой и разрешением.

Обычно по этому поводу на форумах часто возникают вопросы: «А какова максимально возможная частота ШИМа для ESP32?»

На этот вопрос можно ответить следующим образом: максимально возможная частота вычисляется с помощью следующей формулы:
исходная частота / 2^(в степени разрешения)
Например, 80 000 000 / 2^16 = 1,22 кГц

Таким образом, можно сказать, что разрешение сигнала (если по-простому) означает плавность его изменения, так как сигнал более высокого разрешения имеет большую градацию подуровней. Например, из таблицы выше видно, что 16-битный сигнал имеет 65536 градаций. То есть можно изменять яркость LED источника очень плавно.

К слову, таймеры ШИМ у ESP32 имеют максимальное разрешение в 20 бит. То есть на каждый из 16 каналов может быть назначено 20-битное разрешение.

Посчитаем, какой тогда будет максимально допустимая частота ШИМ:
80 000 000 / 2^20 = 76,3 Гц
В принципе, вы можете сами установить нужное разрешение для ваших целей. Например, я использую 8-битное разрешение, которое даёт мне возможность изменять выходной сигнал в пределах от 0 до 255 градаций. И такое количество «градаций на шкале» меня вполне устраивает. У вас может быть другая ситуация, поэтому можете подойти творчески к этому вопросу.

Приведённый выше метод расчёта позволяет понять, насколько высокая частота может быть в вашем конкретном случае, при вашем конкретном разрешении и частоте входного сигнала.
Например, если вы используете ШИМ для управления электродвигателем, то следует придерживаться частоты более 20 кГц, так как меньшая частота уже достаточно сильно пищит и хорошо слышима человеческим ухом.

Так как ESP32 не имеет возможности управлять ШИМом с помощью Arduino-функции analogWrite(), то обычно (вернее, «наиболее часто в интернете» — так как почти все ленятся и не хотят разбираться в среде разработки от espressif) используют библиотеку esp32-gal-ledc.h, которая предоставляет следующие функции для управления ШИМом:

ledcSetup(канал, частота, разрешение);    
ledcAttachPin(пин, канал);    
ledcWrite(канал, коэффициент заполнения);    
ledcRead(канал);    
ledcWriteTone(канал, частота);    
ledcReadFreq(канал);    
ledcDetachPin(пин);    

Из этого блока наиболее часто используются первые три функции. Пример их использования показан в участке кода ниже, который используется для управления микроэлектродвигателем робототехнической игрушки:
Код для двигателя
#include "esp32-hal-ledc.h"

//частота ШИМ
const int frequency = 30000;

//канал, на который назначим в дальнейшем ШИМ    
const int pwmChannel = 0;

//разрешение ШИМа (в битах)    
const int resolution = 8;

void setup() {

pinMode(motor1Pin1, OUTPUT);
pinMode(motor1Pin2, OUTPUT);

// задаём настройки ШИМ-канала:    
ledcSetup(pwmChannel, frequency, resolution);

}

void loop() {

//крутим двигатель в одну сторону    

ledcWrite(pwmChannel, 155); //255 - максимум оборотов
digitalWrite(motor1Pin1, HIGH);
digitalWrite(motor1Pin2, LOW);

delay (1000);

//крутим двигатель в другую сторону    

ledcWrite(pwmChannel, 155); //255 - максимум оборотов
digitalWrite(motor1Pin1, LOW);
digitalWrite(motor1Pin2, HIGH);

}
Или, например, вот так осуществляется управление яркостью светодиодов:
Код для светодиода
// пин светодиода
const int ledPin1 = 16;

//частота ШИМ                                         
const int frequency = 5000;

//канал, на который назначим в дальнейшем ШИМ                                         
const int ledChannel = 0;

//разрешение ШИМа (в битах)
const int resolution = 8;
 
void setup(){
// задаём настройки ШИМ-канала:                                         
ledcSetup(ledChannel, frequency, resolution);

// подключаем ШИМ-канал к пину светодиода:                                         
ledcAttachPin(ledPin1, ledChannel);

}
 
void loop(){
  // яркость плавно растёт                                         
  for(int i = 0; i <= 255; i++){   

  ledcWrite(ledChannel, dutyCycle);
  delay(1000);
  }

  // яркость плавно cпадает
  for(int i = 255; i >= 0; i--){

  ledcWrite(ledChannel, dutyCycle);   
  delay(1000);
  }
}
Выше я уже говорил, что espressif разработала свою среду для создания приложений под этот микроконтроллер, имеющий название ESP-IDF (Expressif's IoT Development Framework).

С помощью этого средства вы можете разрабатывать свои приложения под ESP32 с использованием языка C++ и C.

Для продвинутых пользователей espressif предлагает плагин этой среды, устанавливаемый в Arduino IDE.

Как обещает официальная документация, сборщик библиотек позволит вам интегрировать ESP-IDF в среду Arduino:
«Arduino Lib Builder is the tool that integrates ESP-IDF into Arduino. It allows you to customize the default settings used by Espressif and try them in Arduino IDE».
После этого вы сможете воспользоваться официальной документацией espressif, находящейся вот по этому адресу, и вам станет доступно API для ШИМ-контроля двигателя.

Например, инициализация выходов для подключения к ним драйвера двигателя, представляющего собой H-мост, происходит с помощью функции mcpwm_gpio_init(), установка разрешения осуществляется с помощью mcpwm_timer_set_resolution().

Кстати сказать, официально имеющийся API предоставляет весьма интересные свойства, например, способ установки контактов для отслеживания положения ротора, если двигатель является бесщёточным.
Скрин из официальной документации

Ещё одной интересной официальной опцией является обработка ошибок об отказе двигателя.
Скрин из официальной документации

Кстати сказать, для того чтобы работать со всеми функциями ESP32, необязательно переходить от привычной Arduino IDE к ESP-IDF.

Для этого всего лишь необходимо писать напрямую в регистры! Если вкратце, регистр представляет собой область памяти, находящуюся по определённому адресу (оффсету) и содержащую заранее сконфигурированные настройки микроконтроллера, заданные espressif.

Теоретически вы можете обращаться напрямую к полям этих регистров (конкретным битам регистра) и устанавливать в них нужные значения, настраивая таким образом микроконтроллер.

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

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

Например, LED-регистры управления находятся на 381 странице официального мануала ESP32 Technical Reference Manual в подразделе Register Summary.

Если мы обратимся к этому подразделу, то увидим большую таблицу регистров, а также их адресов (то есть, грубо говоря, по какому адресу лежит панель с «ручными крутилками», подкручивая которые мы можем добиться чего-либо).


Например, если мы обратимся ко второму регистру сверху и перейдём на 384 страницу, то увидим, что у этого регистра, который является 32-битным, имеются активные поля с 0 по 3 бит (все остальные — «reserved» — то бишь несконфигурированы). Эти поля обладают свойством read/write, то есть значения оттуда могут быть как прочитаны, так и записаны (0 или 1).



Примеры работы с регистрами и подробное объяснение, что это такое (на примере Arduino), вы можете найти вот здесь. Следует сразу сказать, что это непростая тема. Для ESP32 мне даже самому применять прямой доступ к регистрам не приходилось (не было нужды), тем не менее я хотел озвучить такую возможность.

Иногда использование ШИМа весьма помогает в самоделках, когда возникают нестандартные проблемы. Например, при создании радиоуправляемой машинки у меня возникла следующая проблема: при использовании стандартной библиотеки esp32_servo для управления робозахватом, установленным на машинке, возник конфликт библиотеки и существующих настроек ШИМ, которые были предназначены для колёс.



Никакие изменения настроек не помогали, кроме того, время было весьма ограничено. Вследствие чего стало понятно: использовать библиотеку в рамках этого ограниченного времени не получится.

В результате было применено решение, которое использует ШИМ и позволяет выполнять ту же работу, что и стандартная библиотека для сервы:
Код для сервы
#include "esp32-hal-ledc.h"


// переменные для свойств широтно-импульсной модуляции (ШИМ) сервы:                                         
 #define low 1638
 #define high 7864
 #define timer 16

void setup() {

//Настраиваем ШИМ: 1 канал, 50 Гц, 16-бит                                         
ledcSetup(1, 50, timer);

//1 канал ШИМ подключили к 18 пину                                         
ledcAttachPin(18, 1);

}

void loop() {

//повернули серву в одну сторону на 180 градусов                                         

  for (int i=high ; i >=low  ; i=i-100)
  {
        ledcWrite(1, i);
  }
  delay (1000);

//повернули серву в другую сторону на 180 градусов                                         

  for (int i=low ; i < high ; i=i+100)
  {
      ledcWrite(1, i);
  }       

}
Подробное объяснение этого решения (на английском) и пример кода лежат здесь.

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

А какие решения применяете вы?


НЛО прилетело и оставило здесь промокод для читателей нашего блога:

15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.

Комментарии (3)


  1. KN_Dima
    16.05.2022 12:44

    Циклы в коде для сервы как-то влияют на скорость отработки сервы? Кажется, что без Delay в теле цикла они бессмысленны.


    1. kot_review Автор
      16.05.2022 12:57
      +1

      Каких-то специальных замеров скорости не проводил, но этот код отлично работает для тех целей, которые обозначены: открытие и закрытие робозахвата. Всё работает чётко и без тормозов.


  1. alexhott
    17.05.2022 07:59

    Если капнуть чуть глубже то в основе шима счетчики, перед ними делители

    отсюда и зависимость частота + разрешение