В данной статье мы рассмотрим подключение к ESP32 двигателя постоянного тока и научимся управлять им с помощью драйвера двигателя L289N и библиотеки драйверов двигателя ESP-IDF MCPWM. Мы кратко рассмотрим принцип работы драйвера двигателя L298N, его характеристики, распиновку, взаимодействие с платой ESP32, а затем сосредоточимся на программировании нашей платы в VS Code с использованием расширения ESP-IDF. Мы создадим и скомпилируем проект ESP-IDF, в котором сможем управлять скоростью и направлением вращения двигателя постоянного тока. Модуль MCPWM ESP32 будет в основном использоваться для управления двигателем с помощью драйвера двигателя L289N.

Также по данной тематике вы можете посмотреть на нашем сайте статью про работу с контактами и двигателями в ESP32-S3 с помощью ESP-IDF - возможно, в ней некоторые моменты объяснены более доступным языком.
Модуль управления двигателем L298N
Модуль управления двигателем L298N — это недорогой и простой в использовании модуль, широко применяемый в робототехнике или областях, где необходимо управлять двигателями постоянного тока/шаговыми двигателями. Это двухканальный H-мостовой драйвер двигателя, который позволяет легко управлять двумя двигателями, скорость и направление вращения которых можно регулировать. На рисунке ниже показан модуль управления двигателем L298N.
Как видно на рисунке, драйвер двигателя состоит из нескольких компонентов, включая микросхему драйвера двигателя L298N, стабилизатор напряжения 78M05, перемычку включения 5 В, светодиод питания, радиатор, резисторы и конденсаторы и т. д. Все это образует интегральную схему. Также обратите внимание, что микросхема драйвера двигателя L298N окружена большим радиатором. Для включения стабилизатора 5 В 78M05 используется перемычка. Драйвер двигателя обеспечивает питание через выводы +12 В и +5 В. Если напряжение питания ниже 12 В, модуль питается через стабилизатор напряжения. В этом случае вывод 5 В действует как выход для питания микроконтроллера. Если напряжение питания выше 12 В, убедитесь, что перемычка повреждена, и подавайте питание 5 В через отдельный вывод.
Примечание: не подавайте питание одновременно на вход питания двигателя и на вход питания 5 В, если перемычка установлена.
Распиновка модуля драйвера двигателя L298N показана на следующем рисунке.
| VCC | это вывод питания двигателя. Напряжение питания составляет от 6 до 12 В. |
| GND | общий провод (земля) |
| 5V | Это вывод питания +5 В для микросхемы L298N. Он используется, когда перемычка разрешения 5 В не подключена. При подключении перемычки он работает как выходной вывод. |
| ENA | Этот вывод управляет скоростью двигателя А посредством ШИМ-сигнала. |
| IN1 & IN2 | Это входные контакты для двигателя А. Они управляют направлением вращения двигателя А. |
| IN3 & IN4 | Это входные контакты для двигателя B. Они управляют направлением вращения двигателя B. |
| ENB | Этот вывод управляет скоростью двигателя B посредством ШИМ-сигнала. |
| OUT1 & OUT2 | OUT1: Положительный вывод для двигателя A. OUT2: Отрицательный вывод для двигателя A. Это выходные контакты для двигателя A. Двигатель A с напряжением от 5 до 35 В будет подключен через эти два вывода. |
| OUT3 & OUT4 | OUT3: Положительный вывод для двигателя B. OUT4: Отрицательный вывод для двигателя B. Это выходные контакты для двигателя B. |
В принципе, распиновка стандартная для большинства подобных драйверов двигателей.
Технические характеристики
Технические характеристики модуля управления двигателем L298N указаны в таблице ниже.
| Модель драйвера | L298N |
| Чип драйвера | Двойной H-мост L298N |
| Максимальная мощность | 25 Вт |
| Максимальное напряжение питания двигателя | 46 В |
| Максимальный ток питания двигателя | 2А |
| Напряжение драйвера | 5-35 В |
| Ток драйвера | 2А |
| Размер | 43х43х26 мм |
Далее непосредственно рассмотрим уже его использование в данном проекте.
Управление двигателем постоянного тока с помощью ESP32 и ESP-IDF
Мы создадим проект в VS Code с расширением ESP-IDF. Код будет управлять скоростью и направлением вращения двигателя постоянного тока с помощью модуля MCPWM, который представляет собой модуль широтно-импульсной модуляции управления двигателем. Мы будем использовать пример скетча, предоставленный расширением ESP-IDF в разделе периферийных устройств для MCPWM, под названием mcpwm_brushed_dc_control.
Создание примера проекта
Откройте VS Code и перейдите в меню View > Command Palette («Вид» > «Палитра команд»). Введите в строке поиска ESP-IDF: New Project и нажмите Enter.
Укажите название проекта и каталог. Мы назвали наш проект «DC_MOTOR_CONTROL». Для платы ESP-IDF мы выбрали опцию custom board («пользовательская плата»). В качестве целевой платформы ESP-IDF мы выбрали модуль ESP32. Нажмите кнопку ‘Choose Template’ («Выбрать шаблон»), чтобы продолжить.
В разделе «Расширение» выберите опцию ESP-IDF:
Перейдите в раздел Peripherals > mcpwm > mcpwm_brushed_dc_control. Затем нажмите ‘Create project using template mcpwm_brushed_dc_control’ («Создать проект, используя шаблон mcpwm_brushed_dc_control»).
Вы получите уведомление о создании проекта. Чтобы открыть проект в новом окне, нажмите «Да».
Это откроет проект, который мы создали на вкладке «Проводник». Внутри папки проекта находится несколько папок. Это справедливо для каждого проекта, который вы будете создавать с помощью ESP-IDF Explorer.
Теперь перейдите к файлу main.c. В папке main находится исходный код, поэтому файл main.c будет расположен именно там. Перейдите в папку main > main.c и откройте его.
Код ESP32 для управления коллекторным двигателем постоянного тока с помощью ESP-IDF
Мы модифицировали код, предоставленный в качестве примера управления коллекторным двигателем постоянного тока MCPWM от ESP-IDF. В нашем случае двигатель постоянного тока сначала движется вперед в течение 5 секунд, затем движется назад в течение 5 секунд, а затем останавливается на 2 секунды, пока не будет нажата кнопка. Количество нажатий кнопки регулирует скорость двигателя, при этом каждый раз, когда кнопка нажимается, коэффициент заполнения увеличивается на 10. Это приводит к постоянному увеличению скорости двигателя при каждом нажатии кнопки, в противном случае двигатель останавливается.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.h" #include "esp_attr.h" #include "driver/mcpwm.h" #include "soc/mcpwm_periph.h" #define GPIO_PWM0A_OUT 15 //Set GPIO 15 as PWM0A #define GPIO_PWM0B_OUT 16 //Set GPIO 16 as PWM0B #define PUSH_BUTTON_PIN_SPEED 33 int buttonState = 0; int count_value =0; int prestate =0; static void mcpwm_example_gpio_initialize(void) { printf("initializing mcpwm gpio...\n"); mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_PWM0A_OUT); mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, GPIO_PWM0B_OUT); } /** * @brief motor moves in forward direction, with duty cycle = duty % */ static void brushed_motor_forward(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num , float duty_cycle) { mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_B); mcpwm_set_duty(mcpwm_num, timer_num, MCPWM_OPR_A, duty_cycle); mcpwm_set_duty_type(mcpwm_num, timer_num, MCPWM_OPR_A, MCPWM_DUTY_MODE_0); //call this each time, if operator was previously in low/high state } /** * @brief motor moves in backward direction, with duty cycle = duty % */ static void brushed_motor_backward(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num , float duty_cycle) { mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_A); mcpwm_set_duty(mcpwm_num, timer_num, MCPWM_OPR_B, duty_cycle); mcpwm_set_duty_type(mcpwm_num, timer_num, MCPWM_OPR_B, MCPWM_DUTY_MODE_0); //call this each time, if operator was previously in low/high state } /** * @brief motor stop */ static void brushed_motor_stop(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num) { mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_A); mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_B); } /** * @brief Configure MCPWM module for brushed dc motor */ static void mcpwm_example_brushed_motor_control(void *arg) { //1. mcpwm gpio initialization mcpwm_example_gpio_initialize(); //2. initial mcpwm configuration printf("Configuring Initial Parameters of mcpwm...\n"); mcpwm_config_t pwm_config; pwm_config.frequency = 1000; //frequency = 500Hz, pwm_config.cmpr_a = 0; //duty cycle of PWMxA = 0 pwm_config.cmpr_b = 0; //duty cycle of PWMxb = 0 pwm_config.counter_mode = MCPWM_UP_COUNTER; pwm_config.duty_mode = MCPWM_DUTY_MODE_0; mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config); //Configure PWM0A & PWM0B with above settings brushed_motor_forward(MCPWM_UNIT_0, MCPWM_TIMER_0, 20.0); vTaskDelay(5000 / portTICK_RATE_MS); brushed_motor_backward(MCPWM_UNIT_0, MCPWM_TIMER_0, 20.0); vTaskDelay(5000 / portTICK_RATE_MS); brushed_motor_stop(MCPWM_UNIT_0, MCPWM_TIMER_0); vTaskDelay(2000 / portTICK_RATE_MS); gpio_set_direction(PUSH_BUTTON_PIN_SPEED, GPIO_MODE_INPUT); while(1){ buttonState = gpio_get_level(PUSH_BUTTON_PIN_SPEED); if (buttonState == 1 && prestate == 0) { count_value = count_value + 10; float duty_cycle = count_value; brushed_motor_forward(MCPWM_UNIT_0, MCPWM_TIMER_0, duty_cycle); prestate = 1; } else if(buttonState == 0) { prestate = 0; brushed_motor_stop(MCPWM_UNIT_0, MCPWM_TIMER_0); } vTaskDelay(500 / portTICK_RATE_MS); } } void app_main(void) { printf("Testing brushed motor...\n"); xTaskCreate(mcpwm_example_brushed_motor_control, "mcpwm_example_brushed_motor_control", 4096, NULL, 5, NULL); } |
Как работает код?
Плата разработки ESP32 состоит из двух блоков MCPWM, управляющих различными двигателями. Она использует генератор ШИМ, состоящий из нескольких субмодулей, для обработки различных сценариев, таких как управление двигателем, цифровое преобразование энергии, цифро-аналоговый преобразователь мощности и т. д.
Приведённая ниже схема взята из официального руководства по программированию модуля MCPWM от компании Espressif. На ней показаны подмодули, составляющие модуль MCPWM.
Как видно на приведенной выше схеме, модуль MCPWM состоит из нескольких подмодулей, таких как:
- MCPWM таймер;
- Компаратор MCPWM;
- Генератор MCPWM;
- Синхронизация MCPWM;
- и др.
Каждый подмодуль имеет собственное распределение ресурсов. Давайте рассмотрим код, чтобы понять, как работает управление двигателем постоянного тока через драйвер двигателя.
Как обычно, начнём с включения необходимых библиотек для проекта. Это включает библиотеки FreeRTOS для генерации задержек и создания задач, драйвер GPIO для настройки контакта GPIO кнопки и библиотеки MCPWM для управления двигателем постоянного тока.
|
1 2 3 4 5 6 7 8 9 |
#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.h" #include "esp_attr.h" #include "driver/mcpwm.h" #include "soc/mcpwm_periph.h" |
Настройте контакты GPIO ESP32, которые будут подключены к входам IN1 и IN2 модуля драйвера двигателя L298N. В данном случае GPIO15 назначен входом 1, а GPIO16 — входом 2 для драйвера двигателя.
В данном случае эти два выходных сигнала будут использоваться для перемещения двигателя постоянного тока вперед или назад.
|
1 2 |
#define GPIO_PWM0A_OUT 15 //Set GPIO 15 as PWM0A #define GPIO_PWM0B_OUT 16 //Set GPIO 16 as PWM0B |
Мы подключим кнопку к выводу GPIO33. Для этого мы определим переменную с именем 'PUSH_BUTTON_PIN_SPEED', которая будет хранить значение вывода GPIO 33. Она будет использоваться позже в коде для считывания цифрового входного сигнала, который будет управлять скоростью двигателя постоянного тока.
|
1 |
#define PUSH_BUTTON_PIN_SPEED 33 |
Далее мы определяем несколько переменных для отслеживания состояний и подсчета нажатий кнопок.
|
1 2 3 |
int buttonState = 0; int count_value =0; int prestate =0; |
Начнём с функции mcpwm_example_gpio_initialize(), которая будет использоваться для инициализации выводов GPIO MCPWM. Это делается путём вызова функции mspwm_gpio_init() внутри этой функции. Эта функция инициализирует два вывода GPIO как выходные сигналы выбранного устройства. Она принимает три параметра. Первый параметр — это номер MCPWM, который устанавливает значение MCPWM-устройства равным 0 или 1. Второй параметр — это сигнал MCPWM IO, который устанавливает сигнал MCPWM. Третий параметр — это номер GPIO, который будет сконфигурирован для MCPWM. Обратите внимание, что эту функцию необходимо вызывать отдельно для каждого из выводов GPIO.
|
1 2 3 4 5 6 |
static void mcpwm_example_gpio_initialize(void) { printf("initializing mcpwm gpio...\n"); mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_PWM0A_OUT); mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, GPIO_PWM0B_OUT); } |
Функция brushed_motor_forward() отвечает за вращение двигателя постоянного тока в прямом направлении в соответствии с заданным значением коэффициента заполнения. Внутри этой функции мы сначала устанавливаем сигнал MCPWM PWM0B в низкое состояние с помощью функции mcpwm_set_signal_low(). Эта функция принимает три параметра: блок MCPWM, номер таймера и оператор. Затем мы устанавливаем коэффициент заполнения каждого оператора PWM0A с помощью функции mcpwm_set_duty(). Эта функция принимает четыре параметра: блок MCPWM, номер таймера MCPWM, значение генератора и значение коэффициента заполнения. Наконец, мы устанавливаем тип заполнения PWM0A как активный высокий или активный низкий.
|
1 2 3 4 5 6 |
static void brushed_motor_forward(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num , float duty_cycle) { mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_B); mcpwm_set_duty(mcpwm_num, timer_num, MCPWM_OPR_A, duty_cycle); mcpwm_set_duty_type(mcpwm_num, timer_num, MCPWM_OPR_A, MCPWM_DUTY_MODE_0); //call this each time, if operator was previously in low/high state } |
Аналогично, функция brushed_motor_backward() отвечает за движение двигателя постоянного тока в обратном направлении в соответствии с заданным значением коэффициента заполнения. Внутри этой функции мы сначала устанавливаем сигнал MCPWM PWM0A в низкое состояние с помощью функции mcpwm_set_signal_low(). Затем мы устанавливаем коэффициент заполнения каждого оператора PWM0B с помощью функции mcpwm_set_duty(). Наконец, мы устанавливаем тип заполнения PWM0B как активный высокий или активный низкий.
Примечание: сигнал PWM0A перемещает двигатель постоянного тока в прямом направлении, а сигнал PWM0B — в обратном направлении.
|
1 2 3 4 5 6 |
static void brushed_motor_backward(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num , float duty_cycle) { mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_A); mcpwm_set_duty(mcpwm_num, timer_num, MCPWM_OPR_B, duty_cycle); mcpwm_set_duty_type(mcpwm_num, timer_num, MCPWM_OPR_B, MCPWM_DUTY_MODE_0); //call this each time, if operator was previously in low/high state } |
Функция brushed_motor_stop() отвечает за остановку двигателя постоянного тока путем установки обоих выходных сигналов в низкое состояние.
|
1 2 3 4 5 |
static void brushed_motor_stop(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num) { mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_A); mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_B); } |
Внутри функции mcpwm_example_brushed_motor_control() сначала инициализируются выводы GPIO MCPWM путем вызова функции mcpwm_example_gpio_initialize().
|
1 |
mcpwm_example_gpio_initialize(); |
После этого устанавливаются начальные параметры конфигурации MCPWM. Это включает частоту таймера, коэффициенты заполнения обоих выходных сигналов, режим счетчика и режим заполнения в структуре mcpwm_config_t. Функция mcpwm_init() вызывается для инициализации всех этих параметров MCPWM путем указания блока MCPWM, номера таймера и структуры конфигурации mcpwm_config_t в качестве параметров.
|
1 2 3 4 5 6 7 8 |
printf("Configuring Initial Parameters of mcpwm...\n"); mcpwm_config_t pwm_config; pwm_config.frequency = 1000; //frequency = 500Hz, pwm_config.cmpr_a = 0; //duty cycle of PWMxA = 0 pwm_config.cmpr_b = 0; //duty cycle of PWMxb = 0 pwm_config.counter_mode = MCPWM_UP_COUNTER; pwm_config.duty_mode = MCPWM_DUTY_MODE_0; mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config); //Configure PWM0A & PWM0B with above settings |
После этого мы вызываем три функции, которые определили ранее, для перемещения двигателя вперед, назад и последующей остановки. Двигатель постоянного тока будет двигаться вперед с коэффициентом заполнения 20 в течение 5 секунд. По истечении 5 секунд двигатель будет двигаться назад в течение 5 секунд с коэффициентом заполнения 5 секунд. Затем двигатель остановится.
|
1 2 3 4 5 6 |
brushed_motor_forward(MCPWM_UNIT_0, MCPWM_TIMER_0, 20.0); vTaskDelay(5000 / portTICK_RATE_MS); brushed_motor_backward(MCPWM_UNIT_0, MCPWM_TIMER_0, 20.0); vTaskDelay(5000 / portTICK_RATE_MS); brushed_motor_stop(MCPWM_UNIT_0, MCPWM_TIMER_0); vTaskDelay(2000 / portTICK_RATE_MS); |
Затем мы зададим направление вывода кнопки в качестве входного сигнала, используя функцию gpio_set_direction(). Эта функция принимает два аргумента. Первый аргумент — это вывод GPIO, а второй — режим (вход или выход), в котором мы хотим установить вывод. Используя непрерывный цикл while, мы будем проверять состояние вывода кнопки. При каждом нажатии кнопки коэффициент заполнения будет увеличиваться на 10. Мы будем вызывать функцию brushed_motor_forward() для перемещения двигателя постоянного тока в прямом направлении с увеличением коэффициента заполнения при каждом нажатии кнопки. Когда кнопка будет отпущена, двигатель остановится.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
gpio_set_direction(PUSH_BUTTON_PIN_SPEED, GPIO_MODE_INPUT); while(1){ buttonState = gpio_get_level(PUSH_BUTTON_PIN_SPEED); if (buttonState == 1 && prestate == 0) { count_value = count_value + 10; float duty_cycle = count_value; brushed_motor_forward(MCPWM_UNIT_0, MCPWM_TIMER_0, duty_cycle); prestate = 1; } else if(buttonState == 0) { prestate = 0; brushed_motor_stop(MCPWM_UNIT_0, MCPWM_TIMER_0); } vTaskDelay(500 / portTICK_RATE_MS); } |
Внутри функции app_main() мы вызовем функцию xTaskCreate() для создания задачи управления коллекторным двигателем в примере mcpwm, которая будет задавать скорость и направление вращения двигателя постоянного тока.
|
1 2 3 4 5 |
void app_main(void) { printf("Testing brushed motor...\n"); xTaskCreate(mcpwm_example_brushed_motor_control, "mcpwm_example_brushed_motor_control", 4096, NULL, 5, NULL); } |
Настройка оборудования
Теперь давайте соберем нашу схему, подключив плату разработки ESP32 к драйверу двигателя L298N, который будет управлять двигателем постоянного тока, и кнопке.
Необходимое оборудование
- Плата ESP32.
- Модуль управления двигателем L289N.
- Внешний источник питания 3-12 В.
- Двигатель постоянного тока.
- Нажмите кнопку.
- Подтягивающий резистор 10 кОм.
- Соединительные провода.
- Макетная плата.
Соберите схему, как показано на рисунке ниже.
Используются редукторные двигатели постоянного тока TT, для работы которых требуется напряжение 3-12 В постоянного тока, тогда как рекомендуемое рабочее напряжение составляет 3-6 В постоянного тока. Поэтому для питания двигателей постоянного тока мы будем использовать 4 батарейки типа AA (4 × 1,5 В = 6 В). Для управления этим двигателем будут использоваться выходные контакты двигателя A. Кроме того, перемычка 5 В Enable не будет удалена и будет питать драйвер двигателя L298N.
В ESP32 ШИМ поддерживается только через все выходные контакты GPIO. Поэтому выберите контакт GPIO для подключения к контакту разрешения драйвера двигателя L298N. В нашем коде выходные сигналы подключены к GPIO15 и GPIO16, поэтому мы подключаем IN1 и IN2 к этим контактам соответственно.
На кнопке расположены четыре контакта. ESP32 подает питание на один контакт напряжением 3,3 вольта, а второй контакт соединен через GPIO33 и подтягивающий резистор 10 кОм. Общая земля подключена к противоположному концу резистора.
Все устройства будут иметь общее заземление.
Компиляция скетча
Для прошивки микросхемы введите следующую команду в последовательном терминале. Не забудьте заменить COM-порт на тот, через который подключена ваша плата.
|
1 |
idf.py -p COMX flash monitor |
После успешной прошивки кода на терминале ESP-IDF можно увидеть различные сообщения, указывающие на инициализацию MCPWM GPIO и настройку начальных параметров.
Двигатель постоянного тока начинает вращаться после успешной настройки всех параметров. Вы также можете регулировать скорость вращения двигателя с помощью кнопки.
40 просмотров











