Showing posts with label Arduino Servo Example. Show all posts
Showing posts with label Arduino Servo Example. Show all posts

Sunday, October 28, 2012

Arduino Serial Servos - 20 Servos, 4 Pins, High Refresh Rates

In a recent post we looked at a very simple technique for controlling 10 servos with two Arduino pins using only one 30 cent chip.

I have since extended the library for two new functions -

1) Control upto 20 servos using only Four pins and two 30 cent chips
2) Variable refresh rate to maximise stability and holding torque

10 More Servos

The first addition - An additional 10 more servos has been achieved by duplicating the original libraries interaction with Timer1, OCR1A and the 4017 Chip onto OCR1B and a second 4017 chip. To put it another way each of the timer1 output compare registers is driving its own bank of ten servos through its own 4017 decade counter.

Refer to the original post here - http://rcarduino.blogspot.com/2012/08/arduino-serial-servos.html
For a description of the basic scheme.


Variable Refresh Rate
This second capability has always been a function of the library but is now more accessible.


The library blindly cycles through each bank of ten servos generating the required pulse. When the library gets to the last servo it points itself back to the first servo and starts again.



Remember, this is interrupt driven and is happening in the background, your main code is not effected.

A rough calculation suggests that driving 20 servos with this library will take a fraction more than half of one percent of Arduino processing power leaving you free to fill the other 99.5%


As the library continually cycles through the two banks of servos, a reduction in the number of servos will reduce the time taken for a cycle and therefore increase the servo refresh rate.

If we assume an average servo pulse of 1500ms, the following average refresh rates apply -
 
Servos In Each Bank Average Servo Position Time To Cycle Through Servos Average Refresh Rate
1 1500 1500 666.6666667
2 1500 3000 333.3333333
3 1500 4500 222.2222222
4 1500 6000 166.6666667
5 1500 7500 133.3333333
6 1500 9000 111.1111111
7 1500 10500 95.23809524
8 1500 12000 83.33333333
9 1500 13500 74.07407407
10 1500 15000 66.66666667


I have highlighted the values for 3 and 4 servos as 250Hz seems to be a common request for the refresh rates of ESCs used in quadcopters.

What advantage do we get by increasing the servo refresh rate ? in the case of quadcopters faster refresh can improve stability.

In the case of robotics, higher refresh rates can increase holding torque and again improve stability.

To drive 8 servos with a high refresh rate a fast and simple option is to use both banks of servos and drive only four servos on each bank.


You may already be using a variety of refresh rates without knowing, some of my RC Car transmitters operate at 50Hz, others at 60Hz and one at 91Hz - I have been mixing and matching equipment for years and only discovered the different refresh rates by plugging my receivers into an Arduino.


The refresh rate is determined by the total pulse width for each bank of servos - four servos set to 1 microsecond will be cycled through 250 (1/(4*0.001) times a second, if the same four servos are set to 2 microseconds we slow down to 125 (1/(4*.002) times a second.

Shouldn't I add code to fix a constant refresh rate ? 

My thinking is that if we can refresh faster and take advantage of the increased stability and hold, why would we introduce code and complexity to prevent this ? If you would prefer a fixed refresh rate, keep a few more servos than you need and use them to adjust the total.

The Code - 
 
Here is the new library and test sketch for you to try. The test sketch sets up the servos with increasing pulse widths, an interrupt service routine is provided which allows you to test the pulse width on any of the servo outputs.

To add or remove the second bank of servos driven by OCR1B, refer to the following line in the RCArduinoSerialServos.h file


// COMMENT OR UNCOMMENT THIS LINE TO ENABLE THE SECOND BANK OF SERVOS
#define MORE_SERVOS_PLEASE 1
 

 To change the number of servos driven by the library in order to increase the refresh rate refer to the following line in the RCArduinSerialServos.h file -

// THIS IS FOR ADVANCED USERS ONLY -
//
// Change the channel out count to adjust the frequency,
//
// BEFORE DOING THIS VERIFY THAT YOUR SERVOS AND ESCS ARE COMPATIBLE
// WITH HIGHER AND VARIABLE REFRESH RATES
//
// The library blindly pulses all ten servos one and after another
// If you change the RC_CHANNEL_OUT_COUNT to 4 servos, the library will pulse them more frequently than
// it can ten -
// 10 servos at 1500us = 15ms = 66Hz
// 4 Servos at 1500us = 6ms = 166Hz
// if you wanted to go even higher, run two servos on each 4017
// 2 Servos at 1500us = 3ms = 333Hz
#define RC_CHANNEL_OUT_COUNT 4

RCArduinoSerialServos.h

**************************************************************************************
// RCArduinoChannels by DuaneB is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
//
// http://rcarduino.blogspot.com
//
*****************************************************************************************************************************/


#include "Arduino.h"

// COMMENT OR UNCOMMENT THIS LINE TO ENABLE THE SECOND BANK OF SERVOS
#define MORE_SERVOS_PLEASE 1
// the second bank of servos uses pin 10 (clock) and 13 (reset) to drive an additional ten servos.
// the first bank uses pin 9 (clock) and 12 (reset)

// THIS IS FOR ADVANCED USERS ONLY -
//
// Change the channel out count to adjust the frequency,
//
// BEFORE DOING THIS VERIFY THAT YOUR SERVOS AND ESCS ARE COMPATIBLE
// WITH HIGHER AND VARIABLE REFRESH RATES
//
// The library blindly pulses all ten servos one and after another
// If you change the RC_CHANNEL_OUT_COUNT to 4 servos, the library will pulse them more frequently than
// it can ten -
// 10 servos at 1500us = 15ms = 66Hz
// 4 Servos at 1500us = 6ms = 166Hz
// if you wanted to go even higher, run two servos on each 4017
// 2 Servos at 1500us = 3ms = 333Hz
#define RC_CHANNEL_OUT_COUNT 4

#if defined (MORE_SERVOS_PLEASE)
#define RCARDUINO_MAX_SERVOS (RC_CHANNEL_OUT_COUNT*2)
#else
#define RCARDUINO_MAX_SERVOS (RC_CHANNEL_OUT_COUNT)
#endif

// Minimum and Maximum servo pulse widths, you could change these,
// Check the servo library and use that range if you prefer
#define RCARDUINO_SERIAL_SERVO_MIN 1000
#define RCARDUINO_SERIAL_SERVO_MAX 2000

//////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// CRCArduinoSerialServos
//
// A class for generating signals in combination with a 4017 Counter
//
// Output upto 10 Servo channels using just digital pins 9 and 12
// 9 generates the clock signal and must be connected to the clock pin of the 4017
// 12 generates the reset pulse and must be connected to the master reset pin of the 4017
//
// The class uses Timer1, as this prevents use with the servo library
// The class uses pins 9 and 12
// The class does not adjust the servo frame to account for variations in pulse width,
// on the basis that many RC transmitters and receivers designed specifically to operate with servos
// output signals between 50 and 100hz, this is the same range as the library
//
// Use of an additional pin would provide for error detection, however using pin 12 to pulse master reset
// at the end of every frame means that the system is essentially self correcting
//
// Note
// This is a simplified derivative of the Arduino Servo Library created by Michael Margolis
// The simplification has been possible by moving some of the flexibility provided by the Servo library
// from software to hardware.
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////

class CRCArduinoSerialServos
{
public:
    CRCArduinoSerialServos();

    // configures timer1
    static void begin();

    // called by the timer interrupt service routine, see the cpp file for details.
    static void OCR1A_ISR();
   
#if defined(MORE_SERVOS_PLEASE)
    static void OCR1B_ISR();
#endif

    // called to set the pulse width for a specific channel, pulse widths are in microseconds - degrees are for wimps !
    static void writeMicroseconds(uint8_t nChannel,uint16_t nMicroseconds);

protected:
    // this sets the value of the timer1 output compare register to a point in the future
    // based on the required pulse with for the current servo
    static void setOutputTimerForPulseDurationA();
   
    // Records the current output channel values in timer ticks
    // Manually set by calling writeChannel, the function adjusts from
    // user supplied micro seconds to timer ticks
    volatile static uint16_t m_unChannelOutA[RC_CHANNEL_OUT_COUNT];
    // current output channel, used by the timer ISR to track which channel is being generated
    static uint8_t m_sCurrentOutputChannelA;
   
#if defined(MORE_SERVOS_PLEASE)
    // Optional channel B for servo number 10 to 19
    volatile static uint16_t m_unChannelOutB[RC_CHANNEL_OUT_COUNT];
    static uint8_t m_sCurrentOutputChannelB;
    static void setOutputTimerForPulseDurationB();
#endif

    // two helper functions to convert between timer values and microseconds
    static uint16_t ticksToMicroseconds(uint16_t unTicks);
    static uint16_t microsecondsToTicks(uint16_t unMicroseconds);
};

RCArduinoSerialServos.cpp
#include "RCArduinoSerialServos.h"

/*----------------------------------------------------------------------------------------

This is essentially a derivative of the Arduino Servo Library created by Michael Margolis

As the technique is very similar to the Servo class, it can be useful to study in order
to understand the servo class.

What does the library do ? It uses a very inexpensive and common 4017 Counter IC
To generate pulses to independently drive up to 10 servos from two Arduino Pins

As previously mentioned, the library is based on the techniques used in the Arduino Servo
library created by Michael Margolis. This means that the library uses Timer1 and Timer1 output
compare register A.

OCR1A is linked to digital pin 9 and so we use digital pin 9 to generate the clock signal
for the 4017 counter.

Pin 12 is used as the reset pin.

*/

// Timer1 Output Compare A interrupt service routine
// call out class member function OCR1A_ISR so that we can
// access out member variables
ISR(TIMER1_COMPA_vect)
{
    CRCArduinoSerialServos::OCR1A_ISR();
}

void CRCArduinoSerialServos::OCR1A_ISR()
{
    // If the channel number is >= 10, we need to reset the counter
    // and start again from zero.
    // to do this we pulse the reset pin of the counter
    // this sets output 0 of the counter high, effectivley
    // starting the first pulse of our first channel
  if(m_sCurrentOutputChannelA >= RC_CHANNEL_OUT_COUNT)
  {
    // reset our current servo/output channel to 0
    m_sCurrentOutputChannelA = 0;

    // pulse reset on the counter - we set it high here
    PORTB|=16;

    // set the duration of the output pulse
    CRCArduinoSerialServos::setOutputTimerForPulseDurationA();

    // finish the reset pulse - we set it low here
    PORTB^=16;
 }
 else
 {
  // pulse the clock pin high
    PORTB|=2;

    // set the duration of the output pulse
    CRCArduinoSerialServos::setOutputTimerForPulseDurationA();

    // finish the clock pulse - set it back to low
    PORTB^=2;
  }
    // done with this channel so move on.
    m_sCurrentOutputChannelA++;
}
// After we set an output pin high, we need to set the timer to comeback for the end of the pulse
void CRCArduinoSerialServos::setOutputTimerForPulseDurationA()
{
  OCR1A = TCNT1 + m_unChannelOutA[m_sCurrentOutputChannelA];
}

#if defined(MORE_SERVOS_PLEASE)
// Timer1 Output Compare B interrupt service routine
// call out class member function OCR1B_ISR so that we can
// access out member variables
ISR(TIMER1_COMPB_vect)
{
    CRCArduinoSerialServos::OCR1B_ISR();
}

void CRCArduinoSerialServos::OCR1B_ISR()
{
    // If the channel number is >= 10, we need to reset the counter
    // and start again from zero.
    // to do this we pulse the reset pin of the counter
    // this sets output 0 of the counter high, effectivley
    // starting the first pulse of our first channel
  if(m_sCurrentOutputChannelB >= RC_CHANNEL_OUT_COUNT)
  {
    // reset our current servo/output channel to 10 -
    // Note that 10 is the first servo on output compare B
    m_sCurrentOutputChannelB = 0;

    // pulse reset on the counter - we set it high here
    PORTB|=32;

    // set the duration of the output pulse
    CRCArduinoSerialServos::setOutputTimerForPulseDurationB();

    // finish the reset pulse - we set it low here
    PORTB^=32;
 }
 else
 {
  // pulse the clock pin high
    PORTB|=4;

    // set the duration of the output pulse
    CRCArduinoSerialServos::setOutputTimerForPulseDurationB();

    // finish the clock pulse - set it back to low
    PORTB^=4;
  }
    // done with this channel so move on.
    m_sCurrentOutputChannelB++;
}

// After we set an output pin high, we need to set the timer to comeback for the end of the pulse
void CRCArduinoSerialServos::setOutputTimerForPulseDurationB()
{
  OCR1B = TCNT1 + m_unChannelOutB[m_sCurrentOutputChannelB];
}
#endif

// updates a channel to a new value, the class will continue to pulse the channel
// with this value for the lifetime of the sketch or until writeChannel is called
// again to update the value
void CRCArduinoSerialServos::writeMicroseconds(uint8_t nChannel,uint16_t unMicroseconds)
{
    // dont allow a write to a non existent channel
    if(nChannel > RCARDUINO_MAX_SERVOS)
        return;

  // constraint the value just in case
  unMicroseconds = constrain(unMicroseconds,RCARDUINO_SERIAL_SERVO_MIN,RCARDUINO_SERIAL_SERVO_MAX);

#if defined(MORE_SERVOS_PLEASE)
  if(nChannel >= RC_CHANNEL_OUT_COUNT)
  {
    // disable interrupts while we update the multi byte value output value
    uint8_t sreg = SREG;
    cli();
     
    m_unChannelOutB[nChannel-RC_CHANNEL_OUT_COUNT] = microsecondsToTicks(unMicroseconds);

    // enable interrupts
    SREG = sreg;
    return;
  }
#endif
 
  // disable interrupts while we update the multi byte value output value
  uint8_t sreg = SREG;
  cli();
 
  m_unChannelOutA[nChannel] = microsecondsToTicks(unMicroseconds);

  // enable interrupts
  SREG = sreg;
}

uint16_t CRCArduinoSerialServos::ticksToMicroseconds(uint16_t unTicks)
{
    return unTicks / 2;
}

uint16_t CRCArduinoSerialServos::microsecondsToTicks(uint16_t unMicroseconds)
{
 return unMicroseconds * 2;
}

void CRCArduinoSerialServos::begin()
{
    // set the pins we will use for channel A (OCR1A) as outputs
    pinMode(9,OUTPUT); // clock uses OCR1A and should not be changed
    pinMode(12,OUTPUT); // reset, if you really needed to, you could change this, but remember to change it in the ISR as well

    // pulse reset
    digitalWrite(12,HIGH);
    digitalWrite(12,LOW);

#if defined (MORE_SERVOS_PLEASE)

    // set the pins we will use for channel B (OCR1B) as outputs
    pinMode(10,OUTPUT); // clock uses OCR1B and should not be changed
    pinMode(13,OUTPUT); // reset, if you really needed to, you could change this, but remember to change it in the ISR as well

    // pulse reset
    digitalWrite(12,HIGH);
    digitalWrite(12,LOW);

#endif

    TCNT1 = 0;              // clear the timer count  

    // Initilialise Timer1
    TCCR1A = 0;             // normal counting mode
    TCCR1B = 2;     // set prescaler of 64 = 1 tick = 4us

    // ENABLE TIMER1 OCR1A INTERRUPT to enabled the first bank (A) of ten servos
    TIFR1 |= _BV(OCF1A);     // clear any pending interrupts;
    TIMSK1 |=  _BV(OCIE1A) ; // enable the output compare interrupt 

#if defined(MORE_SERVOS_PLEASE)

    // ENABLE TIMER1 OCR1B INTERRUPT to enable the second bank (B) of 10 servos
    TIFR1 |= _BV(OCF1B);     // clear any pending interrupts;
    TIMSK1 |=  _BV(OCIE1B) ; // enable the output compare interrupt 

#endif

    OCR1A = TCNT1 + 4000; // Start in two milli seconds
}

// See the .h file
volatile uint16_t CRCArduinoSerialServos::m_unChannelOutA[RC_CHANNEL_OUT_COUNT];
uint8_t CRCArduinoSerialServos::m_sCurrentOutputChannelA;

#if defined(MORE_SERVOS_PLEASE)

volatile uint16_t CRCArduinoSerialServos::m_unChannelOutB[RC_CHANNEL_OUT_COUNT];
uint8_t CRCArduinoSerialServos::m_sCurrentOutputChannelB;

#endif


 RCArduinoSerialServos sketch


// RCArduinoSerialServos by DuaneB is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License.
// Based on a work at rcarduino.blogspot.com.
#include <RCArduinoSerialServos.h>

volatile uint32_t ulRiseTime;
volatile uint32_t ulPulseWidth;

void setup()
{
  Serial.begin(9600);
  Serial.println("RCArduinoSerialServos");
 
  // set the channels
  for(uint16_t nChannel = 0;nChannel < RCARDUINO_MAX_SERVOS;nChannel++)
  {
    CRCArduinoSerialServos::writeMicroseconds(nChannel,1000+(nChannel*50));
  }

  CRCArduinoSerialServos::begin();
 
  attachInterrupt(0,calcPulse,CHANGE);
}

void loop()
{
  delay(10);

  if(ulPulseWidth != 0)
  {
    // disable interrupts so that our pulse value does not get overwritten while we try and read it
    uint8_t sReg = SREG;
    cli();
   
    // take a local copy of the pulse witdth so that we can reenable interrupts as soon as possible
    uint32_t ulLocalPulseWidth = ulPulseWidth;
   
    // clear the pulse width so that we will only pick up new values written by calcPulse rather
    // than keep printing old values.
    ulPulseWidth = 0;
   
    // turn interrupts back on
    SREG = sReg;
   
    // print the pulse width
    Serial.println(ulLocalPulseWidth);
  }
 
}

// Read pulse width applied to digital pin 2 (interrupt 0)
void calcPulse()
{
  if(digitalRead(2))
  {
    ulRiseTime = micros();
  }
  else
  {
    ulPulseWidth = micros()- ulRiseTime;
  }
}










Friday, August 17, 2012

Arduino Serial Servos

Creative Commons License
RCArduinoSerialServos by DuaneB is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License.
Based on a work at rcarduino.blogspot.com.


It is possible to drive a bank of upto 10 independently controlled servos using just two Arduino pins. The concept can easily be expanded to 20 servos using only four pins.

There is very little that is new in the world and this technique definitely isn't new but here is my explanation and a ready made library and test sketch for you to try.

If you have read any of the previous posts on the RCArduino blog you will know that a servo is controlled using a pulse which is between 1000 and 2000 microseconds long.

Its all here if you need a refresher -
http://rcarduino.blogspot.com/2012/01/how-to-read-rc-receiver-with.html

At its most basic a pulse is simply setting a pin high and then setting it low again. The Servo library created by Michael Margolis does this in software, its well know, well supported and a very flexible library.

Here is a quick overview of the Servo Library
http://rcarduino.blogspot.com/2012/01/can-i-control-more-than-x-servos-with.html

Flexibility comes at a cost.

If you were to review the source code of the servo library you would see that a lot of the code is there to give users the choice of which pins to use. By moving this flexibility to hardware we can reduce the code required to generate servo signals and also reduce the Arduino resources to just two pins for each bank of ten servos. The code is also much small and faster as a result.

How does it work ?
The RCArduinoSerialServo library relies on a 4017 decade counter - its an incredibly simple chip that is often used in introductory courses to create LED Chasers.

Sample LED Chaser Using 4017 - The first line represent the clock signal, lines O0 to O9 represent the 4017 outputs. The clock switches the outputs on in sequence giving an LED chaser effect.



The chip has 10 outputs which are switched on in sequence based on a clock signal. This is very similar to what the Arduino Servo Library does in software, it sets the first servo pin high, then sets a timer to come back and set the pin low to end the pulse, at the same time, its sets the next servo pin high to begin that servos pulse, then sets the timer to come back again to end this new pulse and begin the next one.

We can achieve the same result using the 4017 Decade counter and only two Arduino pins. The pin we are most interested in is the clock pin of the 4017 Counter. When we pulse the clock pin, the 4017 will switch the current pin low and set the next pin high - effectively ending the current servo pulse and beginning a new one - just like the hardware LED Chaser above and the Servo Library does in software.

In order to control servos all we need to do is generate a clock pulse based on the required servo pulse durations.

Serial Servos - By controlling the space between clock pulses, we can control the pulse duration for each of 10 channels using only 1 Arduino pin.




Taking a closer look at the clock pulse - 

Here we can clearly see that the clock pulse itself is very short, around 1 millionth of a second. However by varying the time between clock pulses we can control the time that each channel is high - the channel pulse width. 

The second and third clock pulse are clearly closer together and this can be seen in the narrow pulse width of channel O1 and also the servo angle, in the previous image.




Here is the Timer1 Output compare servo routine where it all happens -

// if we are at then end of our ten channels - pulse the reset pin to reset the counter to channel 0 
// else pulse the clock pin to advance to the next channel
// the setOutPutTimerForPulseDuration function sets the timer1 output compare register
// so that it will trigger this function again when we need to end the current pulse and 
// begin the next one, thats it, thats all we need to do to control 10 servos with 2 pins.

void CRCArduinoSerialServos::OCR1A_ISR()
{
  if(m_sCurrentOutputChannel >= RC_CHANNEL_OUT_COUNT)
  {
    m_sCurrentOutputChannel = 0;

    PORTB|=16;

    CRCArduinoSerialServos::setOutputTimerForPulseDuration();

    PORTB^=16;
 }
 else
 {
    PORTB|=2;

    CRCArduinoSerialServos::setOutputTimerForPulseDuration();

    PORTB^=2;
  }
    m_sCurrentOutputChannel++;
}

Looking at the generated assembly code, the whole thing takes only two millionths of a second thats only slightly little longer than a single call to digitalWrite.

Here is a sample sketch which you can use to try the library, it outputs 10 channels from a digital pin 9, reset is through pin 12.

For you to be able try things for yourself, it also reads the pulse width back in using an interrupt on pin2. Each channel is set to a pulse width 100 microseconds greater than the previous channel - ie. Channel 0 = 1000, Channel 1 = 1100, Channel 9 = 1900. You can use the interrupt on pin 2 to read the pulse width back in from each of your channels and show it in the serial monitor.

Don't forge that if you want to drive servos with your Arduino you will need to provide separate power to the servos, here's why -

http://rcarduino.blogspot.com/2012/04/servo-problems-with-arduino-part-1.html

http://rcarduino.blogspot.com/2012/04/servo-problems-part-2-demonstration.html

Install the CPP and .H files into a library folder named RCArduinoChannels

Here is a basic schematic, its that simple - total cost - about 30 cents.



The Sketch -

Note that if the code looks long, it isnt, its all comments, for each line of code there are many many lines of comments for you to read should you wish.

Duane B

// RCArduinoSerialServos by DuaneB is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License.
// Based on a work at rcarduino.blogspot.com.

#include <RCArduinoSerialServos.h>

volatile uint32_t ulRiseTime;
volatile uint32_t ulPulseWidth;

void setup()
{
  Serial.begin(9600);
  Serial.println("RCArduinoSerialServos");
 
  // set the channels
  for(uint16_t nChannel = 0;nChannel < RC_CHANNEL_OUT_COUNT;nChannel++)
  {
    CRCArduinoSerialServos::writeMicroseconds(nChannel,1000+(nChannel*100));
  }

  CRCArduinoSerialServos::begin();
 
  attachInterrupt(0,calcPulse,CHANGE);
}

void loop()
{
  delay(10);

  if(ulPulseWidth != 0)
  {
    // disable interrupts so that our pulse value does not get overwritten while we try and read it
    uint8_t sReg = SREG;
    cli();
   
    // take a local copy of the pulse witdth so that we can reenable interrupts as soon as possible
    uint32_t ulLocalPulseWidth = ulPulseWidth;
   
    // clear the pulse width so that we will only pick up new values written by calcPulse rather
    // than keep printing old values.
    ulPulseWidth = 0;
   
    // turn interrupts back on
    SREG = sReg;
   
    // print the pulse width
    Serial.println(ulLocalPulseWidth);
  }
 
}

// Read pulse width applied to digital pin 2 (interrupt 0)
void calcPulse()
{
  if(digitalRead(2))
  {
    ulRiseTime = micros();
  }
  else
  {
    ulPulseWidth = micros()- ulRiseTime;
  }
}




The .H File -


/*****************************************************************************************************************************
// RCArduinoChannels by DuaneB is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
//
// http://rcarduino.blogspot.com
//
*****************************************************************************************************************************/


#include "Arduino.h"

// Dont change this,
// if you do not need ten channels, just leave some of the 4017 pins disconnected.
#define RC_CHANNEL_OUT_COUNT 10

// Minimum and Maximum servo pulse widths, you could change these,
// Check the servo library and use that range if you prefer
#define RCARDUINO_SERIAL_SERVO_MIN 1000
#define RCARDUINO_SERIAL_SERVO_MAX 2000

//////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// CRCArduinoSerialServos
//
// A class for generating signals in combination with a 4017 Counter
//
// Output upto 10 Servo channels using just digital pins 9 and 12
// 9 generates the clock signal and must be connected to the clock pin of the 4017
// 12 generates the reset pulse and must be connected to the master reset pin of the 4017
//
// The class uses Timer1, as this prevents use with the servo library
// The class uses pins 9 and 12
// The class does not adjust the servo frame to account for variations in pulse width,
// on the basis that many RC transmitters and receivers designed specifically to operate with servos
// output signals between 50 and 100hz, this is the same range as the library
//
// Use of an additional pin would provide for error detection, however using pin 12 to pulse master reset
// at the end of every frame means that the system is essentially self correcting
//
// Note
// This is a simplified derivative of the Arduino Servo Library created by Michael Margolis
// The simplification has been possible by moving some of the flexibility provided by the Servo library
// from software to hardware.
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////

class CRCArduinoSerialServos
{
public:
    CRCArduinoSerialServos();

    // configures timer1
    static void begin();

    // called by the timer interrupt service routine, see the cpp file for details.
    static void OCR1A_ISR();

    // called to set the pulse width for a specific channel, pulse widths are in microseconds - degrees are for wimps !
    static void writeMicroseconds(uint8_t nChannel,uint16_t nMicroseconds);

protected:
    // this sets the value of the timer1 output compare register to a point in the future
    // based on the required pulse with for the current servo
    static void setOutputTimerForPulseDuration();

    // Records the current output channel values in timer ticks
    // Manually set by calling writeChannel, the function adjusts from
    // user supplied micro seconds to timer ticks
    volatile static uint16_t m_unChannelSignalOut[RC_CHANNEL_OUT_COUNT];

    // current output channel, used by the timer ISR to track which channel is being generated
    static uint8_t m_sCurrentOutputChannel;

    // two helper functions to convert between timer values and microseconds
    static uint16_t ticksToMicroseconds(uint16_t unTicks);
    static uint16_t microsecondsToTicks(uint16_t unMicroseconds);
};



The CPP File - 


/*****************************************************************************************************************************
// RCArduinoSerialServos by DuaneB is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
//
// http://rcarduino.blogspot.com
//
*****************************************************************************************************************************/

#include "arduino.h"
#include "RCArduinoSerialServos.h"

/*----------------------------------------------------------------------------------------

This is essentially a derivative of the Arduino Servo Library created by Michael Margolis

As the technique is very similar to the Servo class, it can be useful to study in order
to understand the servo class.

What does the library do ? It uses a very inexpensive and common 4017 Counter IC
To generate pulses to independently drive up to 10 servos from two Arduino Pins

As previously mentioned, the library is based on the techniques used in the Arduino Servo
library created by Michael Margolis. This means that the library uses Timer1 and Timer1 output
compare register A.

OCR1A is linked to digital pin 9 and so we use digital pin 9 to generate the clock signal
for the 4017 counter.

Pin 12 is used as the reset pin.

*/

// Timer1 Output Compare A interrupt service routine
// call out class member function OCR1A_ISR so that we can
// access out member variables
ISR(TIMER1_COMPA_vect)
{
    CRCArduinoSerialServos::OCR1A_ISR();
}

void CRCArduinoSerialServos::OCR1A_ISR()
{
    // If the channel number is >= 10, we need to reset the counter
    // and start again from zero.
    // to do this we pulse the reset pin of the counter
    // this sets output 0 of the counter high, effectivley
    // starting the first pulse of our first channel
  if(m_sCurrentOutputChannel >= RC_CHANNEL_OUT_COUNT)
  {
    // reset our current servo/output channel to 0
    m_sCurrentOutputChannel = 0;

    // pulse reset on the counter - we set it high here
    PORTB|=16;

    // set the duration of the output pulse
    CRCArduinoSerialServos::setOutputTimerForPulseDuration();

    // finish the reset pulse - we set it low here
    PORTB^=16;
 }
 else
 {
  // pulse the clock pin high
    PORTB|=2;

    // set the duration of the output pulse
    CRCArduinoSerialServos::setOutputTimerForPulseDuration();

    // finish the clock pulse - set it back to low
    PORTB^=2;
  }
    // done with this channel so move on.
    m_sCurrentOutputChannel++;
}

// After we set an output pin high, we need to set the timer to comeback for the end of the pulse
void CRCArduinoSerialServos::setOutputTimerForPulseDuration()
{
  OCR1A = TCNT1 + m_unChannelSignalOut[m_sCurrentOutputChannel];
}

// updates a channel to a new value, the class will continue to pulse the channel
// with this value for the lifetime of the sketch or until writeChannel is called
// again to update the value
void CRCArduinoSerialServos::writeMicroseconds(uint8_t nChannel,uint16_t unMicroseconds)
{
    // dont allow a write to a non existent channel
    if(nChannel > RC_CHANNEL_OUT_COUNT)
        return;

  // constraint the value just in case
  unMicroseconds = constrain(unMicroseconds,RCARDUINO_SERIAL_SERVO_MIN,RCARDUINO_SERIAL_SERVO_MAX);

  // disable interrupts while we update the multi byte value output value
  uint8_t sreg = SREG;
  cli();
 
  m_unChannelSignalOut[nChannel] = microsecondsToTicks(unMicroseconds);

  // enable interrupts
  SREG = sreg;
}

uint16_t CRCArduinoSerialServos::ticksToMicroseconds(uint16_t unTicks)
{
    return unTicks / 2;
}

uint16_t CRCArduinoSerialServos::microsecondsToTicks(uint16_t unMicroseconds)
{
 return unMicroseconds * 2;
}

void CRCArduinoSerialServos::begin()
{
    // set the pins we will to outputs
    pinMode(9,OUTPUT);
    pinMode(12,OUTPUT);

    // pulse reset
    digitalWrite(12,HIGH);
    digitalWrite(12,LOW);

    TCNT1 = 0;              // clear the timer count  

    // Initilialise Timer1
    TCCR1A = 0;             // normal counting mode
    TCCR1B = 2;     // set prescaler of 64 = 1 tick = 4us

    // ENABLE TIMER1 OC1A INTERRUPT
    TIFR1 |= _BV(OCF1A);     // clear any pending interrupts;
    TIMSK1 |=  _BV(OCIE1A) ; // enable the output compare interrupt 

    OCR1A = TCNT1 + 4000; // Start in two milli seconds
}

// See the .h file
volatile uint16_t CRCArduinoSerialServos::m_unChannelSignalOut[RC_CHANNEL_OUT_COUNT];
uint8_t CRCArduinoSerialServos::m_sCurrentOutputChannel;









Friday, August 3, 2012

Never Say Never - The RC Arduino Library

I always told myself I would never write an Arduino library but after looking at the assembly code of some of my recent projects I have changed my mind.

Most of my projects involve reading and writing RC Signals, these are both time critical activities, the difference between full brakes and full throttle is only one thousandth of a second.

Existing solutions such as the code I have posted previously are perfectly good, but they are based on general purpose libraries that sacrifice performance and accuracy for flexibility.

Examples using general purpose libraries -
Servo Library
http://rcarduino.blogspot.com/2012/01/can-i-control-more-than-x-servos-with.html

PinChangeInt library
http://rcarduino.blogspot.com/2012/03/need-more-interrupts-to-read-more.html
http://rcarduino.blogspot.com/2012/04/how-to-read-multiple-rc-channels-draft.html


I am in the testing stages with a dedicated RC Library which sacrifices some flexibility for a big improvement in accuracy, size and performance.

I have a the perfect test bed for the library in my existing projects -

The L293D RC Robot
http://rcarduino.blogspot.com/2012/05/rc-arduino-robot.html

RC Race Car Child Mode
http://rcarduino.blogspot.com/2012/01/traction-control-part-13-we-have.html

Active Yaw Control
http://rcarduino.blogspot.com/2012/07/rcarduino-yaw-control-part-2.html

By converting each project to use the new library I can demonstrate the performance, ease of use and reliability of the library. I also plan to include the existing projects as samples within the library download.

Bench testing is complete, road testing starts tomorrow.

Duane B


Tuesday, April 3, 2012

Servo Problems - Part 2 - A Demonstration


If you have read the post Servo Problems With Arduino you will recognise the following as an apparently working circuit for driving one or more servos -




Lets have another look at this circuit -

In the following video I have replaced the 4 AA batteries that are powering the servos with a single 9 volt PP3 Battery. This battery is providing power to the servos and two LEDs through a 7805 regulator. This is a common regulator used in many Arduino's such as the Boarduino and standalone Arduinos. It is a reasonable replica of the 9V PP3 battery and on board regulator powering my Arduino UNO in the first video.

PP3 9 Volt Battery and The 7805 Regulator (red highlight) That Power A Boarduino

I have made one change to the circuit and introduced two series LEDs (and a current limiting resistor). Like all LEDs these two require a minimum voltage to light, in this case, combined they require around 4.5 volts. Thats about the minimum operating voltage of an Arduino so lets imagine that the LEDs are infact an Arduino sharing the same power circuit as the Servos.

Now lets see what happens under load -

Servo Under Static Load

When the LEDs go out, it is because the are no longer getting their minimum 4.5 volts.

When we apply a load to the servo, it draws more current, in this case it draws more current than the battery is able to deliver, when this happens, the battery voltage drops.

If this really was your Arduino it would have 'browned out' or at the very least your project would begin to behave oddly.

In the following video the servo is being instructed to sweep, even less effort is required to get the servo to take our imaginary Arduino out of service -

Loading Servos In Motion



Something else interesting is also happening, its easier to see in the following videos, but even without a load applied the servo is able to take down the LED Arduino -

In this circuit I have added another LED in the top left corner for comparison, this LED is driven by the Arduino power circuit (+5v -> 680 Omh resistor -> GND). The other two LEDs remain on the servo power circuit, note how the Arduino LED remains at a constant brightness whereas the imaginary Arduino LEDs flash noticeably -

Three Servos Overloading The Power Circuit



The camera is not fast enough to capture the full extent of the flashing in the LEDs, but if we can see it at all , you can be sure the Arduino is feeling it.

So what is this noise, where is it coming from and what can we do about it ?

Its not really noise, its our battery telling us that it just can't deliver the current we are asking for. The video is produced with no load, applying even a light load takes the LEDs out completely - thats not noise, thats your circuit telling you something.

The flashing is in sync with the servos changing direction at the end of their sweep. I don't have the means to investigate this further but suspect its down to the following effects -

1) The servos internal motor driver will be switching one set of transistors off and another on in order to reverse the motor
2) Momentum, the servo has to stop its internal motor and gears and get them moving in the opposite direction, this will require a burst of power.

Ok, but how do we get rid of the noise ?

As its not really noise, just our circuits way of telling us it doesn't have enough power, the solution is to add power.

Repeating the tests with the original four AA Battery pack provided improved results however it was not until I powered the circuit with a high power LIPO battery that the LEDs would remain lit at a constant brightness.


More on LIPOs and other batteries in a future post and some suggestions on using a similar LED set up to 'let the hardware do the work' in monitoring the power situation.

Duane B

Sunday, April 1, 2012

Servo Problems With Arduino - Part 1



Servo problems are one of the most frequently posted topics within the Arduino community. While problems may arise from programming, circuit design and faulty hardware, the vast majority or problems are a result of insufficient power or incorrectly connected power.

Control is nothing without power !

The Arduino itself is very good at controlling servos, the Servo library will allow a single Arduino to control upto 12 servos with no additional hardware.

http://rcarduino.blogspot.com/2012/01/can-i-control-more-than-x-servos-with.html

What the Arduino cannot do is deliver power to 12 Servos, its questionable whether an Arduino can reliably deliver power to even a single servo.

Power - Not All Servos Are Created Equal

While building the circuit for the pictures in this post, I decided to measure the current drawn by each of the test servos with no load applied. Each of the servos is the standard size used in radio controlled cars, they are all low end servos available for between 10 and 15 dollars. 

No Load Current Of Test Servos In mA

The Bluebird draws five times more current at no load than the Futaba, its also less smooth and the least expensive of the servos tested.

The current at no load is only relevant in a small number of applications, all of the servos drew more than the the maximum 250mA I was able to measure when subjected to a light load in the form of finger pressure.

I will be interested to follow up this test in a future post using a variety of repeatable loads to compare servo performance and current draw. The fact that the lowest cost servo draws five times more current than the Futaba servo suggest that this will be an important concern for larger autonomous projects which will need to operate both under their own power and under load.

Note that while controlling all four servos the Arduino drew only 10mA from its separate power source.



So How Can Successfully Drive Lots of Servos With An Arduino ?

The power problem is easily solved through the addition of a 'power circuit'. This is can be as simple as four disposable AA Batteries such as you might use in a camera or toy car.

4 Domestic AA Batteries in a holder

Rechargable AAs are an even better option, they store as much charge, deliver as much current and can be used again and again.


For large projects which need to operate under their own power the basic concept is the same however the choice of battery technology will be different. See the 'Performance Power' section in Part 2.

So why do we need two power circuits ?

The Arduino has a narrow operating voltage around 5 Volts (3.3 Volts in some) and is sensitive to variation in this voltage. The Arduino design is based on the assumption that a stable 5 Volt power source will be feeding the chip at the heart of the Arduino. In the case of the popular UNO, this regulated 5 Volt power is supplied by the USB Connection or through a regulator built into the board.

The onboard regulator is designed to provide power to the Arduino and supporting circuitry. It is not designed to power external devices and trying to do so is the single most common reason for failure with servo projects. This 'Not designed to power external devices' also applies to USB Connected projects.

The remainder of this post provides a walk through of setting up an Arduino servo example which will drive four servos from the Arduino using a separate power pack to meet the power requirements of the servos.


Arduino Servo Walkthrough -

1) Power The Arduino

For the walkthrough I am using an Arduino UNO powered by a PP3 9 Volt Battery.

The Arduino has been loaded with my Multi Sweep example sketch, a link is provided at the end.

While running the servos, the Arduino was drawing only 10mA. A good PP3 could power the Arduino for days, after all, they power smoke alarms for months.

 See the note below for more on the PP3 and why its not a good battery for use elsewhere in your projects.





 Note - The PP3 is a poor battery choice for most applications, it has a small charge capacity (run time) and cannot deliver the higher currents required to drive servos or motors, however the 9 volts it provides is great for powering an Arduino through the onboard regulator. As the Arduino makes so little demand on a battery the PP3 is a common and practical choice to power the Arduino - just not any shields, motors, servos, transmitters etc.

The PP3's small charge capacity and limited ability to deliver current make it an unsuitable choice for providing the power circuit in our projects, the common AA battery is a far better alternative. In the case of servos the 9 Volts supplied by an unregulated PP3 is over the 4.8 to 6 volt recommended operating range and will result in immediate damage to the servo. Again AA Batteries are a better choice as four will provide a usable 6 Volts for our servo power circuit and a better run time.

This wont work for me, I need USB for Serial Output -
 
This is no problem at all, you can simply connect the USB Cable to the Arduino as you normally would. This will provide power to the Arduino so you do not need to use the 9V PP3 Battery. 

You should still use the separate servo power and this will work provided that the ground from the battery pack is connected to the Arduino.

 
2) Power For The Servos

For servo power I am using four disposable AA Batteries. These are high capacity versions sold for cameras which will give us enough current and charge (run time) for our servos.

Each AA Battery provides 1.5 volts for a total of 6 volts (4*1.5).

Most servos are designed to operate with 4.8 to 6 volts. Powering them with more voltage can result in instant damage. The four AA Batteries give us a usable 6 volts.


I have connected the AA Batteries to the power (red) and ground (black) rails on my bread board.

Notice the black jumper running from the bread board to the Arduino ?

This is the next most common mistake in servo projects, when people introduce the servo power pack, they forget to connect a common ground.

You must connect the ground wire between the power circuit and the Arduino (control circuit) without this connection your circuit will not work. This applies whether you are powering your Arduino from a wall socket, USB port or a battery.

In the picture you can see where I have simply connected the two circuits through the black jumper wire running from the black ground rail of the bread board to the ground (GND) pin on the Arduino next to pin 13.

All of the ground pins on the Arduino are connected so use which GND pin is most convenient.

These two circuits now share a common ground allowing us to add some servos.


2) Connecting Individual Servo Power

I have added a 3-Pin section of PCB Header to make it easier to connect the female servo plug to female breadboard.

Next to this you can see that I have added a green jumper from the battery pack ground rail to pin1 of the header, this is the shared ground between the Arduino, battery pack and servo.

Next I have added a yellow jumper from the power rail - 6 Volts from the AA Battery pack - to the center pin of the header. This will provide power to our servo directly from the AA Battery pack, not from the Arduino or its 9 Volt PP3 Battery.

The final pin is the signal pin, this is the pin which the Arduino will use to tell the servo which position to hold.

Remember - Without the common ground between the Arduino and the Servo AA battery pack, your project will not work. In the picture, this common ground is provided by the black wire linking the ground rail of the breadboard with the GND pin next to pin 13 of the Arduino.


3) Adding a servo

All we need to do now is connect our first servo -
 You can see here that I have connected the first servo taking care to make sure that the black ground wire of the servo connector is connected to pin 1 of my header. This is then connected to the shared ground of the Arduino (and Battery pack ) through the green jumper wire.

You can also see that I have added headers for three more servos following the same convention of yellow for 6Volt power from the AA Battery pack and green for the shared Arduino/AA Battery pack ground.

The servo is receiving its position signals from the Arduino through the white jumper wire connected to pin 13. Remember - Without the common ground (black wire) connecting the Arduino GND Pin to the ground (-)  fo the battery pack through the black ground rail of the breadboard, this will not work !

If you have uploaded the multi sweep example sketch linked at the end of this post, you should see your servo sweeping back and forth.

4) Adding more servos

Once you have one servo up and running, you can add more by following the same convention - connect the servo plug to the header so that the end with the black wire attaches to the header pin with the green wire. You can then add a connection from the signal pin to any of the Arduino digital pins 2-13 and you should see your additional servos follow the same sweeping pattern as the first servo -


For the 'Multi Sweep' example sketch used in this video see the post - Can I control more than X Servos with an Arduino ?

Conclusions -

The Arduino can control large numbers of servos without additional hardware provided a separate power source is dedicated to the servos.

Four AA disposable or rechargeable batteries are a good choice for providing this power in smaller projects.

Large projects will require hobby batteries or fixed power supplies, part 2 will look at some of these options and the power demands of the test servos under a variety of loads.

Servo models used and current drawn at no load -

Servo                              Current mA

Futaba S3003  55
HITEC HS-322HD  75
ACE S1903  90
Bluebird BMS410  250

Duane B