Page 1 of 2
Driving a brushless motor with an ESC from a ESP32
Posted: Sat Apr 17, 2021 9:50 pm
by JacktheRipper
I'm trying to use an ESP32 Dev Kit V1 to command a hobby 2200KV brushless motor connected to a hobby 30A ESC. This is easy to do with an Arduino, like a Nano, by using a built-in library (ESC.h). I don't see an equivalent library for the ESP32. Brushless motors and the ESCs that drive them, are very complex beasts. This complexity is handled within the ESC.h library which, in turn, uses the Servo.h library, to arm and then set speeds for the motor. It's much more complicated than just sending a PWM signal to the device. In ESC.h, there's a specific arming command sequence that must be sent first, then specific calls to set the speed of the motor. This tailors the resulting PWM signal in a specific, very complicated way. Again, I could find no equivalent library for the ESP32. Has anyone been able to do this?
Re: Driving a brushless motor with an ESC from a ESP32 (Solved)
Posted: Tue Apr 20, 2021 12:46 pm
by JacktheRipper
Well, I got this working with the following steps, using the Arduino IDE and it's library manager, and running a modified version of the Knob Example from the ESC library on a generic ESP32 DevKit V1:
(1) In the Library Manager, search for "ESP32Servo" and install version 0.9.0 by Kevin Harrington. Note that, in any program that you write for using servos with an ESP32, you must have #include <ESP32Servo.h> rather than #include <Servo.h>. These libraries use timers that are different for ESP32 versus the AVR boards, like the Arduino and its variants.
(2) In the Library Manager, search for "RC_ESC" and install version 1.1.0 by Eric Nantel. This library does not claim to work with ESP32s, and, indeed, it does not. It has a buried reference to Servo.h which must be changed to ESP32Servo.h. To do this, go to your libraries folder where you store your programs, drill down to RC_ESC>>src>>ESC.h, and edit it (I used NotePad++). Change line 16 to #include <ESP32Servo.h> and save the changes. Note that this library will still throw a warning that it's AVR-only when compiled for an ESP32 board, but it's only a warning, and compilation will finish OK. Note that if you let the Library Manager update this library, you'll have to repeat this step!
(3) Hobby ESCs and brushless motors are fussy beasts. The Knob Example that comes with the RC_ESC library only compiles with an AVR board. At first, using a NANO, the program only made my motor chirp at me, but not run. These motors like to be started up very slowly after arming, so I put in a loop to do just that. I modified the example until I arrived at a NANO sketch that reliably started the motor and made it run though a good range of speeds when I turned the pot full travel. The code listed below is a modification of the NANO code, and it works with an ESP32 DevKit V1 and my particular hardware combination. You may have to fiddle with arming parameters, commanded speeds, etc., etc., etc. to get working.
/* Note: the following code is a modification of the Knob Example that
* comes with the ESC library, so that it works with an ESP32 DevKit V1
* and my particular ESC (generic 30A) and brushless motor (generic 2200KV)
*/
#include <ESP32Servo.h> // ESP32Servo library installed by Library Manager
#include "ESC.h" // RC_ESP library installed by Library Manager
#define ESC_PIN (33) // connected to ESC control wire
#define LED_BUILTIN (2) // not defaulted properly for ESP32s/you must define it
#define POT_PIN (34) // Analog pin used to connect the potentiometer center pin
// Note: the following speeds may need to be modified for your particular hardware.
#define MIN_SPEED 1040 // speed just slow enough to turn motor off
#define MAX_SPEED 1240 // speed where my motor drew 3.6 amps at 12v.
ESC myESC (ESC_PIN, 1000, 2000, 500); // ESC_Name (PIN, Minimum Value, Maximum Value, Arm Value)
long int val; // variable to read the value from the analog pin
void setup() {
Serial.begin(9600);
delay(1000);
pinMode(POT_PIN, INPUT);
pinMode(ESC_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH); // set led to on to indicate arming
myESC.arm(); // Send the Arm command to ESC
delay(5000); // Wait a while
digitalWrite(LED_PIN, LOW); // led off to indicate arming completed
// the following loop turns on the motor slowly, so get ready
for (int i=0; i<350; i++){ // run speed from 840 to 1190
myESC.speed(MIN_SPEED-200+i); // motor starts up about half way through loop
delay(10);
}
} // speed will now jump to pot setting
void loop() {
val = analogRead(POT_PIN); // read the pot (value between 0 and 4095 for ESP32 12 bit A to D)
Serial.println(val);
val = map(val, 0, 4095, MIN_SPEED, MAX_SPEED); // scale pot reading to valid speed range
myESC.speed(val); // sets the ESC speed
delay(10); // Wait for a while
}
Re: Driving a brushless motor with an ESC from a ESP32 (Corrected)
Posted: Tue Apr 20, 2021 1:17 pm
by JacktheRipper
Oops! Errors in the code I previously posted. Correct code is....
/* Note: the following code is a modification of the Knob Example that
* comes with the ESC library, so that it works with an ESP32 DevKit V1
* and my particular ESC (generic 30A) and brushless motor (generic 2200KV)
*/
#include <ESP32Servo.h> // ESP32Servo library installed by Library Manager
#include "ESC.h" // RC_ESP library installed by Library Manager
#define ESC_PIN (33) // connected to ESC control wire
#define LED_BUILTIN (2) // not defaulted properly for ESP32s/you must define it
#define POT_PIN (34) // Analog pin used to connect the potentiometer center pin
// Note: the following speeds may need to be modified for your particular hardware.
#define MIN_SPEED 1040 // speed just slow enough to turn motor off
#define MAX_SPEED 1240 // speed where my motor drew 3.6 amps at 12v.
ESC myESC (ESC_PIN, 1000, 2000, 500); // ESC_Name (PIN, Minimum Value, Maximum Value, Arm Value)
long int val; // variable to read the value from the analog pin
void setup() {
Serial.begin(9600);
delay(1000);
pinMode(POT_PIN, INPUT);
pinMode(ESC_PIN, OUTPUT);
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH); // set led to on to indicate arming
myESC.arm(); // Send the Arm command to ESC
delay(5000); // Wait a while
digitalWrite(LED_BUILTIN, LOW); // led off to indicate arming completed
// the following loop turns on the motor slowly, so get ready
for (int i=0; i<350; i++){ // run speed from 840 to 1190
myESC.speed(MIN_SPEED-200+i); // motor starts up about half way through loop
delay(10);
}
} // speed will now jump to pot setting
void loop() {
val = analogRead(POT_PIN); // read the pot (value between 0 and 4095 for ESP32 12 bit A to D)
Serial.println(val);
val = map(val, 0, 4095, MIN_SPEED, MAX_SPEED); // scale pot reading to valid speed range
myESC.speed(val); // sets the ESC speed
delay(10); // Wait for a while
}
Re: Driving a brushless motor with an ESC from a ESP32
Posted: Wed Apr 21, 2021 11:08 am
by markxr
More advanced brushless controllers also support the Dshot (or other) digital protocol. This allows exact control, avoids the need to run an initial calibration, and provides access to more features.
I've implemented this on esp32 using the RMT peripheral, example here:
https://github.com/MarkR42/robotbits/bl ... n/motors.c
Re: Driving a brushless motor with an ESC from a ESP32
Posted: Thu Apr 22, 2021 11:34 am
by JacktheRipper
I've implemented this on esp32 using the RMT peripheral, example here
markxr... Looks like a fantastic way to go. I found a DSHOT600-compliant ESC on Amazon for $9 [url=
https://www.amazon.com/FlashHobby-BLhel ... 15&sr=8-14 Here[/url] , and I'll have my hands on it tomorrow. My goal here is learning, and the first thing I want to do is a Knob Example (pot + ESC + KV2200 motor). I'll start with your code and try to distill the minimum necessary part of it towards that capability. I'll build on it from there. Thanks very much for pointing me towards this...Jack
Re: Driving a brushless motor with an ESC from a ESP32
Posted: Sat Apr 24, 2021 10:36 am
by JacktheRipper
I got the ESC I mentioned above, and wrote the following Amazon review. Works much better than the unit I had:
"I bought this unit for its DShot 600 digital control capability, but my first evaluation was done with standard PWM control signals sent from an Arduino Nano (using the RC_ESC.h and Servo.h libraries). I'm able to achieve a wide speed range (850 to 8650 rpm) using a 2200 KV hobby grade motor, also bought on Amazon. Great torque even at the lowest speed. I first ran the RC_Calibration example program from the RC_ESC.h library, and the unit responded with many chirps of various tones, so something good was happening, who knows what. I then set up the RC_Knob program with the speed range mapped from 1000 to 1400 over the pot's full travel. The motor turns on reliably at about 1050 with very low rpms, and runs up to a measured 8650 rpm at 1400. The ESC drew 2.3 amps at 12v for this speed, and that seems to be a redline current for this voltage. The MOSFET drivers got rather hot at max speed (127 Deg F measured), so I fashioned a small copper heat sink spanning the top of the three chips. I measured a more reasonable 116 Deg F. So, all in all, excellent performance when run with standard PWM. But note: soldering five 22 gauge power wires, and two 26 gauge control wires onto something that measures only 15 X 11 mm is tough (a penny is 22 mm diameter, and completely covers it). So skip the Starbucks that day.
Next step is to see what I can do using DShot 600, but that's going to take some learning on my part."
I tried to run this ESC with an ESP32 generating the PWM control signal, but it didn't respond at all. May be it needs 5v, not 3.3v, but maybe I just had something wrong in the setup. I'll continue to work on this...
Re: Driving a brushless motor with an ESC from a ESP32
Posted: Sat Apr 24, 2021 1:25 pm
by JacktheRipper
Update: I have now verified that this ESC works very well with an ESP32 driving it using PWM. In fact, I would say the low speed performance is a shade better than what the Nano did.
Re: Driving a brushless motor with an ESC from a ESP32
Posted: Tue Apr 27, 2021 12:50 pm
by JacktheRipper
I've implemented this on esp32 using the RMT peripheral
markxr... I borrowed heavily from your provided code and the HW timer tutorial, and now have Dshot 600 working beautifully, but only in forward speed. Still trying to dig out how to reverse direction. Seems to be more than just commanding speeds above 1048. Here's my current sketch...
Code: Select all
#include <Arduino.h>
#include "esp32-hal.h" // lots in this, including HW timer definitions
#define POT_PIN 34 // potentiometer pin
#define ESC_PIN 5 // ESC control pin
#define FWD_REV_PIN 21 // HIGH is forward, LOW is reverse
#define MOTOR_POLES 14 // seems to be correct for my 2200KV motor
#define TMR_CHAN 0 // HW timer channel
#define BOARD_MHZ 80 // used as prescale in timerBegin call
#define COUNT_UP true // HW timer count direction
#define TX_NO_RX true // only send data to ESC for now
#define TELEM_ON true // but we'll be commanding off for now
// DShot 600 has frame rate of 600 kHz, which is 133 ticks of a 80MHz clock
#define SHORT_TICKS 44 // results in 555 nsec
#define LONG_TICKS 89 // results in 1111 nsec; total is 133 ticks of 80MHz clock
rmt_data_t dshotPacket[16]; // structure defined in esp32_hal_h ?
rmt_obj_t* rmt_send = NULL;
//
//uint8_t receivedBytes = 0;
//volatile bool requestTelemetry = false;
//bool printTelemetry = true;
volatile uint16_t dshotUserInputValue = 0; // holds the pot reading for speed to be sent in packet
// Note: The following lines marked with *nn were taken from HW timer tutorial at
// https://techtutorialsx.com/2017/10/07/esp32-arduino-timer-interrupts/ (blank lines not numbered
volatile int interruptCounter = 0; // *1 counts the interrupts from timer. volitile to make it persistent
int totalInterruptCounter; // *2
hw_timer_t * timer = NULL; // *4
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; // *5 used to maintain sync between interupt and loop code
void IRAM_ATTR onTimer() { // *7 ISR (interrupt service routine) for HW timer
portENTER_CRITICAL_ISR(&timerMux); // *8
interruptCounter++; // *9
dshotOutput(dshotUserInputValue, false); // send out the 16 bit digital command to ESC
portEXIT_CRITICAL_ISR(&timerMux); // *10
} // *12
void setup() { // *14
Serial.begin(115200); // *16
Serial.print("init starting\n");
pinMode(POT_PIN, INPUT);
pinMode(FWD_REV_PIN, INPUT_PULLUP);
pinMode(ESC_PIN, OUTPUT);
rmt_send = rmtInit(ESC_PIN, TX_NO_RX, RMT_MEM_64);
if (rmt_send == NULL) {
Serial.println("init sender failed\n");
}
Serial.print("init sender success\n");
float realTick = rmtSetTick(rmt_send, 12.5); // 12.5 nsec sample rate
Serial.printf("rmt_send tick set to: %fns\n", realTick);
while (millis() < 3500) {
dshotOutput(0, !TELEM_ON);
delay(1);
}
timer = timerBegin(TMR_CHAN, BOARD_MHZ, COUNT_UP); // *18 returns pointer to timer structure
timerAttachInterrupt(timer, &onTimer, true); // *19 attatch timer interupt to onTimer routine defined above: edge type=true
timerAlarmWrite(timer, 1000, true); // *20 1000 and prescale of 80 is a msec. reload the timeer=true
timerAlarmEnable(timer); // *21 timer is now set up and attached correctly
} // *23
void loop() {
// printTelemetry = false ;
uint16_t dUR = analogRead(POT_PIN); // read the pot pin 0 - 4095
dshotUserInputValue = map(dUR, 0, 4095, 48, 1047); // 1 to 47 commands are reserved
bool goforward = digitalRead(FWD_REV_PIN);
if (!goforward) {
dshotUserInputValue = map(dUR, 0, 4095, 1049, 2097);
}
if (dUR < 2) dshotUserInputValue = 0;
if (interruptCounter > 0) { // *27 true if it's time to enter interrupt code
portENTER_CRITICAL(&timerMux); // *29
interruptCounter--; // *30
portEXIT_CRITICAL(&timerMux); // *31
totalInterruptCounter++; // *33
Serial.printf("forward:%d, pot: %i totalinterruptcounter: %i \n", goforward, dshotUserInputValue, totalInterruptCounter); // *35,36
}
}
void dshotOutput(uint16_t value, bool telemetry) { // creates packet to send
uint16_t packet;
if (telemetry) {
packet = (value << 1) | 1;
} else {
packet = (value << 1) | 0;
}
int csum = 0; // holds checksum part of packet?
int csum_data = packet;
for (int i = 0; i < 3; i++) {
csum ^= csum_data;
csum_data >>= 4;
}
csum &= 0xf;
packet = (packet << 4) | csum;
for (int i = 0; i < 16; i++) {
dshotPacket[i].level0 = 1;
dshotPacket[i].level1 = 0;
if (packet & 0x8000) { // true for bit set
dshotPacket[i].duration0 = LONG_TICKS;
dshotPacket[i].duration1 = SHORT_TICKS;
} else { // true for bit clear
dshotPacket[i].duration0 = SHORT_TICKS;
dshotPacket[i].duration1 = LONG_TICKS;
}
packet <<= 1;
}
rmtWrite(rmt_send, dshotPacket, 16);
return;
}
Re: Driving a brushless motor with an ESC from a ESP32
Posted: Wed Apr 28, 2021 8:11 am
by markxr
Usually brushless ESCs only drive in one direction. You will need to enable bidirectional mode (aka "3d mode") on the ESC.
This can usually be done by a firmware setting on the ESC, but it's also possible on some models using dshot commands. I suggest you read this post:
https://www.swallenhardware.io/battlebo ... dshot-escs
Essentially, the command must be sent many times on startup, but also, you need to wait for any startup beeps or sequence to finish before sending commands (they will be ignored). So you'll need some delays in the startup code.
I did this in mine, it sends the dshot "enable 3d mode" command (10) - I think I'm sending the command 20 times at 2ms intervals which seems to work.
If the ESC has flashing lights then there may be commands to change the light colour or brightness which you can play with; this is useful to check dshot commands are working.
Also, ESCs will "disarm" if they receive no commands or pulses for a while - so you need to *keep* sending commands at regular intervals to every ESC even if you don't want it to do anything. A zero command ("zero throttle") is usually good to work as a "keep alive".
If the ESC disarms then everything might be reset to the default.
My startup sequence looks like:
* Send zero commands every 2ms for 3 seconds (waiting for startup, arms ESCs)
* Send "3d mode on" command 20x - several repeats of this with zeros
* Set LED colours
I do this in paralllel for all ESCs and then keep sending pulses continuously even if the robot isn't doing anything.
Also note that after a few minutes of zero commands my ESCs start beeping - this is the "finding lost drone in long grass" mode which is annoying but harmless.
Re: Driving a brushless motor with an ESC from a ESP32
Posted: Thu Apr 29, 2021 6:50 pm
by JacktheRipper
My startup sequence looks like:
* Send zero commands every 2ms for 3 seconds (waiting for startup, arms ESCs)
* Send "3d mode on" command 20x - several repeats of this with zeros
* Set LED colours
I've tried to do this with the following code at the bottom of my Setup function, but no joy on setting 3D mode:
Code: Select all
uint16_t cmd = 0; // Code that waits for chirps to finish?
Serial.print("Sending command "); Serial.println(cmd);
long int Tnow = millis();
while (millis() < Tnow + 3500) {
dshotOutput(cmd, !TELEM_ON);
delay(2);
}
Serial.println("Tone finished?");
cmd = 10; // Set to 3D mode
Serial.print("Sending command "); Serial.println(cmd);
Tnow = millis();
while (millis() < Tnow + 3000) {
dshotOutput(cmd, !TELEM_ON);
delay(2);
}
cmd = 12; // save changes command
Serial.print("Sending command "); Serial.println(cmd);
Tnow = millis();
while (millis() < Tnow + 3000) {
dshotOutput(cmd, !TELEM_ON);
delay(2);
}
Serial.println("Setup commands finished");
My ESC is labelled "35A BLHeli-32, DS1200", and I'm driving it with an ESP32 DevKit V1. The ESC does have a solder tab labelled "T", presuming for telemetry, but I haven't connected it to anything. Beyond the code above, I tried using the BLHeli-32 Configuration Suite; it finds the port OK, but it does not connect to the ESC.
So, to implement bi-direction operation I use two external relays to swap two of the lines to the motor. Clunky, but it works like a charm, with very smooth operation from 250 to 11,900 rpm in either direction. Amazing.
Hey, thanks for your guidance and code sample for this. I know I'm am outlier here, since I'm not a quad flier, just an Arduino hobby guy. I've done lots of things with steppers, but now I can consider doing projects with brushless motors too.