Thursday, April 25, 2013

Reading RC Channels with Arduino Due

Update 27/04/2013

The following code was originally presented as a test sketch to demonstrate a glitch in the Arduino Due micros function which is being resolved. See the following post for details of the glitch and resolution.

http://arduino.cc/forum/index.php/topic,162787.0/topicseen.html

With this resolution in place the code presented below can be used to read 8 RC Channels and output them to a combination of upto 8 RC Servos or ESCs.

The code can be operated in two configurations -

1) Loop Back Test - Here 8 servo outputs are created and given fixed values from 1100 to 1800, these pulse values are output on pins 10 to 17 and can be read back in through the interrupts attached to pins 2 to 9. This is intended to give the user confidence that the code is able to read multiple RC Channels before moving to configuration 2 - Pass Through

2) Pass Through -  This is similar to 1) above however the input is now connected to the output such that a change in an incoming signal will cause a corresponding change in the servo output signal. To implement pass  through on any channel, simply remove the comment form the start of the servo.writeMicrosecond command for that channel for example

Change this -

  if(bUpdateFlags & CHANNEL1_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      //servoChannel1.writeMicroseconds(unChannel1In);
      Serial.print(unChannel1In);
      Serial.print(",");
  }
to this -

  if(bUpdateFlags & CHANNEL1_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      servoChannel1.writeMicroseconds(unChannel1In);
      Serial.print(unChannel1In);
      Serial.print(",");
  }




The code has been ported from an original project based on the Arduino UNO, follow the links in the comments for the background and detailed explanation.

// MultiChannels
//
// rcarduino.blogspot.com
//
// A simple approach for reading and writing eight RC Channels using Arduino Due 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 "Servo.h"

// Assign your channel in pins
#define CHANNEL1_IN_PIN 2
#define CHANNEL2_IN_PIN 3
#define CHANNEL3_IN_PIN 4
#define CHANNEL4_IN_PIN 5
#define CHANNEL5_IN_PIN 6
#define CHANNEL6_IN_PIN 7
#define CHANNEL7_IN_PIN 8
#define CHANNEL8_IN_PIN 9

// Assign your channel out pins
#define CHANNEL1_OUT_PIN 10
#define CHANNEL2_OUT_PIN 11
#define CHANNEL3_OUT_PIN 12
#define CHANNEL4_OUT_PIN 13
#define CHANNEL5_OUT_PIN 14
#define CHANNEL6_OUT_PIN 15
#define CHANNEL7_OUT_PIN 16
#define CHANNEL8_OUT_PIN 17

// 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 servoChannel1;
Servo servoChannel2;
Servo servoChannel3;
Servo servoChannel4;
Servo servoChannel5;
Servo servoChannel6;
Servo servoChannel7;
Servo servoChannel8;

// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define CHANNEL1_FLAG 1
#define CHANNEL2_FLAG 2
#define CHANNEL3_FLAG 4
#define CHANNEL4_FLAG 8
#define CHANNEL5_FLAG 16
#define CHANNEL6_FLAG 32
#define CHANNEL7_FLAG 64
#define CHANNEL8_FLAG 128

// holds the update flags defined above
volatile uint32_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 uint32_t unChannel1InShared;
volatile uint32_t unChannel2InShared;
volatile uint32_t unChannel3InShared;
volatile uint32_t unChannel4InShared;
volatile uint32_t unChannel5InShared;
volatile uint32_t unChannel6InShared;
volatile uint32_t unChannel7InShared;
volatile uint32_t unChannel8InShared;

void setup()
{
  Serial.begin(115200);

  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
  servoChannel1.attach(CHANNEL1_OUT_PIN);
  servoChannel2.attach(CHANNEL2_OUT_PIN);
  servoChannel3.attach(CHANNEL3_OUT_PIN);
  servoChannel4.attach(CHANNEL4_OUT_PIN);
  servoChannel5.attach(CHANNEL5_OUT_PIN);
  servoChannel6.attach(CHANNEL6_OUT_PIN);
  servoChannel7.attach(CHANNEL7_OUT_PIN);
  servoChannel8.attach(CHANNEL8_OUT_PIN);

  // attach the interrupts used to read the channels
  attachInterrupt(CHANNEL1_IN_PIN, calcChannel1,CHANGE);
  attachInterrupt(CHANNEL2_IN_PIN, calcChannel2,CHANGE);
  attachInterrupt(CHANNEL3_IN_PIN, calcChannel3,CHANGE);
  attachInterrupt(CHANNEL4_IN_PIN, calcChannel4,CHANGE);
  attachInterrupt(CHANNEL5_IN_PIN, calcChannel5,CHANGE);
  attachInterrupt(CHANNEL6_IN_PIN, calcChannel6,CHANGE);
  attachInterrupt(CHANNEL7_IN_PIN, calcChannel7,CHANGE);
  attachInterrupt(CHANNEL8_IN_PIN, calcChannel8,CHANGE);

  // for loop back test only, lets set each channel to a known value
  servoChannel1.writeMicroseconds(1100);
  servoChannel2.writeMicroseconds(1200);
  servoChannel3.writeMicroseconds(1300);
  servoChannel4.writeMicroseconds(1400);
  servoChannel5.writeMicroseconds(1500);
  servoChannel6.writeMicroseconds(1600);
  servoChannel7.writeMicroseconds(1700);
  servoChannel8.writeMicroseconds(1800);
}

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 uint32_t unChannel1In;
  static uint32_t unChannel2In;
  static uint32_t unChannel3In;
  static uint32_t unChannel4In;
  static uint32_t unChannel5In;
  static uint32_t unChannel6In;
  static uint32_t unChannel7In;
  static uint32_t unChannel8In;
 
  // local copy of update flags
  static uint32_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 & CHANNEL1_FLAG)
    {
      unChannel1In = unChannel1InShared;
    }
  
    if(bUpdateFlags & CHANNEL2_FLAG)
    {
      unChannel2In = unChannel2InShared;
    }
  
    if(bUpdateFlags & CHANNEL3_FLAG)
    {
      unChannel3In = unChannel3InShared;
    }
   
    if(bUpdateFlags & CHANNEL4_FLAG)
    {
      unChannel4In = unChannel4InShared;
    }
  
    if(bUpdateFlags & CHANNEL5_FLAG)
    {
      unChannel5In = unChannel5InShared;
    }
  
    if(bUpdateFlags & CHANNEL6_FLAG)
    {
      unChannel6In = unChannel6InShared;
    }
   
    if(bUpdateFlags & CHANNEL7_FLAG)
    {
      unChannel7In = unChannel7InShared;
    }
  
    if(bUpdateFlags & CHANNEL8_FLAG)
    {
      unChannel8In = unChannel8InShared;
    }
    // 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 unChannel1, unChannel2, unChannel3, unChannel4, unChannel5, unChannel6, unChannel7, unChannel8
  // variables unChannel1InShared, unChannel2InShared, etc are always owned by the
  // the interrupt routines and should not be used in loop
 
  if(bUpdateFlags & CHANNEL1_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      //servoChannel1.writeMicroseconds(unChannel1In);
      Serial.println();
      Serial.print(unChannel1In);
      Serial.print(",");
  }

  if(bUpdateFlags & CHANNEL2_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      //servoChannel2.writeMicroseconds(unChannel2In);
      Serial.print(unChannel2In);
      Serial.print(",");
  }

  if(bUpdateFlags & CHANNEL3_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      //servoChannel3.writeMicroseconds(unChannel3In);
      Serial.print(unChannel3In);
      Serial.print(",");
  }

  if(bUpdateFlags & CHANNEL4_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel4.writeMicroseconds(unChannel4In);
    Serial.print(unChannel4In);
    Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL5_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel5.writeMicroseconds(unChannel5In);
    Serial.print(unChannel5In);
    Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL6_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel6.writeMicroseconds(unChannel6In);
    Serial.print(unChannel6In);
  }
 
  if(bUpdateFlags & CHANNEL7_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel7.writeMicroseconds(unChannel7In);
    Serial.print(unChannel7In);
    Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL8_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel8.writeMicroseconds(unChannel8In);
    Serial.print(unChannel8In);
  }
 
  bUpdateFlags = 0;
}

void calcChannel1()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL1_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel1InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL1_FLAG;
  }
}

void calcChannel2()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL2_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel2InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL2_FLAG;
  }
}

void calcChannel3()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL3_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel3InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL3_FLAG;
  }
}

void calcChannel4()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL4_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel4InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL4_FLAG;
  }
}

void calcChannel5()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL5_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel5InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL5_FLAG;
  }
}

void calcChannel6()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL6_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel6InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL6_FLAG;
  }
}

void calcChannel7()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL7_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel7InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL7_FLAG;
  }
}

void calcChannel8()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL8_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel8InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL8_FLAG;
  }
}


Wednesday, April 24, 2013

Problem reading RC Channels - The RCArduino Loop Back Test

Many people experience problems adding additional channels to the RCArduino test sketches or porting them to new devices, its usually down to mistakes in the way the additional channels are coded

This post presents an example of managing 8 input/output channels, its a very simple sketch which outputs 8 RC Channels and reads them right back in again.

The sketch can be used in three steps -

1) Initially the sketch outputs fixed values for the servos and reads them back in as a loop back test.

2) Once you are comfortable with the code, you can replace one or more of the loop back connections to start reading RC Channels. 

3) Once you are confident that the sketch is reading your channels correctly you can connect your servos and ESCs to the outputs and your done, 8 channels in, 8 channels out in three easy steps.


Step 1 - Loop back testing
To use the loop back test in step 1, connect pins 2 to 9 (the channel inputs) to pins 10 to 13 and A0 to A3 (the channel outputs).

Each channel is set to a fixed output from 1100 to 1800, if the code works on your board, your should see these values being output in the serial monitor.

Step 2 - RC Channel reading

Once you have this up and running, you can start swapping the connections to an RC receiver one by one. You should now see the channel values for the receiver channels updating in your serial monitor. If the values are within a range of around 1000 to 2000, your ready to move to step 3.

Step 3 - Full Control
To start outputting the values that you have read in, remove the comments from the servoName.writeMicroseconds functions to have full control of upto 8 servos/escs using 8 RC Receiver channels.

If it does not work, let me know which board you have, the code is easily adapted to work on any of the 8-bit Arduino boards. If you have a 32 bit Arduino Due, there is a dedicated post here -

http://rcarduino.blogspot.ae/2013/04/reading-rc-channels-with-arduino-due.html

For more performance and smoother output on the 8-bit Arduinos an RCArduinoFastLib versions will be added in separate in the coming days.

// RCArduino MultiChannel Loop back and servo ESC control for upto 8 RC channels
//
// 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 CHANNEL1_IN_PIN 2
#define CHANNEL2_IN_PIN 3
#define CHANNEL3_IN_PIN 4
#define CHANNEL4_IN_PIN 5
#define CHANNEL5_IN_PIN 6
#define CHANNEL6_IN_PIN 7
#define CHANNEL7_IN_PIN 8
#define CHANNEL8_IN_PIN 9

// Assign your channel out pins
#define CHANNEL1_OUT_PIN 10
#define CHANNEL2_OUT_PIN 11
#define CHANNEL3_OUT_PIN 12
#define CHANNEL4_OUT_PIN 13
#define CHANNEL5_OUT_PIN A0
#define CHANNEL6_OUT_PIN A1
#define CHANNEL7_OUT_PIN A2
#define CHANNEL8_OUT_PIN A3

// 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 servoChannel1;
Servo servoChannel2;
Servo servoChannel3;
Servo servoChannel4;
Servo servoChannel5;
Servo servoChannel6;
Servo servoChannel7;
Servo servoChannel8;

// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define CHANNEL1_FLAG 1
#define CHANNEL2_FLAG 2
#define CHANNEL3_FLAG 4
#define CHANNEL4_FLAG 8
#define CHANNEL5_FLAG 16
#define CHANNEL6_FLAG 32
#define CHANNEL7_FLAG 64
#define CHANNEL8_FLAG 128

// 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 unChannel1InShared;
volatile uint16_t unChannel2InShared;
volatile uint16_t unChannel3InShared;
volatile uint16_t unChannel4InShared;
volatile uint16_t unChannel5InShared;
volatile uint16_t unChannel6InShared;
volatile uint16_t unChannel7InShared;
volatile uint16_t unChannel8InShared;

void setup()
{
  Serial.begin(115200);

  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
  servoChannel1.attach(CHANNEL1_OUT_PIN);
  servoChannel2.attach(CHANNEL2_OUT_PIN);
  servoChannel3.attach(CHANNEL3_OUT_PIN);
  servoChannel4.attach(CHANNEL4_OUT_PIN);
  servoChannel5.attach(CHANNEL5_OUT_PIN);
  servoChannel6.attach(CHANNEL6_OUT_PIN);
  servoChannel7.attach(CHANNEL7_OUT_PIN);
  servoChannel8.attach(CHANNEL8_OUT_PIN);

  // using the PinChangeInt library, attach the interrupts
  // used to read the channels
  PCintPort::attachInterrupt(CHANNEL1_IN_PIN, calcChannel1,CHANGE);
  PCintPort::attachInterrupt(CHANNEL2_IN_PIN, calcChannel2,CHANGE);
  PCintPort::attachInterrupt(CHANNEL3_IN_PIN, calcChannel3,CHANGE);
  PCintPort::attachInterrupt(CHANNEL4_IN_PIN, calcChannel4,CHANGE);
  PCintPort::attachInterrupt(CHANNEL5_IN_PIN, calcChannel5,CHANGE);
  PCintPort::attachInterrupt(CHANNEL6_IN_PIN, calcChannel6,CHANGE);
  PCintPort::attachInterrupt(CHANNEL7_IN_PIN, calcChannel7,CHANGE);
  PCintPort::attachInterrupt(CHANNEL8_IN_PIN, calcChannel8,CHANGE);

  // for loop back test only, lets set each channel to a known value
  servoChannel1.writeMicroseconds(1100);
  servoChannel2.writeMicroseconds(1200);
  servoChannel3.writeMicroseconds(1300);
  servoChannel4.writeMicroseconds(1400);
  servoChannel5.writeMicroseconds(1500);
  servoChannel6.writeMicroseconds(1600);
  servoChannel7.writeMicroseconds(1700);
  servoChannel8.writeMicroseconds(1800);
}

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 unChannel1In;
  static uint16_t unChannel2In;
  static uint16_t unChannel3In;
  static uint16_t unChannel4In;
  static uint16_t unChannel5In;
  static uint16_t unChannel6In;
  static uint16_t unChannel7In;
  static uint16_t unChannel8In;

  uint8_t bUpdateFlags = 0;
  // check shared update flags to see if any channels have a new signal
  // for nicely formatted serial output use this
  if(bUpdateFlagsShared == 0xFF)
  // for more responsive projects update any channels whenever a new signal is available using this
  // 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 & CHANNEL1_FLAG)
    {
      unChannel1In = unChannel1InShared;
    }
  
    if(bUpdateFlags & CHANNEL2_FLAG)
    {
      unChannel2In = unChannel2InShared;
    }
  
    if(bUpdateFlags & CHANNEL3_FLAG)
    {
      unChannel3In = unChannel3InShared;
    }

    if(bUpdateFlags & CHANNEL4_FLAG)
    {
      unChannel4In = unChannel4InShared;
    }
  
    if(bUpdateFlags & CHANNEL5_FLAG)
    {
      unChannel5In = unChannel5InShared;
    }
  
    if(bUpdateFlags & CHANNEL6_FLAG)
    {
      unChannel6In = unChannel6InShared;
    }
   
    if(bUpdateFlags & CHANNEL7_FLAG)
    {
      unChannel7In = unChannel7InShared;
    }
  
    if(bUpdateFlags & CHANNEL8_FLAG)
    {
      unChannel8In = unChannel8InShared;
    }
  
    // 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
  if(bUpdateFlags & CHANNEL1_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel1.writeMicroseconds(unChannel1In);
      Serial.println("");
      Serial.print(bUpdateFlags);
      Serial.print(",");
      Serial.print(unChannel1In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL2_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel2.writeMicroseconds(unChannel2In);
      Serial.print(unChannel2In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL3_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel3.writeMicroseconds(unChannel3In);
      Serial.print(unChannel3In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL4_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel4.writeMicroseconds(unChannel4In);
      Serial.print(unChannel4In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL5_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel5.writeMicroseconds(unChannel5In);
      Serial.print(unChannel5In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL6_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel6.writeMicroseconds(unChannel6In);
      Serial.print(unChannel6In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL7_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel7.writeMicroseconds(unChannel7In);
      Serial.print(unChannel7In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL8_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel8.writeMicroseconds(unChannel8In);
      Serial.print(unChannel8In);
      Serial.print(",");
  }
}


void calcChannel1()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState) // this is equivalent to digitalRead(CHANNEL1_IN_PIN) but about 10 times faster
  {
    ulStart = micros();
  }
  else
  {
    unChannel1InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL1_FLAG;
  }
}

void calcChannel2()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel2InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL2_FLAG;
  }
}

void calcChannel3()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel3InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL3_FLAG;
  }
}

void calcChannel4()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel4InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL4_FLAG;
  }
}

void calcChannel5()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel5InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL5_FLAG;
  }
}

void calcChannel6()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel6InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL6_FLAG;
  }
}

void calcChannel7()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel7InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL7_FLAG;
  }
}

void calcChannel8()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel8InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL8_FLAG;
  }
}
Duane B


Thursday, April 11, 2013

The Problem ( and Solutions ) With Arduino Interrupts

If you have ever tried to port an Arduino project that uses interrupts from one board type to another, you have probably experienced frustration, this includes the sample code throughout RCArduino.


The following post examines the differences in interrupts between the popular boards, the Arduino UNO, Micro, Mega, Mini and Leonardo. Based on this information we can modify sketches to run on the full range of 8-bit Arduinos.

The 8-bitArduino boards are based on one of three related chips -

ATMega328 - UNO and Mini
ATMega32u4 - Leonardo and Micro
ATMega2560 - Mega

Each of these chips supports two types of interrupts -

1) External Interrupts
These are flexible easy to use interrupts which can be triggered by rising, falling or changing signals. The disadvantage is that there are a limited number available on each chip type.

If we want to access more interrupts we need to look at the next type -

2) Pin Change Interrupts
The underlying chip in your Arduino supports a second type of interrupt however these interrupts are not directly supported by Arduino and need to be accessed through an additional library.

Differences In External Interrupts

The external interrupts are associated with specific digital pins on each chip type, the following table taken from the attachInterrupt reference page lists the available external interrupts and the associated Arduino pin on each chip -


Boardint.0int.1int.2int.3int.4int.5
Uno, Ethernet,
Mini
23
Mega25602321201918
Leonardo, Micro3201


The Arduino team have hidden some of the differences between the ATMega328 and ATM2560 so that attaching INT0 using the attachInterrupt function will attach an interrupt to digital pin 2 on both chips even though on the Mega digital pin2 is actually INT4.

The same logic has not been carried across to the ATMega32u4 Based leonardo. Notice how int0 and int1 are actually reversed on the Leonardo, this will be a major trap for people who are porting code from the UNO.


Does the Leonardo have four external interrupts ?
While the Leonardo appears to have 4 external interrupts, int2 and int3 are attached to digital pins 0 and 1 which are almost always reserved for serial input/output. So yes there are four interrupts, but two of them are only available by disabling serial functionality.

Differences In Pin Change Interrupts
On the Arduino UNO, pin change interrupts can be used to enable interrupts on any of the Arduino PINs to give access to a total of 19 interrupts (13 digital pins and 6 Analog pins).

I initially assumed that this was also possible on the Mega, Micro and Leonardo as well. It isn't.

Pin change interrupts are supported on the following Leonardo/Micro pins - 8,9,10 and 11.

Pin change interrupts are supported on Arduino Mega pins 10,11,12,13,14,15 and analog pins 6 to 15

Interrupts and RCArduino
These differences between the Arduino platforms will have been responsible for some of the difficulty that users have had in porting RCArduino code to Minis, Micros, Leonardos and Megas.

The good news is that now we have a full understanding of the inconsistencies between the different devices there should be no problem in modifying the sample sketches to run on them.

If your having trouble with a sample sketch, get in touch, in the meantime I will be updating some of the sketches to work across multiple boards.

Stay tuned

Duane B

Tuesday, February 12, 2013

Arduino Notes, Frequencies and Scales plus a Bonus Filter.

A quick post with a file I use a lot to experiment with Audio projects.

The file contains the frequencies used to generate the midi notes 0 to 127 in your Arduino projects. The file doesn't quite provide the frequency, instead it provides the phase increment required to generate the frequency. If you are familiar with Direct Digital Synthesis you will know that this is what we use in our Auduino projects to generate sine and other interesting waveforms at a given frequency.

The Illutron B Uses Direct Digital Synthesis, there is an introduction to the technique here -
http://rcarduino.blogspot.com/2012/08/the-must-build-arduino-project-illutron.html

Saving Memory
The note tables file uses PROGMEM to efficiently store the frequency table out of the way in program memory -

http://arduino.cc/en/Reference/PROGMEM

In addition to the 128 midi notes, two scales are provided, the pentatonic blues scale that will be familiar to anyone who has built and Auduino and the equally widely used C Minor blues scale. To preserve memory, these scales are also stored in progmem. You can add your own scales by following the same pattern.

Accessing The Notes

To access a note you can use -

uint16_t unPhaseIncrement = getMidiNotePhaseIncrement(sNote);

where sNote is between 0 and 127

To access a note from one of the scales based on an analogInput you can use -

uint16_t unPhaseIncrement = getCMBluesPhaseIncrement(map(aInput,0,1024,0,CMBLUES_NOTES));

In the sample code above, we are using the map function to map an analog input value held in aInput the range of notes available in the C Minor Blues scale.

A Simple Test Synthesiser
At the end of this post is a very simple synth which uses the note tables and is based on the popular Auduino with one bonus feature. I have included the low pass filter from the Mozzi Arduino audio libraries. The Mozzi filter is better than the simple hardware filters I have tried and is very easy to add to a project.

In the test sketch the filter can be driven in two ways based on the position of an analog input. At the bottom end of the input range, the filter is controlled directly by the input, at the top end of the range, the filter is controlled by an oscillator, the frequency of the oscillator is  adjustable within this range.

To keep things simple, the test sketch uses the same hardware as an Auduino so if you want to have a play around with the mozzi filter or creating your own scales - load up and play away.

Mozzi

The Mozzi filter can be downloaded from http://sensorium.github.com/Mozzi/

The Mozzi sound samples are very good quality, the project implements a range of synthesizer building blocks and is something to take a closer look at.

Arduino Note Tables -

// Arduino Note Tables rcarduino.blogspot.com

#ifndef _NOTETABLES_
#define _NOTETABLES_

#include "avr/pgmspace.h"

#define MIDI_NOTES 128
// used to convert midi note numbers into the increments required to generate the note in the ISR
PROGMEM unsigned int midiNoteToWavePhaseIncrement[MIDI_NOTES] =
{
 66 // 0,8.18,66.98,66                C
,70 // 1,8.66,70.96,70
,75 // 2,9.18,75.18,75
,79 // 3,9.72,79.65,79
,84 // 4,10.30,84.38,84
,89 // 5,10.91,89.40,89
,94 // 6,11.56,94.72,94
,100 // 7,12.25,100.35,100
,106 // 8,12.98,106.32,106
,112 // 9,13.75,112.64,112
,119 // 10,14.57,119.34,119
,126 // 11,15.43,126.43,126
,133 // 12,16.35,133.95,133            C
,141 // 13,17.32,141.92,141
,150 // 14,18.35,150.35,150
,159 // 15,19.45,159.29,159
,168 // 16,20.60,168.77,168
,178 // 17,21.83,178.80,178
,189 // 18,23.12,189.43,189
,200 // 19,24.50,200.70,200
,212 // 20,25.96,212.63,212
,225 // 21,27.50,225.28,225
,238 // 22,29.14,238.67,238
,252 // 23,30.87,252.86,252
,267 // 24,32.70,267.90,267           C - lowest note used on rcarduino ribbon synth
,283 // 25,34.65,283.83,283
,300 // 26,36.71,300.71,300
,318 // 27,38.89,318.59,318
,337 // 28,41.20,337.53,337
,357 // 29,43.65,357.60,357
,378 // 30,46.25,378.87,378
,401 // 31,49.00,401.40,401
,425 // 32,51.91,425.27,425
,450 // 33,55.00,450.55,450
,477 // 34,58.27,477.34,477
,505 // 35,61.74,505.73,505
,535 // 36,65.41,535.80,535           C
,567 // 37,69.30,567.66,567
,601 // 38,73.42,601.42,601
,637 // 39,77.78,637.18,637
,675 // 40,82.41,675.07,675
,715 // 41,87.31,715.21,715
,757 // 42,92.50,757.74,757
,802 // 43,98.00,802.79,802
,850 // 44,103.83,850.53,850
,901 // 45,110.00,901.11,901
,954 // 46,116.54,954.69,954
,1011 // 47,123.47,1011.46,1011       C
,1071 // 48,130.81,1071.60,1071
,1135 // 49,138.59,1135.32,1135
,1202 // 50,146.83,1202.83,1202
,1274 // 51,155.56,1274.36,1274
,1350 // 52,164.81,1350.13,1350
,1430 // 53,174.61,1430.42,1430
,1515 // 54,185.00,1515.47,1515
,1605 // 55,196.00,1605.59,1605
,1701 // 56,207.65,1701.06,1701
,1802 // 57,220.00,1802.21,1802
,1909 // 58,233.08,1909.38,1909
,2022 // 59,246.94,2022.92,2022
,2143 // 60,261.63,2143.20,2143       C
,2270 // 61,277.18,2270.64,2270
,2405 // 62,293.66,2405.66,2405
,2548 // 63,311.13,2548.71,2548
,2700 // 64,329.63,2700.27,2700
,2860 // 65,349.23,2860.83,2860
,3030 // 66,369.99,3030.95,3030
,3211 // 67,392.00,3211.18,3211
,3402 // 68,415.30,3402.12,3402
,3604 // 69,440.00,3604.42,3604
,3818 // 70,466.16,3818.75,3818
,4045 // 71,493.88,4045.83,4045
,4286 // 72,523.25,4286.41,4286      C
,4541 // 73,554.37,4541.29,4541
,4811 // 74,587.33,4811.33,4811
,5097 // 75,622.25,5097.42,5097
,5400 // 76,659.26,5400.53,5400
,5721 // 77,698.46,5721.67,5721
,6061 // 78,739.99,6061.89,6061
,6422 // 79,783.99,6422.36,6422
,6804 // 80,830.61,6804.25,6804
,7208 // 81,880.00,7208.85,7208
,7637 // 82,932.33,7637.51,7637
,8091 // 83,987.77,8091.66,8091
,8572 // 84,1046.50,8572.82,8572     C
,9082 // 85,1108.73,9082.58,9082
,9622 // 86,1174.66,9622.66,9622
,10194 // 87,1244.51,10194.85,10194
,10801 // 88,1318.51,10801.07,10801
,11443 // 89,1396.91,11443.33,11443
,12123 // 90,1479.98,12123.79,12123
,12844 // 91,1567.98,12844.71,12844
,13608 // 92,1661.22,13608.50,13608
,14417 // 93,1760.00,14417.70,14417
,15275 // 94,1864.65,15275.02,15275
,16183 // 95,1975.53,16183.31,16183
,17145 // 96,2093.00,17145.63,17145     C
,18165 // 97,2217.46,18165.16,18165
,19245 // 98,2349.32,19245.31,19245
,20389 // 99,2489.01,20389.70,20389
,21602 // 100,2637.02,21602.14,21602
,22886 // 101,2793.83,22886.67,22886
,24247 // 102,2959.95,24247.58,24247
,25689 // 103,3135.96,25689.42,25689
,27216 // 104,3322.44,27216.99,27216
,28835 // 105,3520.00,28835.39,28835
,30550 // 106,3729.31,30550.04,30550
,32366 // 107,3951.06,32366.63,32366
,34291 // 108,4186.01,34291.26,34291    C
,36330 // 109,4434.92,36330.32,36330
,38490 // 110,4698.64,38490.65,38490
,40779 // 111,4978.03,40779.41,40779
,43204 // 112,5274.04,43204.25,43204
,45773 // 113,5587.65,45773.32,45773
,48495 // 114,5919.91,48495.14,48495
,51378 // 115,6271.92,51378.79,51378
,54433 // 116,6644.87,54433.96,54433
,57670 // 117,7040.00,57670.76,57670
,61100 // 118,7458.62,61100.07,61100
,64733 // 119,7902.13,64733.26,64733
,3046 // 120,8372.02,68582.53,3046      C
,7124 // 121,8869.84,72660.64,7124
,11445 // 122,9397.27,76981.30,11445
,16022 // 123,9956.06,81558.77,16022
,20872 // 124,10548.07,86408.50,20872
,26010 // 125,11175.30,91546.65,26010
,31454 // 126,11839.81,96990.28,31454
,31454 // 127,11839.81,96990.28,31454 // this is wrong, need to calculate correct value, even though at 8Khz its wrapping around on every tick
};

// Pentatonic scale
// C D E G A C
// to map to midi note

#define PENTATONIC_NOTES 54

// provides an index of pentatonic notes in the midi note table

PROGMEM unsigned char sPentatonicNotes[PENTATONIC_NOTES] =
{
  0,   2,  4,  7,  9,
  12, 14, 16, 19, 21,
  24, 26, 28, 31, 33,
  36, 38, 40, 43, 45,
  48, 50, 52, 55, 57,
  60, 62, 64, 67, 69,
  72, 74, 76, 79, 81,
  84, 86, 88, 91, 93,
  96, 98,100,103,105,
 108,110,112,115,117,
 120,122,124,127
};

// C Minor Blues scale
// C D# F F# G A# C
// to map to midi note

#define CMBLUES_NOTES 65

// provides an index of pentatonic notes in the midi note table

PROGMEM unsigned char sCMBluesNotes[CMBLUES_NOTES] =
{
  0,   3,  5,  6,  7, 10,  // 6
  12, 15, 17, 18, 19, 22,  // 12
  24, 27, 29, 30, 31, 34,  // 18
  36, 39, 41, 42, 43, 46,  // 24
  48, 51, 53, 54, 55, 58,  // 30
  60, 63, 65, 66, 67, 70,  // 36  
  72, 75, 77, 78, 79, 82,  // 42
  84, 87, 89, 90, 91, 94,  // 48
  96, 99,101,102,103,106,  // 54
 108,111,113,114,115,118,  // 60
 120,123,125,126,127       // 65
};


unsigned int getMidiNotePhaseIncrement(unsigned char sNote)
{
 if(sNote >= MIDI_NOTES)
 {
   sNote = (MIDI_NOTES - 1);
 }

 return pgm_read_word(midiNoteToWavePhaseIncrement + (sNote));
}
unsigned int getPentatonicPhaseIncrement(unsigned char sPentatonicNote)
{
 if(sPentatonicNote >= PENTATONIC_NOTES)
  sPentatonicNote = (PENTATONIC_NOTES - 1);

 uint8_t sMidiIndex = pgm_read_byte(sPentatonicNotes + sPentatonicNote);

 return pgm_read_word(midiNoteToWavePhaseIncrement + sMidiIndex);
}

unsigned int getCMBluesPhaseIncrement(unsigned char sCMBluesNote)
{
 if(sCMBluesNote >= CMBLUES_NOTES)
  sCMBluesNote = (CMBLUES_NOTES - 1);
 
 uint8_t sMidiIndex = pgm_read_byte(sCMBluesNotes + sCMBluesNote);
 return pgm_read_word(midiNoteToWavePhaseIncrement + sMidiIndex);
}

#endif

The very simple, very lo-fi synth

// Very Lo Fi synth with Mozzi filter rcarduino.blogspot.com

#include "NoteTables.h"
#include "lowpass.h"

#define PWM_OUT_REG OCR2B

#define KEY_COUNT 20
#define KEY_WIDTH (1024/KEY_COUNT)

class CAudio
{
  public:
   CAudio(){}
   static void begin(uint8_t bStopTimer0Interrupts)
   {
     // Setup timer 1
     TCCR1A=0x0;          // set the timer prescaler to 8 = 16/8 = 2MHz
     TCCR1B=0x02;          // set the timer prescaler to 8 = 16/8 = 2MHz
     TIMSK1 |= (1<<OCIE1A);   // Enable output compare match interrupt on OCR1A
 
     TCCR2A=0B10110011;                                    //-8 bit audio PWM
     //TCCR0A=0x83;          // Set timer waveform generation mode to FAST PWM, clear OC0A On match, set at bottom - OC0A = digital pin 6.
     TCCR2B=0x01;          // Set to clock frequency, no prescaler

     pinMode(3,OUTPUT);
    
     if(bStopTimer0Interrupts)
     {
      // stops timer0 which triggers interrupts for the millis and micros functions
      // if we stop the timer, we get better audio, but loose millis and micros.
      TIMSK0 &= (~((1 << OCIE0A)| (1 << OCIE0B)));
     }
   }
};


#define SAW 0
#define SQUARE 1
#define RAMP 2


class COscilator
{
public:
  void setWaveform(uint8_t sWaveform)
  {
    m_sWaveform = sWaveform;
  }
  void setPhaseIncrement(uint16_t unPhaseIncrement)
  {
    m_unPhaseIncrement = unPhaseIncrement;
  }
  uint8_t hasTriggered()
  {
    return m_unPhaseAccumulator < m_unPhaseIncrement;
  }
  void trigger()
  {
    m_unPhaseAccumulator = 0;
  }
  void updatePhase()
  {
    m_unPhaseAccumulator += m_unPhaseIncrement;
  }
  uint8_t getSample()
  {
    uint8_t sSample = m_unPhaseAccumulator>>8;
    switch(m_sWaveform)
    {
      case SQUARE:
       (sSample >= 127) ? sSample = 255 : sSample = 0;
      break;
      case RAMP:
       sSample = ~sSample;
      break;
    }
    return sSample;
  }
protected:
  uint16_t m_unPhaseIncrement;
  uint16_t m_unPhaseAccumulator;
  uint8_t m_sWaveform;
};

COscilator Osc1,Osc2,LFO,LFOFilter;

uint16_t getPhaseIncrement(uint16_t aInput,uint8_t sOffset)
{
  uint8_t sKey = aInput/KEY_WIDTH;

  // find where on the ribbon the key starts 
  uint16_t unKeyStart = sKey * KEY_WIDTH;
 
  // find where on the ribbon the key ends
  uint16_t unKeyEnd = (sKey+1) * KEY_WIDTH;
 
  // map where the current input sits between the key start and key end to a frequency that sits at the same point between
  // the frequency of the key and the frequency of the next key
 
  return map(aInput,unKeyStart,unKeyEnd,getMidiNotePhaseIncrement(sKey+sOffset),getMidiNotePhaseIncrement(sKey+1+sOffset));
}

LowPassFilter lpFilter;
uint16_t gFilter;

void setup()
{
  Serial.begin(9600);
  CAudio::begin(true); 

  Osc1.setWaveform(RAMP);
  Osc2.setWaveform(RAMP);
  LFO.setWaveform(SAW);
  LFOFilter.setWaveform(RAMP);
}

void loop()
{
  uint16_t aInput = analogRead(1);
  if(aInput >= 1021)
  {
    Osc1.setPhaseIncrement(0);
    Osc2.setPhaseIncrement(0);
    LFO.setPhaseIncrement(0);
    LFOFilter.trigger();
  }
  else
  {
    uint8_t sOffset = 20;//((analogRead(1) >> 3)/12)*12;
    uint16_t unPhaseIncrement = getCMBluesPhaseIncrement(map(aInput,0,1024,0,CMBLUES_NOTES));
    Osc1.setPhaseIncrement(analogRead(2)<<2);
    Osc2.setPhaseIncrement(analogRead(3)<<5);

    //lpFilter.setCutoffFreq(analogRead(4)>>2);
    lpFilter.setResonance(analogRead(5)>>2);
   
    LFO.setPhaseIncrement(unPhaseIncrement);   
   
    uint16_t unFilter = analogRead(4);
    if(unFilter < 512)
    {
     LFOFilter.setPhaseIncrement(0);
     unFilter>>=1;
    
     uint8_t sreg = SREG;
     cli();
     gFilter = unFilter;
     SREG = sreg;
    }
    else
    {
     uint8_t sreg = SREG;
     cli();
     gFilter = unFilter;
     SREG = sreg;
    
     LFOFilter.setPhaseIncrement((gFilter-512));
    }
  }
}



#define DELAY_LENGTH 1024

// iterate the grains and LFO
SIGNAL (TIMER1_COMPA_vect)
{
  static uint8_t sDelayBuffer[DELAY_LENGTH];
  static uint16_t sDelayIndex;

  sDelayIndex++;
  if(sDelayIndex >= DELAY_LENGTH)
  {
    sDelayIndex = 0;
  }
 
  OCR1A += 125;

  Osc1.updatePhase();
  Osc2.updatePhase();
  LFO.updatePhase();
  LFOFilter.updatePhase();
 
  if(LFO.hasTriggered())
  {
    Osc1.trigger();
    Osc2.trigger();
  }
   
  if(gFilter < 512)
  {
    lpFilter.setCutoffFreq(gFilter>>1);
  }
  else
  {
   lpFilter.setCutoffFreq(LFOFilter.getSample());
  }
 
  uint16_t nOutput = lpFilter.next((Osc1.getSample()>>1)+(Osc2.getSample()>>1));
  if(nOutput > 255)
   nOutput = 255;
  PWM_OUT_REG = nOutput;

  // not being mixed in to output but easy to add, see rcarduino.blogspot.com
  sDelayBuffer[sDelayIndex] = PWM_OUT_REG;
}




And don't forget the Mozzi filter 'lowpass.h' from https://github.com/sensorium/Mozzi

Duane B

Tuesday, February 5, 2013

RCArduino Libraries FAQ

A central point for questions and help on using the RCArduino Libraries.

The RCArduino Libraries provides servo output, PPM input and individual RC Channel input.

Its been created to reduce servo jitter in projects that need to both read and output RC Channels. If you want smooth focus for camera work, animatronics that don't twitch around or fast smooth control of your RC Vehicle, read on.

1) Why a new servo and RC library ?

The standard Arduino Servo library is very good, its well documented and well supported, it also has some challenges when used in projects that read incoming RC Signals. The RCArduino Servo library addresses these challenges to provide smoother output. This is achieved through faster interrupt service routines which reduce clashes and glitches.

A major improvement is also acheived by not resetting timer1 as the standard servo library can. This allows us to use timer1 for timing incoming RC Signals as well as generating the servo outputs.



An detailed explanation can be found here -

http://rcarduino.blogspot.com/2012/11/how-to-read-rc-channels-rcarduinofastlib.html

2) Can I use the library to read PPM signals and output RC Channels ?

Yes, an example is provided here, but come back here for more documentation if you need it.

http://rcarduino.blogspot.com/2012/11/how-to-read-rc-receiver-ppm-stream.html

3) Can I use the library to read individual RC Channels and output RC Channels ? 

Yes, an example is provided here, but come back here for more documentation and if your planning to use a mega look out for an update with an example using the six Arduino Mega interrupts instead of the pinchangeint library which is used in the UNO version to provide more than the standard two interrupts.

http://rcarduino.blogspot.com/2012/11/how-to-read-rc-channels-rcarduinofastlib.html

Some background on the pinchangeint library if you need more than 2 interrupts on an UNO or more than 6 on a Mega -

http://rcarduino.blogspot.com/2012/03/need-more-interrupts-to-read-more.html
How do we use the library ?

The RCArduino library uses arrays internally to record the channel input and output values. This is slightly more efficient than the linked lists used by the servo library however its also a little less user friendly. Unlike the Servo library which allows us to attach servos at runtime, the RCArduino library requires that we set the number of servos at compile time. Similarly if you plan to use the PPM Input capability you will need to specify the number of input channels at compile time.

To make this as simple as possible, two #define's are used  in the RCArduinoFastLib.h file.

To set the number of input channels if your going to read a PPM Stream change the number here -
// Change to set the number of channels in PPM Input stream
#define RC_CHANNEL_IN_COUNT 3

To set the number of servos in your project change the number here -

// Change to set the number of servos/ESCs
#define RC_CHANNEL_OUT_COUNT 4

Set the RC_CHANNEL_OUT_COUNT to one more servo than you need, the library is able to generate higher refresh rate signals than the standard Servo library which forces a 50Hz refresh rate. To set the refresh rate we create an additional entry in the servo array. We use the 'setFrameSpace' function on this last entry to set the frame space which sets the refresh rate. While this is initially an awkward additional step, it also gives us a lot more flexibility, for example we can use the library to drive high refresh rates upto 500Hz.

To generate a 50Hz refresh rate which is equivalent to the standard servo library we can use the following calculation -

1/50 = 20,000 microseconds
frame space = 20,000 - (2,000 * number of servos)

An Example -
To drive 8 servos we would set RC_CHANNEL_OUT_COUNT to (8 + 1)= 9. Remember the additional entry is for our framespace.

#define RC_CHANNEL_OUT_COUNT 9

we can then calculate

frame space = 20,000 - (2,000 * (RC_CHANNEL_OUT_COUNT - 1) )

Note we take away 1 so that we do not include the framespace in the framespace calculation

frame space = 20,000 - (2,000 * 8)
frame space = 4,000

So to set the frame space to give a refresh rate of 50 Hz for 8 servos we call

CRCArduinoFastServos::setFrameSpaceA(SERVO_FRAME_SPACE,4000);

Why the maths ?
 
While this is more complex than the standard servo library it is also more flexible and provides a quick and easy way to drive the high frequency ESCs and Servos that are becoming more popular.

If your unsure about the frame space calculation or would like to try the RCArduino libraries with 500Hz ESCs and Servos use the comments or contact me Duane B through the Arduino forum.

I am also considering automating the calculation through a convenience function in a future release, stay tuned.

Now that we have that out of the way, its all very easy.

Attaching A Servo
To attach a servo we call the attach function supply the index of the servo we want to attach (they start at 0, remember they are held in an array) and the Arduino pin we want to attach it to.

CRCArduinoFastServos::attach(SERVO_THROTTLE/* Servo index */,THROTTLE_OUT_PIN /* the pin we want it attached to */);

The final thing we need to do inside out set up function is to call the begin function, this initialises timer1 for use by the library and attaches the interrupt routines.

Understanding The Library and Reading and Writing Channels

The key to understanding and using the library is to be aware of the RC_CHANNEL_IN_COUNT and RC_CHANNEL_OUT_COUNT settings and also to understand that the channel values are stored in arrays which are accessed by the index you supply when calling the read and write functions -

CRCArduinoFastServos::writeMicroseconds(SERVO_STEERING /* 0 based index into servo array */,unSteeringIn);
CRCArduinoPPMChannels::getChannel(SERVO_STEERING /* 0 based index into PPM input array */);

You can create your own indexes into the arrays by defining them in your sketch, the number of input channels does not need to match the number of output channels. You can also have a different index for an input channel that you use for the output channel, in fact you will sometimes find that channels within the PPM stream are in a different order than the printed channel numbers on your receiver.

To get started try the examples provided -

http://rcarduino.blogspot.com/2012/11/how-to-read-rc-channels-rcarduinofastlib.html

http://rcarduino.blogspot.com/2012/11/how-to-read-rc-receiver-ppm-stream.html

For any help, use the comments section below

Duane B

Sunday, January 13, 2013

New Arduino Board - Esplora

A new Arduino Board has recently been announced - the Arduino Esplora.

Its a very different proposition to previous Arduino boards in that it is hardwired with a large number of built in sensors -


The Esplora has the following on-board inputs and outputs :


  • Analog joystick with central push-button two axis (X and Y) and a center pushbutton.
  • 4 push-buttons laid out in a diamond pattern.
  • Linear potentiometer slider near the bottom of the board.
  • Microphone for getting the loudness (amplitude) of the surrounding environment.
  • Light sensor for getting the brightness.
  • Temperature sensor reads the ambient temperature
  • Three-axis accelerometer measures the board's relation to gravity on three axes (X, Y, and Z)
  • Buzzer can produce square-waves.
  • RGB led bright LED with Red Green and Blue elements for color mixing.
  • 2 TinkerKit Inputs to connect the TinkerKit sensor modules with the 3-pin connectors.
  • 2 TinkerKit Outputs to connect the TinkerKit actuator modules with the 3-pin connectors.
  • TFT display connector connector for an optional color LCD screen, SD card, or other devices that use the SPI protocol. 
Information reposted from the official Arduino site where full details of the Arduino Esplora can be found -

http://arduino.cc/en/Main/ArduinoBoardEsplora


The TFT Display connector will be interesting - adding a display to the array of build in inputs will make the Esplora and ideal platform for 8-bit Arcade clones - something that will feature on rcarduino as and when an Esplora arrives.

To get up and running quickly with the new inputs and outputs, the Esplora has its own set of easy to use libraries.

Esplora libraries -

readSlider() 
readLightSensor() 
readTemperature() 
readMicrophone() 
readJoystickSwitch() 
readJoystickButton() 
readAccelerometer() 
readButton() 
readJoystickX() 
readJoystickY() 
writeRGB() 
writeRed() 
writeGreen() 
writeBlue() 
readRed() 
readGreen() 
readBlue() 
tone() 
noTone() 

reposted from the official Arduino website - http://arduino.cc/en/Reference/EsploraLibrary
 

To get a flavour of what can be achieved out of the box with the new Arduino Esplora, check out the example sketches

EsploraBlink : Blink the Esplora's RGB LED
EsploraAccelerometer : Read the values from the accelerometer
EsploraJoystickMouse : Use the Esplora's joystick to control the cursor on your computer EsploraLedShow : Use the Joystick and slider to create a light show with the LED
EsploraLedShow2 : Use the Esplora's microphone, linear potentiometer, and light sensor to change the color of the onboard LED.
 EsploraLightCalibrator : Read the values from the accelerometer
 EsploraMusic : Make some music with the Esplora
 EsploraSoundSensor : Read the values from the Esplora's microphone
 EsploraTemperatureSensor : Read the temperature sensor and get the temperature in in Farhenheit or Celsius.

Experts
EsploraKart : Use the Esplora as a controller to play a kart racing game.
EsploraTable : Print the Esplora sensor information to a table format.
 EsploraRemote : Connect the Esplora to Processing and control the outputs.
 EsploraPong : Play Pong with the Esplora using Processing.

For the original list see the official Arduino website link - http://arduino.cc/en/Reference/EsploraLibrary
Stay Tuned Duane B

Saturday, December 29, 2012

New Year Projects

A quick teaser of some of the projects and components that will be featured on RC Arduino in the new year.

TFT Screen, 4 * Bi Color LED Matrix, Analogue Joystick, Microphone

Analogue Joystick, Touch Screen Overlay, Soft Pot.

A small collection of mini synths - Korg Monotron, Dubreq Stylophone, Bleep Labs Nebulophone.

RC Car Active Power Distribution
I am also planning to bring this monster back to life -
 Its two RC Cars joined together to make a twin motor 4wd car with independent throttle control to the front and rear axles. The result is a small car with way too much power, its very much in the spirit of a Group B rally car or the twin motored Suzuki Escudo rally car.

Stay Tuned

DUane B