Programming Q on reading PWM signals from RC receiver

Hello,
I'm using the sabertooth 2x25 motor driver in MODE 2 (RC, differential, exponential).
Sabertooth 2X25 V2 regenerative dual motor driver (also has a manual/PDF file link).
In this mode (2, RC), there are 2 signal inputs to the motor driver: S1 & S2. S1 is the PWM signal for foward/reverse for both of my drive motors. S2 is the PWM signal for steering left/right.
This is where switch 4 is set to mixed mode ON (differential drive), where you control the movements with 1 stick (2 channels) on the transmitter.
I had a tough time finding some reference code that doesn't use the S1 & S2 signal inputs for each drive motor. Example: S1 controls left motor & S2 controls right motor (independent drive).
I also like the way the author of this blog/code is not using pulseIn commands and using interrupt service routines, etc... He actually goes into detail about this.

*Btw, I'm still learning a lot, but still suck at progr./C++ as you may know, but I plan on taking an online class/tutorial to hopefully take me up another level.

He included a <PinChangeInt.h> library which I downloaded and installed.
Here is the page:

I have my receiver hooked up to the Input pins, but in the serial monitor the only thing that shows up is 'multiChannels' which I see in the code.
I would like to add some Serial.print(); Serial.println(); to give me readouts of my 2 channels (throttle & steering).
Just unsure of what variable to use (unThrottleIn..?)

Here is the code:

// MultiChannels
//
// rcarduino.blogspot.com
//
// A simple approach for reading three RC Channels using pin change interrupts
//
// See related posts - 
// http://rcarduino.blogspot.co.uk/2012/01/how-to-read-rc-receiver-with.html
// http://rcarduino.blogspot.co.uk/2012/03/need-more-interrupts-to-read-more.html
// http://rcarduino.blogspot.co.uk/2012/01/can-i-control-more-than-x-servos-with.html
//
// rcarduino.blogspot.com
//

// include the pinchangeint library - see the links in the related topics section above for details
#include <PinChangeInt.h>

#include <Servo.h>

// Assign your channel in pins
#define THROTTLE_IN_PIN 5
#define STEERING_IN_PIN 6
#define AUX_IN_PIN 7

// Assign your channel out pins
#define THROTTLE_OUT_PIN 8
#define STEERING_OUT_PIN 9
#define AUX_OUT_PIN 10

// Servo objects generate the signals expected by Electronic Speed Controllers and Servos
// We will use the objects to output the signals we read in
// this example code provides a straight pass through of the signal with no custom processing
Servo servoThrottle;
Servo servoSteering;
Servo servoAux;

// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define THROTTLE_FLAG 1
#define STEERING_FLAG 2
#define AUX_FLAG 4

// holds the update flags defined above
volatile uint8_t bUpdateFlagsShared;

// shared variables are updated by the ISR and read by loop.
// In loop we immediatley take local copies so that the ISR can keep ownership of the 
// shared ones. To access these in loop
// we first turn interrupts off with noInterrupts
// we take a copy to use in loop and the turn interrupts back on
// as quickly as possible, this ensures that we are always able to receive new signals
volatile uint16_t unThrottleInShared;
volatile uint16_t unSteeringInShared;
volatile uint16_t unAuxInShared;

// These are used to record the rising edge of a pulse in the calcInput functions
// They do not need to be volatile as they are only used in the ISR. If we wanted
// to refer to these in loop and the ISR then they would need to be declared volatile
uint32_t ulThrottleStart;
uint32_t ulSteeringStart;
uint32_t ulAuxStart;

void setup()
{
  Serial.begin(9600);
  
  Serial.println("multiChannels");

  // attach servo objects, these will generate the correct 
  // pulses for driving Electronic speed controllers, servos or other devices
  // designed to interface directly with RC Receivers  
  servoThrottle.attach(THROTTLE_OUT_PIN);
  servoSteering.attach(STEERING_OUT_PIN);
  servoAux.attach(AUX_OUT_PIN);

  // using the PinChangeInt library, attach the interrupts
  // used to read the channels
  PCintPort::attachInterrupt(THROTTLE_IN_PIN, calcThrottle,CHANGE); 
  PCintPort::attachInterrupt(STEERING_IN_PIN, calcSteering,CHANGE); 
  PCintPort::attachInterrupt(AUX_IN_PIN, calcAux,CHANGE); 
}

void loop()
{
  // create local variables to hold a local copies of the channel inputs
  // these are declared static so that thier values will be retained 
  // between calls to loop.
  static uint16_t unThrottleIn;
  static uint16_t unSteeringIn;
  static uint16_t unAuxIn;
  // local copy of update flags
  static uint8_t bUpdateFlags;

  // check shared update flags to see if any channels have a new signal
  if(bUpdateFlagsShared)
  {
    noInterrupts(); // turn interrupts off quickly while we take local copies of the shared variables

    // take a local copy of which channels were updated in case we need to use this in the rest of loop
    bUpdateFlags = bUpdateFlagsShared;
    
    // in the current code, the shared values are always populated
    // so we could copy them without testing the flags
    // however in the future this could change, so lets
    // only copy when the flags tell us we can.
    
    if(bUpdateFlags & THROTTLE_FLAG)
    {
      unThrottleIn = unThrottleInShared;
    }
    
    if(bUpdateFlags & STEERING_FLAG)
    {
      unSteeringIn = unSteeringInShared;
    }
    
    if(bUpdateFlags & AUX_FLAG)
    {
      unAuxIn = unAuxInShared;
    }
     
    // clear shared copy of updated flags as we have already taken the updates
    // we still have a local copy if we need to use it in bUpdateFlags
    bUpdateFlagsShared = 0;
    
    interrupts(); // we have local copies of the inputs, so now we can turn interrupts back on
    // as soon as interrupts are back on, we can no longer use the shared copies, the interrupt
    // service routines own these and could update them at any time. During the update, the 
    // shared copies may contain junk. Luckily we have our local copies to work with :-)
  }
  
  // do any processing from here onwards
  // only use the local values unAuxIn, unThrottleIn and unSteeringIn, the shared
  // variables unAuxInShared, unThrottleInShared, unSteeringInShared are always owned by 
  // the interrupt routines and should not be used in loop
  
  // the following code provides simple pass through 
  // this is a good initial test, the Arduino will pass through
  // receiver input as if the Arduino is not there.
  // This should be used to confirm the circuit and power
  // before attempting any custom processing in a project.
  
  // we are checking to see if the channel value has changed, this is indicated  
  // by the flags. For the simple pass through we don't really need this check,
  // but for a more complex project where a new signal requires significant processing
  // this allows us to only calculate new values when we have new inputs, rather than
  // on every cycle.
  if(bUpdateFlags & THROTTLE_FLAG)
  {
    if(servoThrottle.readMicroseconds() != unThrottleIn)
    {
      servoThrottle.writeMicroseconds(unThrottleIn);
    }
  }
  
  if(bUpdateFlags & STEERING_FLAG)
  {
    if(servoSteering.readMicroseconds() != unSteeringIn)
    {
      servoSteering.writeMicroseconds(unSteeringIn);
    }
  }
  
  if(bUpdateFlags & AUX_FLAG)
  {
    if(servoAux.readMicroseconds() != unAuxIn)
    {
      servoAux.writeMicroseconds(unAuxIn);
    }
  }
  
  bUpdateFlags = 0;
}


// simple interrupt service routine
void calcThrottle()
{
  // if the pin is high, its a rising edge of the signal pulse, so lets record its value
  if(digitalRead(THROTTLE_IN_PIN) == HIGH)
  { 
    ulThrottleStart = micros();
  }
  else
  {
    // else it must be a falling edge, so lets get the time and subtract the time of the rising edge
    // this gives use the time between the rising and falling edges i.e. the pulse duration.
    unThrottleInShared = (uint16_t)(micros() - ulThrottleStart);
    // use set the throttle flag to indicate that a new throttle signal has been received
    bUpdateFlagsShared |= THROTTLE_FLAG;
  }
}

void calcSteering()
{
  if(digitalRead(STEERING_IN_PIN) == HIGH)
  { 
    ulSteeringStart = micros();
  }
  else
  {
    unSteeringInShared = (uint16_t)(micros() - ulSteeringStart);
    bUpdateFlagsShared |= STEERING_FLAG;
  }
}

void calcAux()
{
  if(digitalRead(AUX_IN_PIN) == HIGH)
  { 
    ulAuxStart = micros();
  }
  else
  {
    unAuxInShared = (uint16_t)(micros() - ulAuxStart);
    bUpdateFlagsShared |= AUX_FLAG;
  }
}

Hi,
Thats my blog, if you look in the comments for the code you will find -

// do any processing from here onwards
// only use the local values unAuxIn, unThrottleIn and unSteeringIn, the shared ...

so you can simply add -

Serial.println(unAuxIn);
Serial.println(unThrottleIn);
Serial.println(unSteeringIn);

immediately after the comments copied above

Duane B

rcarduino.blogspot.com

R/C uses pulse-width modulation (PWM), not pulse-position modulation (PPM). In fact the term PWM used with the Arduino is somewhat mis-used as its really duty-cycle modulation at a fixed frequency... For R/C control signals pulses are often lost due to reception varying, so the pulse frequency and position specifically ignored, its only the width of pulses that is used.

DuaneB:
Hi,
Thats my blog, if you look in the comments for the code you will find -

// do any processing from here onwards
// only use the local values unAuxIn, unThrottleIn and unSteeringIn, the shared ...

so you can simply add -

Serial.println(unAuxIn);
Serial.println(unThrottleIn);
Serial.println(unSteeringIn);

immediately after the comments copied above

Duane B

rcarduino.blogspot.com

Ahh, thanks very much Duane for posting all the great info and code on your blogsite :slight_smile:
Really like the way you set the code up for channel inputs and outputs. Just tried it out and works great.

Btw, I emailed a local tech college professor in programming. They offer 2 courses in C++. I told him what I was doing and asked him if these courses would help me out.
He replied saying the C++ courses were more 'business' oriented...really not sure what he meant.
He said to check out C++ courses geared more toward engineering...
I guess I'll look around on-line for some.

@Mark:
Thanks for the clarification :slight_smile:
And your response made perfect sense,
thanks again!

He replied saying the C++ courses were more 'business' oriented...really not sure what he meant.

Most likely, the focus of the course(s) is on developing applications that manipulate data in a way that makes sense for a business-related application, such as payroll processing or employee tracking, rather than manipulating data in a way that makes sense for engineering purposes.

Taking a business-related course will still be of benefit, since you learn the syntax and variety of tools available. The focus of the applications developed may not be as obvious as you want, but learning to collect input, manipulate it, and produce output is all that you really need to be able to do.

R/C uses pulse-width modulation (PWM), not pulse-position modulation (PPM)

R/C uses both.
In the stream between transmitter and receiver, the individual channel pulses are combined into a frame and form a PPM stream, and are then demultiplexed at the receiver to form the individual PWM streams.