Program Arduino Uno to control RC ESC using external reverse switch to change PWM output

Hi All,
I have searched the Forum but haven't found anything exactly like my intended use case. I'd like to program an Arduino to control an RC ESC. The RC ESC has reverse. However I'd like to have a separate switch for forward and reverse. Just for clarification I am not talking about switching the polarity of the ESC's output. Instead, I want the Arduino's PWM output to be dependent on the position of the foward/reverse switch as follows:

Switch Input PWM Arduino Output PWM
FWD 1000 1500
FWD 2000 2000
REV 1000 1500
REV 2000 1000

I'd like to use an ebike throttle and I have a circuit board to convert that to an RC PWM output, if Arduino can read that as an input. Or I could connect the ebike throttle directly to the Arduino, if it can read 0-5 volts directly, in which case the Input PWM column above would change accordingly.

Once again, the switch would not be used to change the the wiring of the ESC output. Instead I want the Arduino to read the position of the switch and then output a PWM signal depending on the input from the throttle and the input from the position of the switch.

If working correctly, the setup would behave much like a children's ride on toy, with one throttle pedal and a separate switch that controls direction (FWD or REV).

Any and all help is appreciated.
Thanks,
Jeff

Where are you stuck ?

if (digitalRead(switchPin) == HIGH)
{
  ESC.write(180);
}
else
{  
  ESC.write(0);
}

Sorry, I'm a complete noob.

Thanks so much for the reply.

I understand the conditional logic part of the code you sent, but have a few questions.

Does the digitalRead() function read the position of the FWD/REV switch or the incoming PWM throttle signal? And what is the meaning of its value being set to "HIGH"?

Thanks,
Jeff

Maybe I should have expanded a bit. I want the throttle to be continuous from the zero point to the max point in each direction (either forward or reverse). So I'm guessing I'll need some kind of for loop?

The simple example I posted would change the direction of rotation of the motor attached to the ESC with no speed control so it would run at full speed. It was an example of simple direction control

It would be easy to add an analogRead() to read the throttle position and apply that to the ESC output depending on the position of the switch, but that would be dangerous

Suppose that the motor is running at full throttle forward and the user flips the forward/reverse switch. Should the motor immediately slam into reverse ? Probably not, so what should happen ?

Thanks again for your reply.

Great question.

Here I will distinguish between throttle position reverse and motor reverse.

The RC ESC has a brake that it normally goes into if you slam the throttle to full throttle position reverse from throttle position forward. Then the user has to go back to neutral point and after that it will go into motor reverse if the throttle is again pointed towards throttle position reverse. So there's already a little bit of a Fail-Safe built-in. However it would be nice if Arduino could detect a change in the position of the FWD/REV switch and then implement a 3-second pause, for example.

That's easy

So, what code have you written so far ?

None! :joy:
I'm a complete noob. Just trying to get started.

Something like:

for()
{
analogRead()

if (digitalRead(switchPin) == HIGH)
{
ESC.write();
}
else
{
ESC.write();
}
}

So for each iteration of the loop, it would check to see what the analog reading is from the throttle pin and then it would output PWM to the ESC differently depending on the position of the FWD/REV switch. I have no idea how to program the pause in if the switch changes.

I think you need to go back to basics

The Arduino loop() function will do the looping for you
You need to detect the change of switch position to invoke the delay
The value that you write to the ESC needs to change depending upon the switch position and what analogRead() returns

If you really are a complete "noob" then don't jump into this project. Start by looking at the examples in the IDE, understanding the structure of an Arduino sketch and building towards you project

  • read an input pin and print its state

  • read an input pin and print a message when it changes state

  • read in input pin and print a message when it becomes LOW

  • Try the Servo examples

  • read an analogue input and print its values

  • read an analogue input and move the servo based on the value read

  • investigate the use of the delay() function and consider alternatives

  • Stitch together what you have learned into a first attempt at you your project

  • debug tour project using Serial.print() statements

  • when it is working consider possible improvements/enhancements

Suppose I start with this:

#include <Servo.h>

byte servoPin = 9; // signal pin for the ESC.
byte potentiometerPin = A0; // analog input pin for the potentiometer (ebike throttle in my case-not sure what to change).
Servo servo;

void setup() {
servo.attach(servoPin);
servo.writeMicroseconds(1500); // send "stop" signal to ESC. Also necessary to arm the ESC.

delay(7000); // delay to allow the ESC to recognize the stopped signal.
}

void loop() {

int potVal = analogRead(potentiometerPin); // read input from potentiometer.

if (digitalRead(switchPin) == HIGH)
{

int pwmVal = map(potVal,0, 1023, 1500, 2000); // maps potentiometer values to PWM value.

servo.writeMicroseconds(pwmVal); // Send signal to ESC.
}
else
{

int pwmVal = map(potVal,0, 1023, 1500, 1000); // maps potentiometer values to PWM value.

servo.writeMicroseconds(pwmVal); // Send signal to ESC.
}

}

I think our last messages flew past each other.

Ok, thanks, I will do all of that.

Much of that looks OK. When posting code here please Auto Format it in the IDE to improve its layout and post it here using code tags to make it easier to read and copy. If you right click in the IDE there is an option top "Copy for forum" that adds the code tags to the copied code automatically ready for posting

Ok, terrific, will do. I didn't know about the "Copy to Forum" feature.

Thanks for all the help,
Jeff

Here is the formatted version. I'm not able to test it yet but will do so soon.

#include <Servo.h>

int ESCPin = 9; // signal pin for the ESC.
int switchPin = A1; // analog input pin for the FWD/REV switch.
int potPin = A0; // analog input pin for the potentiometer (ebike throttle in my case-not sure what to change).
Servo servo;

void setup() {
  servo.attach(ESCPin);
  servo.writeMicroseconds(1500); // send "stop" signal to ESC. Also necessary to arm the ESC.
  delay(2000); // delay to allow the ESC to recognize the stopped signal and arm.
}

void loop() {

  int potVal = analogRead(potPin); // read input from potentiometer.
  
  if (digitalRead(switchPin) == HIGH) {
  
    int pwmVal = map(potVal,0, 1023, 1500, 2000); // maps potentiometer values to PWM value.
    
    servo.writeMicroseconds(pwmVal); // Send signal to ESC.
  }
  else {
  
    int pwmVal = map(potVal,0, 1023, 1500, 1000); // maps potentiometer values to PWM value.
    
    servo.writeMicroseconds(pwmVal); // Send signal to ESC.
  }

}
1 Like

Thanks for the formatted version

It is a matter of personal choice but this is how I have Auto Format set up to format code as I think it shows the code blocks more clearly

#include <Servo.h>

int ESCPin = 9; // signal pin for the ESC.
int switchPin = A1; // analog input pin for the FWD/REV switch.
int potPin = A0; // analog input pin for the potentiometer (ebike throttle in my case-not sure what to change).
Servo servo;

void setup()
{
  servo.attach(ESCPin);
  servo.writeMicroseconds(1500); // send "stop" signal to ESC. Also necessary to arm the ESC.
  delay(2000); // delay to allow the ESC to recognize the stopped signal and arm.
}

void loop()
{
  int potVal = analogRead(potPin); // read input from potentiometer.
  if (digitalRead(switchPin) == HIGH)
  {
    int pwmVal = map(potVal, 0, 1023, 1500, 2000); // maps potentiometer values to PWM value.
    servo.writeMicroseconds(pwmVal); // Send signal to ESC.
  }
  else
  {
    int pwmVal = map(potVal, 0, 1023, 1500, 1000); // maps potentiometer values to PWM value.
    servo.writeMicroseconds(pwmVal); // Send signal to ESC.
  }
}

A question for you
Is there a pulldown resistor on the switch pin to keep it LOW when not expressly switched to HIGH or is it floating at an unknown voltage, maybe LOW, maybe HIGH ?

Consider using INPUT_PULLUP in the pinMode() for the switch pin to activate the built in pullup resistor and change the circuit to take the pin LOW when teh switch is turned on

Thank you once again.

Great point, there is no such resistor on the switch. How would I use "INPUT_PULLUP in the pinMode()? Is "INPUT_PULLUP" an argument to this function?

I will look for code examples.

Thanks!
Jeff

If anyone else is looking for information on INPUT_PULLUP, here's a link:

Hey mate how did you go with this project?
I m trying to do the same thing for my Ride on project

I got it to work! I'll post the code I used.

#include <Servo.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH1106.h>

Adafruit_SH1106 display(-1);

int ESCPin = 9; // signal pin for the ESC.
int killPin = A3; // analog input pin for the kill switch.
int MaxDialPin = A2; // analog input pin for the max speed dial pot.
int switchPin = A1; // analog input pin for the FWD/REV switch.
int potPin = A0; // analog input pin for the throttle (ebike hall throttle in this case).
float potVal;
float MaxDialVal;
float pwmVal;
float MaxpwmVal;
Servo servo;
int nIts = 20;

int LowVolt = 227 + 180;
int HiVolt = 929 - 180;

void setup() {
  Serial.begin(9600); // open the serial port at 9600 bps

  servo.attach(ESCPin);
  pinMode(killPin, INPUT_PULLUP);
  pinMode(MaxDialPin, INPUT);
  pinMode(switchPin, INPUT_PULLUP);
  servo.writeMicroseconds(1500); // send "stop" signal to ESC. Also necessary to arm the ESC.

  display.begin(SH1106_SWITCHCAPVCC, 0x3C);
  display.clearDisplay(); /* Clear display */
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.setTextSize(2); /* Select font size of text. Increases with size of argument. */
  display.println("Tasla");
  display.println("Motors");

  display.display();

  delay(2000); // delay to allow the ESC to recognize the stopped signal and arm.
  display.clearDisplay();

}

void loop() {

  /*potVal = analogRead(potPin); // read input from potentiometer.*/

  display.clearDisplay();

  if (digitalRead(killPin) == HIGH) { // If kill switch not activated, then proceed in Run mode

    for (int i = 0; i < nIts; i++) {
      potVal = potVal + analogRead(potPin);
      /*delay(1);*/
      MaxDialVal = MaxDialVal + analogRead(MaxDialPin);

    }

    potVal = potVal / nIts;
    MaxDialVal = MaxDialVal / nIts;
    //Serial.print("potVal value is: ");
    //Serial.println(potVal);
    //Serial.print("MaxDialVal value is: ");
    //Serial.println(MaxDialVal);

    if (digitalRead(switchPin) == HIGH) {

      pwmVal = map(potVal, LowVolt, HiVolt, 1500, 2000); // maps potentiometer values to PWM value.
      //Serial.print("Raw pwmVal value is: ");
      //Serial.println(pwmVal);
      MaxpwmVal = 1500 + (500 * (MaxDialVal / 1058));
      //Serial.print("MaxpwmVal value is: ");
      //Serial.println(MaxpwmVal);
      if (pwmVal > MaxpwmVal) {
        pwmVal = MaxpwmVal;
      }
      //Serial.print("Max-adjusted pwmVal value is: ");
      //Serial.println(pwmVal);
      if (pwmVal <= 1505) {
        pwmVal = 1500;
      }
      //Serial.print("LT 1505 trimmed pwmVal value is: ");
      //Serial.println(pwmVal);
      servo.writeMicroseconds(pwmVal); // Send signal to ESC.
      /*servo.writeMicroseconds(1500); // Send signal to ESC.*/
      Serial.print("Forward: potVal value is: ");
      Serial.println(potVal);
      Serial.print("Forward: PWM value is: ");
      Serial.println(pwmVal);

      display.setCursor(0, 0);
      display.setTextSize(1); /* Select font size of text. Increases with size of argument. */
      display.println("FWD Throttle: ");

      display.setCursor(0, 10);
      display.setTextSize(2); /* Select font size of text. Increases with size of argument. */
      float FWDVoltPerc = ((potVal - LowVolt) / (HiVolt - LowVolt)) * 100;
      if (FWDVoltPerc < 0) {
        FWDVoltPerc = 0;
      }
      if (FWDVoltPerc > 100) {
        FWDVoltPerc = 100;
      }
      display.print(FWDVoltPerc, 0);
      display.print(" %");

      display.setCursor(0, 35);
      display.setTextSize(1); /* Select font size of text. Increases with size of argument. */
      display.println("PWM Output: ");

      display.setTextSize(2);
      display.setCursor(0, 45);
      float FWDPWMPerc = ((pwmVal - 1500) / 500) * 100;
      if (FWDPWMPerc < 0) {
        FWDPWMPerc = 0;
      }
      if (FWDPWMPerc > 100) {
        FWDPWMPerc = 100;
      }
      //display.print(MaxDialVal, 0);
      //display.print(pwmVal, 0);
      display.print(FWDPWMPerc, 0);
      display.print(" %");

      display.display();



    }
    else {
      /*if (digitalRead(switchPin) == LOW) {*/

      pwmVal = map(potVal, LowVolt, HiVolt, 1500, 1000); // maps potentiometer values to PWM value.
      //Serial.print("Raw pwmVal value is: ");
      //Serial.println(pwmVal);
      MaxpwmVal = 1500 - (500 * (MaxDialVal / 1058));
      //Serial.print("MaxpwmVal value is: ");
      //Serial.println(MaxpwmVal);
      if (pwmVal < MaxpwmVal) {
        pwmVal = MaxpwmVal;
      }
      //Serial.print("Max-adjusted pwmVal value is: ");
      //Serial.println(pwmVal);
      if (pwmVal >= 1475) {
        pwmVal = 1500;
      }
      //Serial.print("GT 1475 trimmed pwmVal value is: ");
      //Serial.println(pwmVal);
      servo.writeMicroseconds(pwmVal); // Send signal to ESC.
      Serial.print("Reverse: potVal value is: ");
      Serial.println(potVal);
      Serial.print("Reverse: PWM value is: ");
      Serial.println(pwmVal);

      display.setCursor(0, 0);
      display.setTextSize(1); /* Select font size of text. Increases with size of argument. */
      display.println("REV Throttle: ");

      display.setCursor(0, 10);
      display.setTextSize(2); /* Select font size of text. Increases with size of argument. */
      float REVVoltPerc = ((potVal - LowVolt) / (HiVolt - LowVolt)) * 100;
      if (REVVoltPerc < 0) {
        REVVoltPerc = 0;
      }
      if (REVVoltPerc > 100) {
        REVVoltPerc = 100;
      }
      display.print(REVVoltPerc, 0);
      display.print(" %");

      display.setCursor(0, 35);
      display.setTextSize(1); /* Select font size of text. Increases with size of argument. */
      display.println("PWM Output: ");

      display.setTextSize(2);
      display.setCursor(0, 45);
      float REVPWMPerc = (((pwmVal - 1500) * -1) / 500) * 100;
      if (REVPWMPerc < 0) {
        REVPWMPerc = 0;
      }
      if (REVPWMPerc > 100) {
        REVPWMPerc = 100;
      }
      //display.print(MaxDialVal, 0);
      //display.print(pwmVal, 0);
      display.print(REVPWMPerc, 0);
      display.print(" %");

      display.display();

    }

  }

  else
  {
    pwmVal = 1500 ;
    servo.writeMicroseconds(pwmVal); // Send signal to ESC.
    Serial.print("Kill switch activated, ESC in neutral");
    Serial.println(pwmVal);

    display.setCursor(0, 0);
    display.setTextSize(2); /* Select font size of text. Increases with size of argument. */
    display.println("KILL");
    display.println("SWITCH ON");

    display.display();

  }
}