In this ESP32 user guide, we will learn how to control a DC motor using L289N motor driver and ESP-IDF MCPWM motor driver library. We will briefly discuss L298N motor driver including its specifications, pinout, interfacing with ESP32 board, and then focus on programming our board in VS Code with ESP-IDF extension. We will create and build an ESP-IDF project where we will be able to control the speed and direction of the dc motor. MCPWM module of ESP32 will be primarily used for motor control using L289N motor driver.
Before we move forward, make sure you have the latest version of VS Code installed in your system with the ESP-IDF extension configured.
L298N Motor Driver Module Introduction
The L298N motor driver module is an inexpensive, easy-to-use motor driver module that is commonly used in robotics or areas where there is a need to control DC/stepper motors. It is a dual-channel H bridge motor driver which can be easily used to drive two motors whose speed and direction of rotation can be controlled. The figure below shows the L289N motor driver module.
As you can view in the figure above, the motor driver consists of several components including the L298N motor driver IC, 78M05 voltage regulator, 5V jumper enable, power LED, heat sink, resistors, and capacitors etc. All these form an integrated circuit. Also, note that a big heat sink surrounds the L298N Motor driver IC. To enable the 78M05 5V regulator, a jumper is used. The motor driver provides +12V and +5V terminals to supply power. If the power supply is less than 12V then the module is powered through the voltage regulator. The 5V pin in this case acts as an output to power the microcontroller. If the power supply is more than 12V, make sure the jumper is not intact and supply 5V power through the pin separately.
Note: Do not supply power to both the motor power supply input and the 5V power supply input when the jumper is intact.
VCC | The VCC pin (+12V) is the power supply pin for the motor. Supply voltages between 6-12V. |
GND | This is the pin that provides the common ground. |
5V | This is the +5V power supply pin for the L298N IC. It is used when the 5V enable jumper is not connected. When the jumper is connected, it acts as an output pin. |
ENA | This pin controls the speed of the motor A through the PWM signal. |
IN1 & IN2 | These are the input pins for motor A. They control the spinning direction for motor A. |
IN3 & IN4 | These are the input pins for motor B. They control the spinning direction for motor B. |
ENB | This pin controls the speed of the motor B through the PWM signal. |
OUT1 & OUT2 | OUT1: Positive terminal for motor A. OUT2: Negative terminal for motor B. These are the output pins for motor A. Motor A having voltage between 5-35V, will be connected through these two terminals. |
OUT3 & OUT4 | OUT3: Positive terminal for motor A. OUT4: Negative terminal for motor B. These are the output pins for motor B. |
Specifications
Let’s look at some specifications of the L298N motor driver module specified in the table below:
Driver Model | L298N |
Driver Chip | Double H-bridge L298N |
Maximum Power | 25W |
Maximum Motor Supply Voltage | 46V |
Maximum Motor Supply Current | 2A |
Driver Voltage | 5-35V |
Driver Current | 2A |
Size | 43x43x26mm |
Control DC Motor with ESP32 MCPWM Module and ESP-IDF
We will build and create a project in VS Code with ESP-IDF extension. The code will control the speed and direction of the dc motor using the MCPWM module which is the motor control pulse width modulator. We will use the example sketch provided by ESP-IDF extension under peripherals for MCPWM named mcpwm_brushed_dc_control.
Create Example Project
Open your VS Code and head over to View > Command Palette. Type ESP-IDF: New Project in the search bar and press enter.
Specify the project name and directory. We have named our project ‘DC_MOTOR_CONTROL.’ For the ESP-IDF board, we have chosen the custom board option. For ESP-IDF target, we have chosen ESP32 module. Click ‘Choose Template’ button to proceed forward.
In the Extension, select ESP-IDF option:
Head over to Peripherals > mcpwm > mcpwm_brushed_dc_control. Now click ‘Create project using template mcpwm_brushed_dc_control.’
You will get a notification that the project has been created. To open the project in a new window, click ‘Yes.’
This opens the project that we created inside the EXPLORER tab. There are several folders inside our project folder. This is the same for every project which you will create through ESP-IDF Explorer.
Now head over to the main.c file. The main folder contains the source code meaning the main.c file will be found here. Go to main > main.c and open it.
ESP32 MCPWM ESP-IDF Brushed DC Motor Control Code
We have modified the code provided as MCPWM brushed dc control example by ESP-IDF. In our case, the dc motor first moves forward for 5 seconds, then moves backward for 5 seconds, and then stops for 2 seconds until a push button is pressed. The push button count controls the speed of the motor, whereby each time the button is pressed, the duty cycle increases by 10. This causes a steady increase in the speed of the motor whenever the push button is pressed, otherwise, the motor stops.
#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);
}
How the Code Works?
The ESP32 development board consists of two MCPWM units that control different motors. It uses a PWM generator consisting of multiple submodules to handle different scenarios such as motor control, digital power conversion, power digital to analog converter etc.
The diagram below is taken from the ESP-IDF official programming guide by Espressif for the MCPWM module. It shows the submodules that form the MCPWM module.
As you can see in the diagram above, there are several submodules that form the MCPWM module such as:
- MCPWM Timer
- MCPWM Operator
- MCPWM Comparator
- MCPWM Generator
- MCPWM Fault
- MCPWM Sync
- Dead Time
- Carrier Modulation
- Brake
- MCPWM Capture
Each submodule has its own resource allocation. Lets look at the code to understand how things work to control the dc motor via the motor driver.
As usual we start off by including the necessary libraries for the project. This includes the FreeRTOS libraries to generate delays and create tasks, the gpio driver to configure the push button GPIO pin and the MCPWM libraries to control the DC motor.
#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"
Set up ESP32 GPIO pins that will be connected with IN1 and IN2 of L298N motor driver module. Here GPIO15 is assigned as input 1 and GPIO16 is assigned as input 2 for the motor driver.
Here, these two output signals will be used to move the dc motor forward or backward.
#define GPIO_PWM0A_OUT 15 //Set GPIO 15 as PWM0A
#define GPIO_PWM0B_OUT 16 //Set GPIO 16 as PWM0B
We will connect the push button at GPIO33. Therefore, we will define a variable called ‘PUSH_BUTTON_PIN_SPEED’ that will hold the GPIO pin 33. This will be used later on in the code to read the digital input that will control the speed of the dc motor.
#define PUSH_BUTTON_PIN_SPEED 33
Next, we define some variables to monitor the push button states and count.
int buttonState = 0;
int count_value =0;
int prestate =0;
We start off with the function mcpwm_example_gpio_initialize() which will be used to initialize the MCPWM GPIOs. This is done by calling the function mspwm_gpio_init() inside this function. This particular function initializes the two GPIOs as output signals from the selected unit. It takes in three parameters. The first parameter is the MCPWM number that sets the MCPWM unit from either 0 or 1. The second parameter is the MCPWM IO signal that sets the MCPWM signal. The third parameter is the GPIO number that will be configured for the MCPWM. Note that you have to call this function individually for each of the GPIO.
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);
}
The brushed_motor_forward() function is responsible for moving the DC motor in the forward direction according to the duty cycle value set. Inside this function, we first set the MCPWM signal PWM0B to a LOW state using mcpwm_set_signal_low(). This function takes in three parameters which includes the MCPWM unit, timer number and the operator respectively. Next, we set the duty cycle of each operator of PWM0A by using mcpwm_set_duty() function. This function takes in four parameters which are MCPWM unit, timer number of MCPWM, the generator value and the duty cycle value respectively. Lastly, we set the duty type of PWM0A as either active high or active low.
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
}
Similarly, the brushed_motor_backward() function is responsible for moving the DC motor in the backward direction according to the duty cycle value set. Inside this function, we first set the MCPWM signal PWM0A to a LOW state using mcpwm_set_signal_low(). Next, we set the duty cycle of each operator of PWM0B by using mcpwm_set_duty() function. Lastly, we set the duty type of PWM0B as either active high or active low.
Note: PWM0A signal moves the DC motor in the forward direction and PWM0B moves the DC motor in the backwards direction.
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
}
The brushed_motor_stop() function is responsible to stop the DC motor by setting both the output signals to a low state.
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);
}
Inside the mcpwm_example_brushed_motor_control() function, firstly the MCPWM GPIOs are initialized by calling mcpwm_example_gpio_initialize() function.
mcpwm_example_gpio_initialize();
After that the initial MCPWM configuration parameters are set. This includes the timer frequency, duty cycles of both the output signals, counter mode and duty mode inside the mcpwm_config_t structure. The mcpwm_init() function is called to initialize all these MCPWM parameters by specifying the MCPWM unit, timer number and the configure structure mcpwm_config_t as parameters inside it.
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
After that we call the three functions that we previously defined to move the motor forwards, backwards and then stopping it. The dc motor will move forwards with duty cycle 20 for 5 seconds. After 5 seconds are over, the motor will move backwards for 5 seconds with duty cycle 5 seconds. Then the motor stops.
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);
Then, we will set the direction of the push button pin as an input using the gpio_set_direction() function. This function takes in two arguments. The first argument is the GPIO pin and the second argument is the mode (input or output) we want to set the pin in. Using a continuous while loop, we will check the state of the push button pin. Whenever the push button will be pressed, the duty cycle will increase by 10. We will call brushed_motor_forward() function to move the dc motor in the forward direction with the increasing duty cycle values with each button press. When the push button is released, the motor stops.
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);
}
Inside the app_main() function, we will call the function xTaskCreate() to create the mcpwm example brushed motor control task that will set the speed and direction of the dc motor.
void app_main(void)
{
printf("Testing brushed motor...\n");
xTaskCreate(mcpwm_example_brushed_motor_control, "mcpwm_example_brushed_motor_control", 4096, NULL, 5, NULL);
}
Hardware Setup
Now let us setup our circuit by connecting the ESP32 development board with the L298N motor driver that will control the DC Motor and a push button
Required Equipment
- ESP32 board
- L289N Motor driver Module
- External 3-12 V power supply
- DC Motor
- Push Button
- 10k ohm pull up resistor
- Connecting Wires
- Breadboard
Assemble the circuit as shown in the connection diagram below.
TT DC gear motors are being used which require an operating voltage of 3-12V DC where the recommended operating voltage is 3-6V DC. Therefore we will use 4xAA batteries (4×1.5V = 6V) to supply power for the DC motors. Motor A output pins will be used to control this motor. Moreover, the 5V Enable jumper will not be removed and it will power up the L298N motor driver.
In ESP32, PWM is supported through all output pins GPIO only. So choose an GPIO pin to connect with the enable pin of the L298N motor driver. According to our code, the output signals are connected with GPIO15 and GPIO16, hence we connect IN1 and IN2 with these pins respectively.
There are four terminals on the push button. The ESP32 supplies power to one terminal at 3.3 volts, while the second terminal is linked through GPIO33 and a 10k ohm pull-down resistor via GPIO33. The common ground is connected to the resistor’s opposite end.
All devices will have a common ground.
Compiling the Sketch
To flash your chip, type the following command in the serial terminal. Remember to replace the COM port with the one through which your board is connected.
idf.py -p COMX flash monitor
After the code is successfully flashed, you can view different messages on the ESP-IDF terminal indicating the initialization of the MCPWM GPIO and configuration of the initial parameters as well.
The DC Motor starts rotating after all the configurations have been successfully set. You can control the speed of the motor with the push button as well. Watch the video below to have a better insight.
You may also like to read:
- Interface DS18B20 with ESP32 ESP-IDF and Display Readings on OLED
- DHT22 with ESP32 ESP-IDF and Display Readings on OLED
- BME280 with ESP32 ESP-IDF and Display Readings on OLED
- Interface OLED with ESP32 using ESP-IDF
- ESP-IDF ESP32 Deep Sleep Modes and Wake up Sources
We are a team of experienced Embedded Software Developers with skills in developing Embedded Linux, RTOS, and IoT products from scratch to deployment with a demonstrated history of working in the embedded software industry. Contact us for your projects: admin@esp32tutorials.com