Hello World,
I am trying to control a servo using the ESP32_Servo Library on my ESP32 S2 Board. However, the code doesn't work and I don't think the servo is getting any PWM signal.
Is there a fix for this?
Thanks!
Hello World,
I am trying to control a servo using the ESP32_Servo Library on my ESP32 S2 Board. However, the code doesn't work and I don't think the servo is getting any PWM signal.
Is there a fix for this?
Thanks!
Welcome to the forum
My first thought would be that the library is not compatible with the S2 board but I don't know if that is the case
For informed help, please read and follow the instructions in the "How to get the best out of the forum" post.
Post the code you are trying to get running.
Are you using GPIO_NUM 2,4,12-19,21-23,25-27,32-33 as one of the selected pins?
The ESP32Servo Library uses the ESP32's LEDC API. The default output pins of the ESP32's pins would be a subset of the ESP32's S model available GPIO pins. Thus, using GPIO_NUM 2,4,12-19,21-23,25-27,32-33 for the servo PWM should work.
Did you get servo sweep to work?
Thanks for the ideas.
Sorry for my poor description. I am using the ESP32 servo library to control an ESC for a brushless motor. I am following the tutorial and using the example code from (Control the Basic ESC with the Arduino Serial Monitor) but substituted the default servo library for ESP32_Servo library. (I am also using the same ESC/motor in tutorial).
I am using the Adafruit Feather S2 (https://www.adafruit.com/product/5000) and am using pins 13, 12, 11, and 10 to control 4 individual ESC/motors.
#include <ESP32_Servo.h>
byte servoPin = 10;
Servo servo;
void setup() {
Serial.begin(9600);
servo.attach(servoPin);
servo.writeMicroseconds(1500); // send "stop" signal to ESC.
delay(7000); // delay to allow the ESC to recognize the stopped signal
}
void loop() {
Serial.println("Enter PWM signal value 1100 to 1900, 1500 to stop");
while (Serial.available() == 0);
int val = Serial.parseInt();
if(val < 1100 || val > 1900)
{
Serial.println("not valid");
}
else
{
servo.writeMicroseconds(val); // Send signal to ESC.
}
}
The code successfully uploads and the ESCs chirp 3 times, but when I open the serial monitor and enter "1400" or "1700" nothing happens.. Everything is wired properly and I'm pretty sure it's a problem with the library and no PWM signal to ESCs.
-Tim
Did the ESP32 servo library example work for you?
int val = Serial.parseInt();
Serial.println( val): //what does this show?
I'm not sure I understand.
The code successfully complies and uploads but the motor doesn't turn on when I pass a number between 1100 and 1900.
Serial.println( val): //what does this show?
This just shows the number I entered. For example, "1400" or "1700"
Many ESC's require an arming procedure so the motors don't just spin wildly when power comes on. Check the ESC documentation to see what you have to feed it. Usually it's something like maximum throttle for a short period followed by minimum throttle.
That's a good idea. However, I have reason to believe that problem is related to using a Servo library for ESP32 only... When I swapped the Adafruit ESP32 board I am using for an Unexpected Maker Tiny Pico normal ESP32, I was able to get one motor working with the same code. But for my project, I must use the Feather ESP32 S2 because I have a custom PCB that it is mounted to via headers. GitHub - madhephaestus/ESP32Servo: Arduino-compatible servo library for the ESP32
Because I am using an ESP32 S2, I think I need to use this library instead? GitHub - khoih-prog/ESP32_S2_ISR_Servo: This library enables you to use 1 Hardware Timer on an ESP32_S2-based board to control up to 16 or more servo motors. Tested OK with ESP32 core v2.0.5
The only problem is that this new servo library for ESP32 S2 does not have the function "writeMicroseconds" which is used in my ESC/motor code above. Any solutions?
-Tim
It's better to use ESP32_ISR_Servo library, which supports either ESP32, ESP32_S2, ESP32_S3 or ESP32_C3.
Instead of using writeMicroseconds() function, you have to use setPosition() with position in degrees
bool setPosition(const uint8_t& servoIndex, const uint16_t& position);
Try ESP32_ISR_MultiServos example on both ESP32 and ESP32_S2 to get used to the library.
Glad to know this library works with ESP32-S2.
My one question is how do I use the setPosition function in order to change the speed of the motor? My starter code used servo.writeMicroseconds() between a value of 1100-1900 (1500 being off). How can I adapt this to use setPosition and this new library?
Thanks!
I just tried running the example code with my board selected as "Adafruit Feather ESP32-S2" and I got this error message
Arduino\libraries\ESP32_S2_ISR_Servo\src/ESP32_S2_FastTimerInterrupt.h:37:4: error: #error This code is intended to run on the ESP32_S2 platform! Please check your Tools->Board setting.
#error This code is intended to run on the ESP32_S2 platform! Please check your Tools->Board setting.
The obsolete ESP32_S2_ISR_Servo library was written for ESP32 core 1.0.6-, so don't use it.
For core v2.0.0+, you have to use either ESP32_ISR_Servo or better ESP32_New_ISR_Servo library.
You're not changing the speed, but position of the servo.
For SG90 servo motor,
1500 uS => 90 degree
544 uS => 0
2400 us => 180
For other servo motors, you have to try and match PULSE_WIDTH to POSITION in degrees, then use in your application.
I use the ESP32's MCPWM API for PWM.
/*
Project, use solar cells to generate power
2/2/2020
*/
// https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/esp_timer.html
///esp_timer_get_time(void) //gets time in uSeconds like Arduino Micros. see above link
//////// http://www.iotsharing.com/2017/09/how-to-use-arduino-esp32-can-interface.html
#include "sdkconfig.h"
#include "esp_system.h" //This inclusion configures the peripherals in the ESP system.
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
// #include "esp32/ulp.h"
// #include "driver/rtc_io.h"
#include <SimpleKalmanFilter.h>
#include <driver/adc.h>
#include "esp_sleep.h"
#include "driver/mcpwm.h"
const int TaskCore1 = 1;
const int TaskCore0 = 0;
const int TaskStack20K = 20000;
const int Priority3 = 3;
const int Priority4 = 4;
const int SerialDataBits = 115200;
volatile bool EnableTracking = true;
////
//
////
void setup()
{
Serial.begin( 115200 );
// https://dl.espressif.com/doc/esp-idf/latest/api-reference/peripherals/adc.html
// set up A:D channels
log_i( "Setup A:D" );
adc_power_on( );
vTaskDelay( 1 );
adc1_config_width(ADC_WIDTH_12Bit);
// ADC1 channel 0 is GPIO36 (ESP32), GPIO1 (ESP32-S2)
adc1_config_channel_atten(ADC1_CHANNEL_0 , ADC_ATTEN_DB_11); // azimuth 0
// // ADC1_CHANNEL_3 ADC1 channel 3 is GPIO39 (ESP32)
adc1_config_channel_atten(ADC1_CHANNEL_3, ADC_ATTEN_DB_11); // azimuth 1
// // ADC1 channel 5 is GPIO33
adc1_config_channel_atten(ADC1_CHANNEL_5, ADC_ATTEN_DB_11); // altitude 1
// // ADC1 channel 6 is GPIO34
adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11); // altitude 0
// // adc for light dark detection, ADC1 channel 7 is GPIO35 (ESP32)
adc1_config_channel_atten(ADC1_CHANNEL_7, ADC_ATTEN_DB_11);
// adc for solar cell voltage detection, ADC1 channel 4 is GPIO33 (ESP32)
adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_DB_11);
//
log_i( "A:D setup complete" );
// control for relay to supply power to the servos
pinMode( 5, OUTPUT );
vTaskDelay(1);
log_i( "setup MCPWM" );
//
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1A, GPIO_NUM_4 ); // Azimuth
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_NUM_12 ); // Altitude servo
//
mcpwm_config_t pwm_config = {};
pwm_config.frequency = 50; //frequency = 50Hz, i.e. for every servo motor time period should be 20ms
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;
log_i( "MCPWM complete" );
//
//
mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config); //Configure PWM0A timer 0 with above settings
mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_1, &pwm_config); //Configure PWM0A timer 1 with above settings
// ////
fMoveAltitude( 1525 );
fMoveAzimuth( 1500 );
////
// xTaskCreatePinnedToCore( TrackSun, "TrackSun", TaskStack20K, NULL, Priority3, NULL, TaskCore1 ); // assigned to core
// xTaskCreatePinnedToCore( fDaylight, "fDaylight", TaskStack20K, NULL, Priority3, NULL, TaskCore0 ); // assigned to core
//// // esp_sleep_enable_timer_wakeup( (60000000 * 2) ); // set timer to wake up once a minute 60000000uS * 2
}
//
void fDaylight( void * pvParameters )
{
int lightLevel = 0;
log_i( "Start up fDaylight" );
while (1)
{
// sufficent voltage at the solar cells to do the thing?
log_i( "Solar Cell: %d", adc1_get_raw(ADC1_CHANNEL_4) );
if (adc1_get_raw(ADC1_CHANNEL_4) > 1600 )
{
lightLevel = adc1_get_raw(ADC1_CHANNEL_7);
if ( adc1_get_raw(ADC1_CHANNEL_7) >= 300 )
{
log_i( "Light Level: %d", lightLevel );
EnableTracking = true;
digitalWrite( 5, LOW ); // power to relay module/servos
vTaskDelay( 1 );
} else {
// if light level to low park boom
fMoveAltitude( 1475 );
fMoveAzimuth( 1900 );
// put esp32 into deep sleep
digitalWrite( 5, HIGH ); // deenergize servo motors
EnableTracking = false;
esp_sleep_enable_timer_wakeup( (60000000 * 5) ); // set timer to wake up once a minute 60000000uS
esp_deep_sleep_start();
}
} else {
digitalWrite( 5, HIGH ); // deenergize servo motors
EnableTracking = false;
esp_sleep_enable_timer_wakeup( (60000000 * 5) ); // set timer to wake up once a minute 60000000uS
esp_deep_sleep_start();
}
vTaskDelay( 1000 );
} // while(1)
} // void fDaylight( void * pvParameters )
////
/**
@brief Use this function to calcute pulse width for per degree rotation
@param degree_of_rotation the angle in degree to which servo has to rotate
@return
- calculated pulse width
*/
//static uint32_t servo_per_degree_init(uint32_t degree_of_rotation)
//{
// const int SERVO_MIN_PULSEWIDTH = 500; //Minimum pulse width in microsecond
//const int SERVO_MAX_PULSEWIDTH 2500 //Maximum pulse width in microsecond
//const int SERVO_MAX_DEGREE 180 //Maximum angle in degree upto which servo can rotate
// uint32_t cal_pulsewidth = 0;
// cal_pulsewidth = (SERVO_MIN_PULSEWIDTH + (((SERVO_MAX_PULSEWIDTH - SERVO_MIN_PULSEWIDTH) * (degree_of_rotation)) / (SERVO_MAX_DEGREE)));
// return cal_pulsewidth;
//}
////
void mcpwm_gpio_initialize(void)
{
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1A, GPIO_NUM_4 );
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_NUM_12 );
}
////
// void TrackSun( int Altitude, int Azimuth )
void TrackSun( void * pvParameters )
{
log_i( "Startup TrackSun" );
int64_t EndTime = esp_timer_get_time();
int64_t StartTime = esp_timer_get_time(); //gets time in uSeconds like Arduino Micros
int Altitude = 1500;
int Azimuth = 900;
int maxAltitudeRange = 2144;
int minAltitudeRange = 900;
int maxAzimuthRange = 2144;
int minAzimuthRange = 900;
float AltitudeThreashold = 10.0f;
float AzimuthThreashold = 10.0f;
float kalmanThreshold = 80.0f;
SimpleKalmanFilter kfAltitude0( AltitudeThreshold, kalmanThreshold, .01 ); // kalman filter Altitude 0
SimpleKalmanFilter kfAltitude1( AltitudeThreshold, kalmanThreshold, .01 ); // kalman filter Altitude 1
SimpleKalmanFilter kfAzimuth0( AzimuthThreshold, kalmanThreshold, .01 ); // kalman filter Azimuth 0
SimpleKalmanFilter kfAzimuth1( AzimuthThreshold, kalmanThreshold, .01 ); // kalman filter Azimuth 1
float filteredAltitude_0 = 0.0f;
float filteredAltitude_1 = 0.0f;
float filteredAzimuth_0 = 0.0f;
float filteredAzimuth_1 = 0.0f;
uint64_t AzimuthEndTime = esp_timer_get_time();
uint64_t AzimuthStartTime = esp_timer_get_time(); //gets time in uSeconds like Arduino Micros,but rolls over after 207 years
uint64_t AltitudeEndTime = esp_timer_get_time();
uint64_t AltitudeStartTime = esp_timer_get_time(); //gets time in uSeconds like Arduino Micros,
int StartupCount = 0;
int TorqueAmmount = 5;
while (1)
{
if ( EnableTracking )
{
//Altitude
AltitudeEndTime = esp_timer_get_time() - AltitudeStartTime; // produce elapsed time for the simpleKalmanFilter
kfAltitude0.setProcessNoise( float(AltitudeEndTime) / 1000000.0f );
kfAltitude1.setProcessNoise( float(AltitudeEndTime) / 1000000.0f );
// Serial.println( AltitudeEndTime,6 );
filteredAltitude_0 = kfAltitude0.updateEstimate( (float)adc1_get_raw(ADC1_CHANNEL_6) );
filteredAltitude_1 = kfAltitude1.updateEstimate( (float)adc1_get_raw(ADC1_CHANNEL_5) );
// Serial.print( filteredAltitude_0 );
// Serial.print( ", " );
// Serial.print( filteredAltitude_1 );
// Serial.print( ", " );
// Serial.println();
if ( (filteredAltitude_0 > filteredAltitude_1) && (abs(filteredAltitude_0 - filteredAltitude_1) > AltitudeThreshold))
{
Altitude -= TorqueAmmount;
// log_i( "> Alt %d", Altitude );
if ( Altitude < minAltitudeRange )
{
Altitude = 900;
}
fMoveAltitude( Altitude );
AltitudeStartTime = esp_timer_get_time();
}
if ( (filteredAltitude_0 < filteredAltitude_1) && (abs(filteredAltitude_0 - filteredAltitude_1) > AltitudeThreshold) )
{
Altitude += TorqueAmmount;
if ( Altitude >= maxAltitudeRange )
{
Altitude = 900;
}
fMoveAltitude( Altitude );
// log_i( "< Alt %d", Altitude );
AltitudeStartTime = esp_timer_get_time();
}
//// AZIMUTH
AzimuthEndTime = esp_timer_get_time() - AzimuthStartTime; // produce elasped time for the simpleKalmanFilter
// Serial.print( (float)adc1_get_raw(ADC1_CHANNEL_3) );
// Serial.print( "," );
// Serial.print( (float)adc1_get_raw(ADC1_CHANNEL_0) );
// Serial.print( "," );
kfAzimuth0.setProcessNoise( float(AzimuthEndTime) / 1000000.0f ); //convert time of process to uS, update SimpleKalmanFilter q
kfAzimuth1.setProcessNoise( float(AzimuthEndTime) / 1000000.0f ); //convert time of process to uS, update SimpleKalmanFilter q
filteredAzimuth_0 = kfAzimuth0.updateEstimate( (float)adc1_get_raw(ADC1_CHANNEL_3) );
filteredAzimuth_1 = kfAzimuth1.updateEstimate( (float)adc1_get_raw(ADC1_CHANNEL_0) );
// Serial.print( filteredAzimuth_0 );
// Serial.print( ", " );
// Serial.print( filteredAzimuth_1 );
// Serial.println();
if ( (filteredAzimuth_0 > filteredAzimuth_1) && (abs(filteredAzimuth_0 - filteredAzimuth_1)) > AzimuthThreashold )
{
Azimuth += TorqueAmmount;
if ( Azimuth >= maxAzimuthRange )
{
Azimuth = 900;
}
// log_i( "> Az %d", Azimuth );
fMoveAzimuth( Azimuth );
AzimuthStartTime = esp_timer_get_time();
}
// //
if ( (filteredAzimuth_0 < filteredAzimuth_1) && (abs(filteredAzimuth_1 - filteredAzimuth_0)) > AzimuthThreashold )
{
// log_i( "< Az %d", Azimuth );
Azimuth -= TorqueAmmount; // make new position to torque to
if ( (Azimuth >= maxAzimuthRange) || (Azimuth <= minAzimuthRange) )
{
Azimuth = 900;
}
fMoveAzimuth( Azimuth );
AzimuthStartTime = esp_timer_get_time();
} //azmiuth end
if ( StartupCount < 500 )
{
++StartupCount;
} else {
TorqueAmmount = 1;
// esp_sleep_enable_timer_wakeup( (60000000 / 30) ); // set timer to wake up
// esp_sleep_enable_timer_wakeup( (60000000 / 20) ); // set timer to wake up
// esp_light_sleep_start();
}
}
vTaskDelay( 65 );
} // while(1)
} //void TrackSun()
////
void fMoveAltitude( int MoveTo )
{
mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, MoveTo);
vTaskDelay( 12 );
}
//////
void fMoveAzimuth( int MoveTo )
{
mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_1, MCPWM_OPR_A, MoveTo);
vTaskDelay( 12 );
}
////
void loop() {}
Is an example of using the mcwpm API under the Arduino IDE.
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/mcpwm.html
The new library worked! but the brushless motors spun like crazy fast and I had to cut the power.
I want to go back to the original code where I can change the speed of a motor using an input from the Serial Monitor as shown in the code here:
#include <Servo.h>
byte servoPin = 9;
Servo servo;
void setup() {
servo.attach(servoPin);
servo.writeMicroseconds(1500); // send "stop" signal to ESC.
delay(7000); // delay to allow the ESC to recognize the stopped signal
}
void loop() {
int signal = 1700; // Set signal value, which should be between 1100 and 1900
servo.writeMicroseconds(signal); // Send signal to ESC.
}
I tried to modify/cut down the "MultipleServos" example so that it is easier to use. I got down to this:
#if !defined(ESP32)
#error This code is intended to run on the ESP32 platform! Please check your Tools->Board setting.
#endif
#define TIMER_INTERRUPT_DEBUG 0
#define ISR_SERVO_DEBUG 1
// For ESP32_C3, select ESP32 timer number (0-1)
// For ESP32 and ESP32_S2, select ESP32 timer number (0-3)
#if defined( ARDUINO_ESP32C3_DEV )
#define USE_ESP32_TIMER_NO 1
#else
#define USE_ESP32_TIMER_NO 3
#endif
// To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error
#include "ESP32_New_ISR_Servo.h"
// Published values for SG90 servos; adjust if needed
#define MIN_MICROS 1100 //544
#define MAX_MICROS 1900
#define NUM_SERVOS 4
typedef struct
{
int servoIndex;
uint8_t servoPin;
} ISR_servo_t;
ISR_servo_t ISR_servo[NUM_SERVOS] =
{
{ -1, 13 }, { -1, 12 }, { -1, 11 }, { -1, 10 }
};
void setup()
{
Serial.begin(115200);
while (!Serial);
delay(200);
Serial.print(F("\nStarting MultipleServos on ")); Serial.println(ARDUINO_BOARD);
Serial.println(ESP32_NEW_ISR_SERVO_VERSION);
//Select ESP32 timer USE_ESP32_TIMER_NO
ESP32_ISR_Servos.useTimer(USE_ESP32_TIMER_NO);
for (int index = 0; index < NUM_SERVOS; index++)
{
ISR_servo[index].servoIndex = ESP32_ISR_Servos.setupServo(ISR_servo[index].servoPin, MIN_MICROS, MAX_MICROS);
}
ESP32_ISR_Servos.setPosition(ISR_servo[1].servoIndex, 1300);
delay(3000);
ESP32_ISR_Servos.setPosition(ISR_servo[1].servoIndex, 1500);
}
However, I'm still not sure how to use the .setPosition function? I'm more of a beginner programmer and did not really understand your explanation.
I tried to do this: which would turn on one motor for 3 seconds at whatever speed 1300 is and then turn off. But it doesn't work the way it would as in the Blue Robotics tutorial using writeMicroseconds...
ESP32_ISR_Servos.setPosition(ISR_servo[1].servoIndex, 1300);
delay(3000);
ESP32_ISR_Servos.setPosition(ISR_servo[1].servoIndex, 1500);
Could you offer any insight or advice on how to accomplish this goal? I'm happy that this library works and want to make it as simple as possible to use for my project.
Once again, thank you so much!
Sorry that I didn't realize you're using brushless motors, which I'm not sure if supported by the library or not.
I suggest now you can test use function setPulseWidth(), which is closest match to writeMicroseconds() function
For example
ESP32_ISR_Servos.setPulseWidth(ISR_servo[1].servoIndex, 1300);
delay(3000);
ESP32_ISR_Servos.setPulseWidth(ISR_servo[1].servoIndex, 1500);
I just replaced my lines with your example but am now getting this error. I don't know what it means?
MultipleServos_new_lib_that_shud_work:136:58: error: cannot bind non-const lvalue reference of type 'uint32_t&' {aka 'unsigned int&'} to an rvalue of type 'uint32_t' {aka 'unsigned int'}
ESP32_ISR_Servos.setPulseWidth(ISR_servo[0].servoIndex, 1500);
It's underlining the "1500" in compiler window.
Sorry, my bad, please change as follows
uint32_t pulse_width = 1300;
ESP32_ISR_Servos.setPulseWidth(ISR_servo[1].servoIndex, pulse_width);
delay(3000);
pulse_width = 1500;
ESP32_ISR_Servos.setPulseWidth(ISR_servo[1].servoIndex, pulse_width);
If you still have more issue, I suggest you open an issue in the library, then post a link here. I'm just afraid if this becomes too long, messy and inappropriate for this Forum
It works! Thank you for your help!!!!!