ESP32 PWM

Introduction to PWM functions


PWM (Pulse Width Modulation)

PWM Signal Properties

A typical PWM signal has the following properties that we can control by programming the microcontroller’s PWM peripheral’s registers. Such as PWM Frequency, PWM Resolution, and PWM Duty Cycle.


PWM architecture

CMRx+1 >= CNR: PWM output high
CMRx+1 < CNR: PWM output low

PWM Frequency = PWM_Clock/(prescale+1)*(clock divider)/(CNR+1)

Duty ratio = (CMR+1)/(CNR+1)


NodeMCU-32S pinout


ESP32 PWM Channels

The ESP32 PWM controller has 8 high-speed channels and 8 low-speed channels, which gives us a total of 16 channels.
For each group, there are 4 timers / 8 channels. This means every two channels share the same timer. Therefore, we can’t independently control the PWM frequency of each couple of channels.

On NodeMCU-32S, GPIO34, GPIO35, GPIO36, GPIO39 don’t support PWM functionality, because they are input-only ADC pins.


ESP32 ledc APIs

List of all the LEDC APIs exposed by the driver. These functions are written for Arduino IDE port of ESP32.

  • ledcSetup(channel, frequency, resolution_bits);
  • ledcAttachPin(pin, channel);
  • ledcWrite(channel, dutycycle);
  • ledcRead(channel);
  • ledcWriteTone(channel, frequency);
  • ledcWriteNote(channel, note, octave);
  • ledcReadFreq(channel);
  • ledcDetachPin(pin);

Some important points to remember while configuring PWM Channel in ESP32:

  • As there are 16 PWM channels, the ‘channel’ argument takes any value between 0 and 15.
  • Next is the frequency of the PWM signal. You can set the frequency as per your requirements like 1 KHz, 5 KHz, 8 KHz, and 10 KHz.
  • The resolution of the PWM is also configurable and ESP32 PWM can be programmed anywhere between 1 bit to 16 bit resolution.
  • PWM frequency and resolution are inversely proportional and is dependent on the clock source. So, be careful when selecting the values for frequency and resolution.
  • Finally, assign a GPIO pin for PWM Output. You can assign any GPIO Pin but be careful when assigning (do not use already used GPIO pins like UART, SPI, etc.).

The following table shows a few commonly used PWM frequencies and resolutions.


[Homework]: ESP32_PWM_LED.ino

  • Edit the following code, and Verify it on NodeMCU-32S with a LED
#define LED_GPIO   13
#define PWM1_Ch    0
#define PWM1_Res   8
#define PWM1_Freq  1000
 
int PWM1_DutyCycle = 0;
 
void setup()
{
  ledcAttachPin(LED_GPIO, PWM1_Ch);
  ledcSetup(PWM1_Ch, PWM1_Freq, PWM1_Res);
}
 
void loop()
{
  while(PWM1_DutyCycle < 255)
  {
    ledcWrite(PWM1_Ch, PWM1_DutyCycle++);
    delay(10);
  }
  while(PWM1_DutyCycle > 0)
  {
    ledcWrite(PWM1_Ch, PWM1_DutyCycle--);
    delay(10);
  }
}

DC Servo

Manage Libraries: ESP32Servo

  • analogWrite.h
  • analogWrite.cpp
  • ESP32PWM.h
  • ESP32PWM.cpp
  • ESP32Servo.h
  • ESP32Servo.cpp
  • ESP32Tone.h
  • ESP32Tone.cpp

Examples: ESP32Servo/PWMExample


Examples: ESP32Servo/Sweep


Examples: ESP32Servo/ToneExample


[Homework]: ESP32_SG90.ino

  • read IO0 button to control SG90 rotation
  • every press on IO0 button increase 30 degree, back to 0 degree after reaching 180 degree

PCA9685


DC Motor Driver

H-Bridge Motor Driver Circuit


Motor Control Pulse Width Modulator (MCPWM)

MCPWM Overview
MCPWM Block Diagram


DRV8833 Dual H-Bridge Motor Driver

Manage Libraries: ESP32MotorControl

ESP32MotorControl.h
ESP32MotorControl.cpp

// Attach one motor
void ESP32MotorControl::attachMotor(uint8_t gpioIn1, uint8_t gpioIn2)
{
    attachMotors(gpioIn1, gpioIn2, 0, 0);
}

// Attach two motors
void ESP32MotorControl::attachMotors(uint8_t gpioIn1, uint8_t gpioIn2, uint8_t gpioIn3, uint8_t gpioIn4)
{
    // debug
    debug("init MCPWM Motor 0");

    // Attach motor 0 input pins.
    // Set MCPWM unit 0
    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, gpioIn1);
    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, gpioIn2);
    
    // Indicate the motor 0 is attached.
    this->mMotorAttached[0] = true;

    // Attach motor 1 input pins.
    if (!(gpioIn3 == 0 && gpioIn4 ==0)) {
        debug("init MCPWM Motor 1");
        // Set MCPWM unit 1
        mcpwm_gpio_init(MCPWM_UNIT_1, MCPWM1A, gpioIn3);
        mcpwm_gpio_init(MCPWM_UNIT_1, MCPWM1B, gpioIn4);

        // Indicate the motor 1 is attached.
        this->mMotorAttached[1] = true;
    }

    // Initial MCPWM configuration

    debug ("Configuring Initial Parameters of MCPWM...");

    mcpwm_config_t pwm_config;
    pwm_config.frequency = 1000;    //frequency,
    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

    mcpwm_init(MCPWM_UNIT_1, MCPWM_TIMER_1, &pwm_config);    //Configure PWM1A & PWM1B with above settings

    debug ("MCPWM initialized");
}
  
// Motor set speed forward

void ESP32MotorControl::motorForward(uint8_t motor, uint8_t speed)
{
    if (!isMotorValid(motor)) {
        return;
    }

    if (speed == 100) { // Full speed
        motorFullForward(motor);
    } else {
        // Set speed -> PWM duty 0-100
        if (motor == 0) {
            mcpwm_set_signal_low(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B);
            mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, speed);
            mcpwm_set_duty_type(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, MCPWM_DUTY_MODE_0); //call this each time, if operator was previously in low/high state

        } else {
            mcpwm_set_signal_low(MCPWM_UNIT_1, MCPWM_TIMER_1, MCPWM_OPR_B);
            mcpwm_set_duty(MCPWM_UNIT_1, MCPWM_TIMER_1, MCPWM_OPR_A, speed);
            mcpwm_set_duty_type(MCPWM_UNIT_1, MCPWM_TIMER_1, MCPWM_OPR_A, MCPWM_DUTY_MODE_0); //call this each time, if operator was previously in low/high state
        }
        mMotorSpeed[motor] = speed; // Save it
        mMotorForward[motor] = true;
        debug("Motor %u forward speed %u", motor, speed);
    }
}
  

Sketchbook>ESP32_RoboCar_DRV8833>


TB6612FNG Dual H-Bridge Motor Driver

The TB6612FNG is a dual motor driver IC from Toshiba.

  • Power supply voltage: VM = 15 V(max)
  • Output current; IOUT=1.2 A(ave) / 3.2 A (peak)
  • Output low ON resistor; 0.5Ω (upper+lower Typ. @VM≥ 5 V)
  • Standby (Power save) system
  • PWM freq. : 100KHz (max)

TB6612 driver

ESP32_TB6612.h
ESP32_TB6612.cpp

Motor::Motor(int In1pin, int In2pin, int PWMpin, int PWMch, int offset, int STBYpin)
{
  In1 = In1pin;
  In2 = In2pin;
  PWM = PWMpin;
  channel = PWMch;
  Standby = STBYpin;
  Offset = offset;
  
  pinMode(In1, OUTPUT);
  pinMode(In2, OUTPUT);
  pinMode(Standby, OUTPUT);
  ledcSetup(channel, DEFAULTFREQ, 10);   
  ledcAttachPin(PWM, channel);  
}

void Motor::fwd(int speed, int ch)
{
   digitalWrite(In1, HIGH);
   digitalWrite(In2, LOW);
   ledcWrite(ch, speed);
}

void Motor::rev(int speed, int ch)
{
   digitalWrite(In1, LOW);
   digitalWrite(In2, HIGH);
   ledcWrite(ch, speed);   
}

void Motor::brake()
{
   digitalWrite(In1, LOW);
   digitalWrite(In2, LOW);
}

void Motor::standby()
{
   digitalWrite(Standby, LOW);
}

Sketchbook>ESP32_RoboCar_TB6612_MPU6050_SR04_BLE


3D Printer

主機板套件

A4988

A4988 is a complete microstepping motor driver with built-in translator for easy operation.

How To Control a Stepper Motor with A4988 Driver and Arduino

/*     Simple Stepper Motor Control Example Code
 *      
 *  by Dejan Nedelkovski, www.HowToMechatronics.com
 *  
 */

// defines pins numbers
const int stepPin = 3; 
const int dirPin = 4; 
 
void setup() {
  // Sets the two pins as Outputs
  pinMode(stepPin,OUTPUT); 
  pinMode(dirPin,OUTPUT);
}

void loop() {
  digitalWrite(dirPin,HIGH); // Enables the motor to move in a particular direction
  // Makes 200 pulses for making one full cycle rotation
  for(int x = 0; x < 200; x++) {
    digitalWrite(stepPin,HIGH); 
    delayMicroseconds(500); 
    digitalWrite(stepPin,LOW); 
    delayMicroseconds(500); 
  }
  delay(1000); // One second delay
  
  digitalWrite(dirPin,LOW); //Changes the rotations direction
  // Makes 400 pulses for making two full cycle rotation
  for(int x = 0; x < 400; x++) {
    digitalWrite(stepPin,HIGH);
    delayMicroseconds(500);
    digitalWrite(stepPin,LOW);
    delayMicroseconds(500);
  }
  delay(1000);
}

Prusa i3 MK3S 組裝


Creality3D Ender-3 V2


Drone

新手DIY多軸飛行器——我的第一架450無人機 QAV250穿越機套件

F450空拍機套件

無人機電機

無人機的電機電調你都了解嗎

電機的種類及區別
對於多旋翼無人機來說,小型無人機通常使用有刷電機,比如空心杯電機;而軸距較大,通常大於200mm左右的無人機都使用無刷電機。
目前的無人機多以無刷電機 中的永磁同步電機爲主。
無刷電機:可連續工作20000小時 左右,常規的使用壽命7-10年。
有刷電機:可連續工作5000小時左右,常規的使用壽命2-3年。

電機的基本參數
最大電流(A),最大電壓(V),KV值。
KV值:指電機在單位電壓下的轉速。一般KV值越高,代表電機的轉速越快。
比方說:KV值爲1000,意思是:此電機在1V電壓下,每分鐘轉速爲1000轉。

電機的命名
四位數字,前兩位是定子的直徑,後兩位是定子的高度。如4108規格的電機,定子的直徑是41mm,高度是8mm。

如何選配電機
多旋翼的電機最大拉力總和不應低於總重的1.5倍,最好是兩倍朝上。

2212 950KV for 四軸F450/F350

4004 KV300 for 四足機器人


共軸雙槳


ESC (Electronic Speed Controller) 電調

Arduino Brushless Motor Control Tutorial | ESC | BLDC

PWM Signal = 50Hz (20ms)
RPM Min ~ Mid ~ Max = 1ms ~ 1.5ms ~ 2ms

Examples of ESC control

// Brushless Motor Control
#include <Servo.h>

Servo ESC;

int potValue;  // value from the analog pin
https://github.com/rkuo2000/MCU-course/tree/main/images
void setup() {
  // Attach the ESC on pin 9
  ESC.attach(9,1000,2000); // (pin, min pulse width, max pulse width in microseconds) 
}

void loop() {
  potValue = analogRead(A0);   // reads the value of the potentiometer (value between 0 and 1023)
  potValue = map(potValue, 0, 1023, 0, 180);   // scale it to use it with the servo library (value between 0 and 180)
  ESC.write(potValue);    // Send the signal to the ESC
}

Sensored ESC -Homemade

/*
 * Electronoobs sensored brushed motor electronic speed controller code
 * Connect the sensors to pins 8, 9 and 10
 * High gates to pins 7, 5 and 3
 * Low gates to 6, 4 and 2 
 * More on http://www.electronoobs.com/eng_circuitos_tut19.php
 */
 
int pot = A3;
int SensorA = 8;
int SensorB = 9;
int SensorC = 10;


int fase = 1;
int Delay=4000;
unsigned long previousMillis = 0; 
unsigned long currentMillis = 0;

void setup() {
  pinMode(pot,INPUT);
  pinMode(SensorA,INPUT);
  pinMode(SensorB,INPUT);
  pinMode(SensorC,INPUT);
   
  DDRD  |= B11111100;  // Sets D2, D3, D4, D5, D6 and D7 as OUTPUT 
  PORTD &= B00000011;  // D2-D7 LOW
  PCICR |= (1 << PCIE0);    //enable PCMSK0 scan                                                 
  PCMSK0 |= (1 << PCINT0);  //Set pin D8 trigger an interrupt on state change.   A
  PCMSK0 |= (1 << PCINT1);  //Set pin D9 trigger an interrupt on state change.   B
  PCMSK0 |= (1 < PCINT2);  //Set pin D10 trigger an interrupt on state change.      C     
  
  currentMillis = micros();
}


void loop() {
  currentMillis = micros();
  if(currentMillis - previousMillis >= Delay){
  
    previousMillis += Delay;   
    
    switch(fase){

      //Phase1 A-B
      	case 1:
        PORTD = B10010000;  //  Pin 7 and 4 to HIGH
        break;       
    
        //Phase2 C-B
        case 2:
        PORTD = B00011000;  //  Pin 3 and 4 to HIGH
        break;
    
        //Phase3 C-A
        case 3:
        PORTD = B01001000;  //  Pin 3 and 6 to HIGH
        break;
     
        //Phase4 B-A
        case 4:
        PORTD = B01100000;  //  Pin 5 and 6 to HIGH
        break;
    
        //Phase5 B-C
        case 5:
        PORTD = B00100100;  //  Pin 5 and 2 to HIGH
        break;
    
        //Phase6 A-C
        case 6:
        PORTD = B10000100;  //  Pin 7 and 2 to HIGH
        break;
    }//end of switch       
  }//Case of if millis  
  
  Delay=map(analogRead(pot),0,1024,1,4000); //we obtain the delay speed using the potentiometer                 
}//Void end

ISR(PCINT0_vect){  
  if(  (PINB & B00000101) && fase == 6  ){   
    fase = 1;    
  }

  if(  (PINB & B00000100) && fase == 1 ){   
    fase = 2;    
  }

  if(  (PINB & B00000110) && fase == 2 ){   
    fase = 3;    
  }

  if(  (PINB & B00000010) && fase == 3 ){   
    fase = 4;    
  }

  if(  (PINB & B00000011) && fase == 4 ){   
    fase = 5;    
  }

  if(  (PINB & B00000001) && fase == 5 ){   
    fase = 6;    
  }
}



This site was last updated June 05, 2023.