Creation of PWM signal for Transmitter based on Digital and Analog Input for an RC Car

Hello, I am trying to create a program that will take in two digital inputs (forward and backward) and read the analog input from a joystick to generate a digital signal, always beginning with four pulses with a width of 1500 microseconds as a synchronization segment (to let the receiver know a valid signal is arriving) and then a command section with a varied number of pulses and a varied pulse width (chosen by the RC car direction and turn angle, respectively). Much of the information about the signal is recorded in the many constants in the code below.

The problem I'm having is that the code is not working at the moment. I only got a flash of a square wave a couple days ago when testing the joystick (in other words, only turning, no forward/backward direction), but it was so brief (and I got distracted by another problem with the joystick) that I couldn't measure any of its parameters. Regardless, any help would be appreciated in helping to fix my code.

Note that I want the output to be, for every iteration of the loop() function, one cycle of the total command signal from pin 10, consisting of four synchronization pulses and some number of pulses with a set pulse width depending on the current mode of operation. For example, if the Arduino was told to only go forward, then the four synchronization pulses would be executed and then 15 pulses with a pulse width of 750 microseconds would be generated.

Hopefully my question was clear, despite its length. Let me know if anything needs to be clarified. Thank you for any help given.

Below is my code (Note that Forward and Backward are separate inputs as I want there to exist no movement in the RC car, if not commanded. Additionally, the angle formula in the commandSignal() method is an equation I derived; I can give the derivation if anyone deems it necessary):

int forwardPin = 12;
int backwardPin = 13;
int commandPin = 10;

// Pulse Widths
const int centerPW = 750;             // Standard Pulse Width for no turning
const int minPW = 500;                 // Minimum Pulse Width for turning
const int maxPW = 1000;              // Maximum Pulse Width for turning
const int syncPW = 1500;              // Synchronization Segment Pulse Width
const float maxSig = 1023.0;        // Joystick minimum and maximum serial outputs
const float minSig = 0.0;
const float restSig = 501.0;           // Resting joystick serial output
const float shiftSig = 30.0;            // Serial output shift of joystick to apply deadzone

// Pulse Counts for each mode of operation
const int forwardPC = 15;
const int backwardPC = 40;
const int leftPC = 25;
const int rightPC = 30;
const int forwardLeftPC = 10;
const int backwardLeftPC = 20;
const int forwardRightPC = 35;
const int backwardRightPC = 45;

const float period = 1 / 490; 
const float conversion = 255; // Number gotten to convert Duty Cycle percentages to analogWrite() output
float w_min = 0.12495*(((maxPW - minPW)/(maxSig - minSig))*minSig + minPW);
float w_max = 0.12495*(((maxPW - minPW)/(maxSig - minSig))*maxSig + minPW);
float w_rest = 0.12495*(((maxPW - minPW)/(maxSig - minSig))*restSig + minPW);

const int syncDutyCycle = syncPW / period;
const int centerCommandDutyCycle = centerPW / period;
const int syncInterval = conversion*syncDutyCycle;
const int centerCommandInterval = conversion*centerCommandDutyCycle;

void setup() {
  pinMode(forwardPin, INPUT);
  pinMode(backwardPin, INPUT);
  pinMode(commandPin, OUTPUT);  
}

void loop() {
  int F = digitalRead(forwardPin);
  int B = digitalRead(backwardPin);
  float Turn = analogRead(A0);
  int L = 0;
  int R = 0;
  int command = 0;
  
  // Joystick center serial output is 500 to 501, so +/- 30 to 
  // include an artificial dead zone to not create sudden turns
  if (Turn > restSig + shiftSig) {
    L = 0;
    R = 1;
  }
  else if (Turn < restSig - shiftSig) {
    L = 1;
    R = 0;
  }
  else {
    L = 0;
    R = 0;
  }

  command = 1000*F + 100*B + 10*L + R;

  // commandSignal(a, b, c, d)
  // If b == 0, no back/forth; if b == 1, forward; if b == -1, backward
  // If c == 0, no turn; if c == 1, wheels angle right; if c == -1, wheels angle left
  switch (command) {
    case 1000:
      commandSignal(forwardPC, 1, 0, Turn);
      break;
    case 0100:
      commandSignal(backwardPC, -1, 0, Turn);
      break;
    case 0010:
      commandSignal(leftPC, 0, -1, Turn);
      break;
    case 0001:
      commandSignal(rightPC, 0, 1, Turn);
      break;
    case 1010:
      commandSignal(forwardLeftPC, 1, -1, Turn);
      break;
    case 0110:
      commandSignal(backwardLeftPC, -1, -1, Turn);
      break;
    case 1001:
      commandSignal(forwardRightPC, 1, 1, Turn);
      break;
    case 0101:
      commandSignal(backwardRightPC, -1, 1, Turn);
      break;
    default:
      command = 0;
  }
  delay(1000); // Temporary
}

void sync() {
  int i;
  for (i = 0; i <= 3; i++) {
    analogWrite(commandPin, syncInterval);
  }
}

void commandSignal(int pulseCount, int motorDir, int turnDir, float turn) {
  int i;
  float anglePWM = 0;
  sync();
  if (motorDir != 0 && turnDir == 0) {
    for (i = 0; i <= pulseCount - 1; i++) {
      analogWrite(commandPin, centerCommandInterval);
    }
  }
  else if (turnDir == -1) {
    anglePWM = ((w_rest - w_min)/((restSig - shiftSig) - minSig))*turn + w_min;
    for (i = 0; i <= pulseCount - 1; i++) {
      analogWrite(commandPin, anglePWM);
    }
  }
  else {
    anglePWM = ((w_max - w_rest)/(maxSig - (restSig + shiftSig)))*turn + w_rest;
    for (i = 0; i <= pulseCount - 1; i++) {
      analogWrite(commandPin, anglePWM);
    }
  }
}

Haven't checked your code, but:

  • A long press "Left" starts a contunuous (pwm) pulse stream.
  • Then press "Forward" produces a single pulse burst (1-?? pulses).

I think that you don't understand analogWrite(), and you possibly did not understand the output signal encoding.

In any case analogWrite() is not usable in your project.
Write your own pulse output function that produces a given number of pulses of a given width.

Is this a protocol you are making up, or one that exists and has been reverse engineered or you are trying to do?

Just curious.

a7

I assume you are talking about using digitialWrite() and delay() in whatever way to create the pulses that I want?

And you're probably right that I don't fully understand analogWrite(). My logic in trying to understand its specifics is that since using digitalWrite(HIGH) and digitalWrite(LOW) with some delay() results in one cycle being generated for every loop() iteration. I assumed that had to be true for analogWrite(), which seems to have been an incorrect assumption.

It is one I'm trying to make up myself.

analogWrite() creates continuous pulses with the same width. What you need are single pulses with adjustable width. You can do that using delay() or non blocking as in the BlinkWithoutDelay example.

Why don't you want to use the digital RC protocol?

Because I can't do that for my project; the code must be made by me. What I'm asking for shouldn't be complicated as all it does is convert some inputs into an output with some basic set characteristics. I will try to use the digitalWrite() method and see what I get.

Thank you all for your help, and thank you for the simulation @dlloyd.

I was able to figure it out and got everything working. Below is the code and a link to the updated simulation:

int forwardPin = 12;
int backwardPin = 13;
int commandPin = 10;

// Pulse Widths
const int centerPW = 750;             // Standard Pulse Width for no turning
const int minPW = 500;                 // Minimum Pulse Width for turning
const int maxPW = 1000;              // Maximum Pulse Width for turning
const int syncPW = 1500;              // Synchronization Segment Pulse Width
const float maxSig = 1023.0;        // Joystick minimum and maximum serial outputs
const float minSig = 0.0;
const float restSig = 501.0;        // Resting joystick serial output
const float shiftSig = 30.0;        // Serial output shift of joystick to apply deadzone

// Pulse Counts for each mode of operation
const int forwardPC = 15;
const int backwardPC = 40;
const int leftPC = 25;
const int rightPC = 30;
const int forwardLeftPC = 10;
const int backwardLeftPC = 20;
const int forwardRightPC = 35;
const int backwardRightPC = 45;
const int syncPC = 4;

const float period_us = 1000000 / 490;
const float conversion = 255; // Number gotten to convert Duty Cycle percentages to analogWrite() output

//const float w_min = 0.12495 * (((maxPW - minPW) / (maxSig - minSig)) * (minSig - minSig) + minPW);
//const float w_max = 0.12495 * (((maxPW - minPW) / (maxSig - minSig)) * (maxSig - minSig) + minPW);
//const float w_rest = 0.12495 * (((maxPW - minPW) / (maxSig - minSig)) * (restSig - minSig) + minPW);

const float pw_min = ((maxPW - minPW) / (maxSig - minSig)) * (minSig - minSig) + minPW;
const float pw_max = ((maxPW - minPW) / (maxSig - minSig)) * (maxSig - minSig) + minPW;
const float pw_rest = ((maxPW - minPW) / (maxSig - minSig)) * (restSig - minSig) + minPW;

const int syncDutyCycle = syncPW / period_us;
const int centerCommandDutyCycle = centerPW / period_us;
const int syncInterval = conversion*syncDutyCycle;
const int centerCommandInterval = conversion*centerCommandDutyCycle;

void setup() {
  pinMode(forwardPin, INPUT);
  pinMode(backwardPin, INPUT);
  pinMode(commandPin, OUTPUT);  
  Serial.begin(9600);
}

void loop() {
  int F = digitalRead(forwardPin);
  int B = digitalRead(backwardPin);
  float Turn = analogRead(A0);
  int L = 0;
  int R = 0;
  int command = 0;
  
  // Joystick center serial output is 500 to 501, so +/- 30 to 
  // include an artificial dead zone to not create sudden turns
  if (Turn > restSig + shiftSig) {
    L = 0;
    R = 1;
  }
  else if (Turn < restSig - shiftSig) {
    L = 1;
    R = 0;
  }
  else {
    L = 0;
    R = 0;
  }

  command = 1000*F + 100*B + 10*L + R;
  Serial.print(command);
  Serial.print(", ");

  // commandSignal(a, b, c, d)
  // If b == 1, no back/forth; if b == 2, forward; if b == 0, backward
  // If c == 1, no turn; if c == 2, wheels angle right; if c == 0, wheels angle left
  switch (command) {
    case 1000:
      commandSignal(forwardPC, 2, 1, 0);
      break;
    case 100:
      commandSignal(backwardPC, 0, 1, 0);
      break;
    case 10:
      commandSignal(leftPC, 1, 0, Turn);
      break;
    case 1:
      commandSignal(rightPC, 1, 2, Turn);
      break;
    case 1010:
      commandSignal(forwardLeftPC, 2, 0, Turn);
      break;
    case 110:
      commandSignal(backwardLeftPC, 0, 0, Turn);
      break;
    case 1001:
      commandSignal(forwardRightPC, 2, 2, Turn);
      break;
    case 101:
      commandSignal(backwardRightPC, 0, 2, Turn);
      break;
    default:
      digitalWrite(commandPin, LOW);
  }

  Serial.println("");

  // delayMicroseconds(period_us); // Temporary
}

void commandSignal(int pulseCount, int motorDir, int turnDir, float turn) {
  int mode = 10*motorDir + turnDir; // motorDir: 0 - Back, 1 - None, 2 - Forward
                                    // turnDir:  0 - Left, 1 - None, 2 - Right

  Serial.println(mode);

  float anglePWM_L = ((pw_rest - pw_min) / ((restSig - shiftSig) - minSig)) * (turn - minSig) + pw_min;
  float anglePWM_R = ((pw_max - pw_rest) / (maxSig - (restSig + shiftSig))) * (turn - (restSig + shiftSig)) + pw_rest;

  // Synchronization Signal
  signalPWM(syncPC, syncPW);

  switch (mode) {
    case 21:
      signalPWM(forwardPC, centerPW);
      break;
    case 1:
      signalPWM(backwardPC, centerPW);
      break;
    case 10:
      signalPWM(leftPC, anglePWM_L);
      break;
    case 12:
      signalPWM(rightPC, anglePWM_R);
      break;
    case 20:
      signalPWM(forwardLeftPC, anglePWM_L);
      break;
    case 0:
      signalPWM(backwardLeftPC, anglePWM_L);
      break;
    case 22:
      signalPWM(forwardRightPC, anglePWM_R);
      break;
    case 2:
      signalPWM(backwardRightPC, anglePWM_R);
      break;
    default:
      digitalWrite(commandPin, LOW);
  }
}

void signalPWM(int PC, float PW) {
  float remain = period_us - PW;
  for (int i = 0; i <= PC - 1; i++) {
    digitalWrite(commandPin, HIGH);
    delayMicroseconds(PW);
    digitalWrite(commandPin, LOW);
    delayMicroseconds(remain);
  }
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.