Example Code for Arduino-PWM mode drive DC motor with encoder

This article offers a comprehensive guide to driving a DC motor with an encoder using Arduino's PWM mode. It includes detailed hardware and software preparation, a truth table for understanding PWM control modes, and sample code to control motor rotation speed using PID. The setup uses Romeo ESP32-S3 and TT Motor with Encoder, and the code initializes PWM, sets PID parameters, and reads encoder data to adjust motor speed. Ideal for Arduino enthusiasts looking to optimize motor control.

Hardware Preparation

Software Preparation

PWM mode drives the motor

Truth table of PWM control mode

IN1 IN2 OUT1 OUT2 explain
0 0 Hi-Z Hi-Z Taxiing (H-bridge high impedance)
0 1 L H retreat (OUT2 → OUT1)
1 0 H L advance (OUT1 → OUT2)
1 1 L L Braking (low side slow attenuation)

Sample Code

The motor rotation speed is controlled by PID

/**
 *@brief motorSpeeddetectionbyPWMwithencoder.ino
 *@ Motor driver in PWM mode (with encoder)
 */
#include "driver/mcpwm.h"
#include "soc/mcpwm_struct.h" 
#include "soc/mcpwm_reg.h"
#include <PID_v1.h>

//IO12->M1_IN1
//IO13->M1_IN2
#define MOTOR_PWM_IN1 12 
#define MOTOR_PWM_IN2 13

const byte encoder0pinA = 3;//A pin -> the interrupt pin 3
const byte encoder0pinB = 4;//B pin -> the digital pin 4
byte encoder0PinALast;
double duration,abs_duration;//the number of the pulses
boolean Direction;//the rotation direction
boolean result;
double val_output;//Used to provide PWM power values to the motor.
double Setpoint;
double Kp=0.6, Ki=5, Kd=0;
PID myPID(&abs_duration, &val_output, &Setpoint, Kp, Ki, Kd, DIRECT);

//Initialize the pins needed to generate the PWM signal
void mcpwm_init(void)
{
  //The binding needs to output the PWM pin to the PWM channel
  mcpwm_gpio_init(MCPWM_UNIT_0,MCPWM0A,MOTOR_PWM_IN1);
  mcpwm_gpio_init(MCPWM_UNIT_0,MCPWM0B,MOTOR_PWM_IN2);

  //Configure mcpwm information
  mcpwm_config_t pwm_config;
  pwm_config.frequency = 1000;/*!<Set frequency of MCPWM in Hz*/
  pwm_config.cmpr_a = 0;/*!<Set % duty cycle for operator a(MCPWMXA), i.e for 62.3% duty cycle, duty_a = 62.3*/
  pwm_config.cmpr_b = 0;/*!<Set % duty cycle for operator b(MCPWMXB), i.e for 48% duty cycle, duty_b = 48.0*/
  pwm_config.counter_mode/*!<Set  type of MCPWM counter*/ = MCPWM_UP_COUNTER/*!<For asymmetric MCPWM*/; 
  pwm_config.duty_mode/*!<Set type of duty cycle*/ = MCPWM_DUTY_MODE_0/*!<Active high duty, i.e. duty cycle proportional to high time for asymmetric MCPWM*/;

  // Initializes a unit of mcpwm and binds the clock
  mcpwm_init(MCPWM_UNIT_0,MCPWM_TIMER_0,&pwm_config);
}


void advance(/*range:0~100*/uint8_t speed)
{
  mcpwm_set_duty_type(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_GEN_A,MCPWM_DUTY_MODE_0);
  mcpwm_set_signal_low(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_GEN_B);
  mcpwm_set_duty(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_GEN_A,speed);
}

void retreat(/*range:0~100*/uint8_t speed)
{
  //Give MOTOR_DIRECTION_PIN a continuous low level
  mcpwm_set_signal_low(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_GEN_A);

  /*
   * Call function mcpwm_set_duty_type() every time after mcpwm_set_signal_high() 
   * or mcpwm_set_signal_low() to resume with previously set duty cycle.
   */
  mcpwm_set_duty_type(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_GEN_B,MCPWM_DUTY_MODE_0);
  
  //The MOTOR_STEP_PIN pin outputs a PWM wave with a duty cycle of "speed"
  mcpwm_set_duty(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_GEN_B,speed);
}

void stop(void)
{
  mcpwm_set_signal_low(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_GEN_A);
  mcpwm_set_signal_low(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_GEN_B);
}

void breake(void)
{
  mcpwm_set_signal_high(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_GEN_A);
  mcpwm_set_signal_high(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_GEN_B);
}

void wheelSpeed()
{
  int Lstate = digitalRead(encoder0pinA);
  if((encoder0PinALast == LOW) && Lstate==HIGH)
  {
    int val = digitalRead(encoder0pinB);
    if(val == LOW && Direction)
    {
      Direction = false; //Reverse
    }
    else if(val == HIGH && !Direction)
    {
      Direction = true;  //Forward
    }
  }
  encoder0PinALast = Lstate;

  if(!Direction)  
    duration++;
  else  
    duration--;
}

void EncoderInit()
{
  Direction = true;//default -> Forward
  pinMode(encoder0pinB,INPUT);
  attachInterrupt(digitalPinToInterrupt(encoder0pinA), wheelSpeed, CHANGE);
}

void setup() {
  Serial.begin(115200);//Initialize the serial port
  mcpwm_init();
  advance(70);
  Setpoint =80;  //Set the output value of PID
  myPID.SetMode(AUTOMATIC);//Set PID to automatic mode
  myPID.SetSampleTime(100);//Set the PID sampling frequency to 100ms
  EncoderInit();//Initialize the module
}

void loop() {
  abs_duration=abs(duration);
  result=myPID.Compute();//The return value of PID conversion is 1
  if(result)
  {
    Serial.print("Pluse: ");
    Serial.println(duration);
    duration = 0; //Count clear and wait for the next count
  }  
}

Was this article helpful?

TOP