"Fixed" Version Of The ServoTimer2

See Following Post For ServoTimer2 Library Files

Hello, SRS.

We spoke on the phone. Have you made any progress? I noticed you deleted your other post. In the future, once you make a post, please do not delete it or edit the content of it dramatically, as it makes it confusing for others.

As I mentioned on the phone, changing between using a servo and the buzzer with Timer2 will probably be complicated. I do not know of a simple way to do it. If you want to try, you should make sure to disable the interrupt and any other things involved in using Timer2 for the servo before using the buzzer and vice versa. If you try and post your code, I would be happy to take a look at it.

- Jeremy

I just found out about this other animal called AVR.
I am taking a crash course in it now.

it is a relatively simple task if you know AVR programming well.
My problem is that at the moment I don’t know it at all.

Do you have an AVR expert at Pololu that can give advice?

Thanks for all of your help Jeremy

If you decide to program the AVR without using the Arduino IDE, you can still post your code here and we would be happy to take a look at it.

- Jeremy

Here is a fixed version of the ServoTimer2 library that I corrected and tested using a Arduino Uno and one servo.

Note:
In my tests the servo positions were reversed from those in the standard servo.h library.
This can easily be compensated for using the map() function.

keywords.txt:

#######################################
# Syntax Coloring Map For Ultrasound
#######################################

#######################################
# Datatypes (KEYWORD1)
#######################################

ServoTimer2	KEYWORD1

#######################################
# Methods and Functions (KEYWORD2)
#######################################

attach	KEYWORD2
detach	KEYWORD2
write	KEYWORD2
read	KEYWORD2
attached	KEYWORD2

#######################################
# Constants (LITERAL1)
#######################################

ServoTimer2.h

/*
  ServoTimer2.h - Interrupt driven Servo library for Arduino using
/*
  This library uses Timer2 to drive up to 8 servos using interrupts so no refresh activity is required from within the sketch.
  The usage and method naming is similar to the Arduino software servo library http://www.arduino.cc/playground/ComponentLib/Servo
  except that pulse widths are in microseconds.

  
  A servo is activated by creating an instance of the Servo class passing the desired pin to the attach() method.
  The servo is pulsed in the background to the value most recently written using the write() method

  Note that analogWrite of PWM on pins 3 and 11 is disabled when the first servo is attached

  The methods are:

   ServoTimer2 - Class for manipulating servo motors connected to Arduino pins.

   attach(pin )  - Attaches a servo motor to an i/o pin.
   attach(pin, min, max  ) - Attaches to a pin setting min and max values in microseconds
    default min is 544, max is 2400  

   write()     - Sets the servo pulse width in microseconds.

   read()	- Gets the last written servo pulse width in microseconds.

   attached()  - Returns true if there is a servo attached.

   detach()    - Stops an attached servos from pulsing its i/o pin.
  

The library takes about 824 bytes of program memory and 32+(1*servos) bytes of SRAM.
The pulse width timing is accurate to within 1%

 */

// ensure this library description is only included once
#ifndef ServoTimer2_h
#define ServoTimer2_h

#include <inttypes.h>
typedef uint8_t boolean;
typedef uint8_t byte;

#define MIN_PULSE_WIDTH	 610	  // the shortest pulse sent to a servo  
#define MAX_PULSE_WIDTH	2300	  // the longest pulse sent to a servo
#define DEFAULT_PULSE_WIDTH  1500	  // default pulse width when servo is attached
#define FRAME_SYNC_PERIOD   20000	  // total frame duration in microseconds
#define NBR_CHANNELS 8			 // the maximum number of channels, don't change this

typedef struct  {
	uint8_t nbr	  :5 ;  // a pin number from 0 to 31
	uint8_t isActive   :1 ;  // false if this channel not enabled, pin only pulsed if true
   } ServoPin_t   ;  

typedef struct {
  ServoPin_t Pin;
  byte counter;
  byte remainder;
}  servo_t;

class ServoTimer2
{
  public:
	// constructor:
	ServoTimer2();

	uint8_t attach(int);     // attach the given pin to the next free channel, sets pinMode, returns channel number or 0 if failure
					 // the attached servo is pulsed with the current pulse width value, (see the write method)
	uint8_t attach(int, int, int); // as above but also sets min and max values for writes.
    void detach();
    void write(int);	   // store the pulse width in microseconds (between MIN_PULSE_WIDTH and MAX_PULSE_WIDTH)for this channel
    int read(); 			 // returns current pulse width in microseconds for this servo
	boolean attached();	// return true if this servo is attached
 private:
	 uint8_t chanIndex;	// index into the channel data for this servo

};


// the following ServoArrayT2 class is not implemented in the first version of this library
class ServoArrayT2
{
  public:
	// constructor:
	ServoArrayT2();

	uint8_t attach(int);     // attach the given pin to the next free channel, sets pinMode, returns channel number or 0 if failure
					 // channels are assigned consecutively starting from 1
					 // the attached servo is pulsed with the current pulse width value, (see the write method)
    void detach(int);	  // detach the servo on the given channel
	void write(int,int);     // store the pulse width in microseconds (between MIN_PULSE_WIDTH and MAX_PULSE_WIDTH)for the given channel
    int read(int); 			 // returns current pulse width in microseconds for the given channel
	boolean attached(int);   // return true if the servo on the given channel is attached
 private:
	 uint8_t chanIndex;	// index into the channel data for this servo

};

#endif

ServoTimer2.cpp:

/* ServoTimer2.cpp*/

// AVR LibC Includes
#include <inttypes.h>
#include <avr/interrupt.h>

#include <Arduino.h>
#include "ServoTimer2.h"
static void initISR();
static void writeChan(uint8_t chan, int pulsewidth);

#define FRAME_SYNC_INDEX   0		   // frame sync delay is the first entry in the channel array
#define FRAME_SYNC_PERIOD  20000	   // total frame duration in microseconds
#define FRAME_SYNC_DELAY   ((FRAME_SYNC_PERIOD - ( NBR_CHANNELS * DEFAULT_PULSE_WIDTH))/ 128) // number of iterations of the ISR to get the desired frame rate
#define DELAY_ADJUST	 8                 // number of microseconds of calculation overhead to be subtracted from pulse timings

static servo_t servos[NBR_CHANNELS+1];    // static array holding servo data for all channels

static volatile uint8_t Channel;   // counter holding the channel being pulsed
static volatile uint8_t ISRCount;  // iteration counter used in the interrupt routines;
uint8_t ChannelCount = 0;	    // counter holding the number of attached channels
static boolean isStarted = false;  // flag to indicate if the ISR has been initialised

ISR (TIMER2_OVF_vect)
{
  ++ISRCount; // increment the overlflow counter
  if (ISRCount ==  servos[Channel].counter ) // are we on the final iteration for this channel
  {
	TCNT2 =  servos[Channel].remainder;   // yes, set count for overflow after remainder ticks
  }
  else if(ISRCount >  servos[Channel].counter)
  {
	// we have finished timing the channel so pulse it low and move on
	if(servos[Channel].Pin.isActive == true)	     // check if activated
	    digitalWrite( servos[Channel].Pin.nbr,LOW); // pulse this channel low if active

	  Channel++;    // increment to the next channel
	ISRCount = 0; // reset the isr iteration counter
	TCNT2 = 0;    // reset the clock counter register
	if( (Channel != FRAME_SYNC_INDEX) && (Channel <= NBR_CHANNELS) ){	     // check if we need to pulse this channel
	    if(servos[Channel].Pin.isActive == true)	   // check if activated
		 digitalWrite( servos[Channel].Pin.nbr,HIGH); // its an active channel so pulse it high
	}
	else if(Channel > NBR_CHANNELS){
	   Channel = 0; // all done so start over
	}
   }
}

ServoTimer2::ServoTimer2()
{
   if( ChannelCount < NBR_CHANNELS)
	this->chanIndex = ++ChannelCount;  // assign a channel number to this instance
   else
	this->chanIndex = 0;  // todo	// too many channels, assigning 0 inhibits this instance from functioning
}

uint8_t ServoTimer2::attach(int pin)
{
	if( isStarted == false)
	 initISR();
	if(this->chanIndex > 0)
	{
	 //debug("attaching chan = ", chanIndex);
	 pinMode( pin, OUTPUT) ;  // set servo pin to output
	 servos[this->chanIndex].Pin.nbr = pin;
	 servos[this->chanIndex].Pin.isActive = true;
	}
	return this->chanIndex ;
}

void ServoTimer2::detach()
{
    servos[this->chanIndex].Pin.isActive = false;
}

void ServoTimer2::write(int pulsewidth)
{
   writeChan(this->chanIndex, pulsewidth); // call the static function to store the data for this servo
}

int ServoTimer2::read()
{
  unsigned int pulsewidth;
   if( this->chanIndex > 0)
	pulsewidth =  servos[this->chanIndex].counter * 128 + ((255 - servos[this->chanIndex].remainder) / 2) + DELAY_ADJUST ;
   else
	 pulsewidth  = 0;
   return pulsewidth;
}

boolean ServoTimer2::attached()
{
    return servos[this->chanIndex].Pin.isActive ;
}

static void writeChan(uint8_t chan, int pulsewidth)
{
   // calculate and store the values for the given channel
   if( (chan > 0) && (chan <= NBR_CHANNELS) )   // ensure channel is valid
   {
	if( pulsewidth < MIN_PULSE_WIDTH )		    // ensure pulse width is valid
	    pulsewidth = MIN_PULSE_WIDTH;
	else if( pulsewidth > MAX_PULSE_WIDTH )
	    pulsewidth = MAX_PULSE_WIDTH;

	  pulsewidth -=DELAY_ADJUST;			 // subtract the time it takes to process the start and end pulses (mostly from digitalWrite)
	servos[chan].counter = pulsewidth / 128;
	servos[chan].remainder = 255 - (2 * (pulsewidth - ( servos[chan].counter * 128)));  // the number of 0.5us ticks for timer overflow
   }
}

static void initISR()
{
	for(uint8_t i=1; i <= NBR_CHANNELS; i++) {  // channels start from 1
	   writeChan(i, DEFAULT_PULSE_WIDTH);  // store default values
	}
	servos[FRAME_SYNC_INDEX].counter = FRAME_SYNC_DELAY;   // store the frame sync period

	Channel = 0;  // clear the channel index
	ISRCount = 0;  // clear the value of the ISR counter;

	/* setup for timer 2 */
	TIMSK2 = 0;  // disable interrupts
	TCCR2A = 0;  // normal counting mode
	TCCR2B = _BV(CS21); // set prescaler of 8
	TCNT2 = 0;     // clear the timer2 count
	TIFR2 = _BV(TOV2);  // clear pending interrupts;
	TIMSK2 =  _BV(TOIE2) ; // enable the overflow interrupt

	isStarted = true;  // flag to indicate this initialisation code has been executed
}  

ServoTimer2.pde [Example]:

// this sketch cycles three servos at different rates

#include <ServoTimer2.h>  // the servo library

// define the pins for the servos
#define rollPin  2
#define pitchPin 3
#define yawPin   4

ServoTimer2 servoRoll;    // declare variables for up to eight servos
ServoTimer2 servoPitch;
ServoTimer2 servoYaw;

void setup() {
  servoRoll.attach(rollPin);     // attach a pin to the servos and they will start pulsing
  servoPitch.attach(pitchPin);
  servoYaw.attach(yawPin);
}


// this function just increments a value until it reaches a maximum
int incPulse(int val, int inc){
   if( val + inc  > 2000 )
	return 1000 ;
   else
	 return val + inc;
}

void loop()
{
 int val;

   val = incPulse( servoRoll.read(), 1);
   servoRoll.write(val);

   val =  incPulse( servoPitch.read(), 2);
   servoPitch.write(val);

   val = incPulse(servoYaw.read(), 4);
   servoYaw.write(val);

   delay(10);
}

Enjoy!

.

Jeremy (And Other Forum Moderators):

If I post a thread that is entirely in error, like the original SoftwareServo2 library problem we worked on over the phone, then I delete the thread.
In the future, I can just mark the thread title “[Wrong]”, but what would be the point of maintaining a completely erroneous thread?

If I am making a contribution to the forum, such as posting code that might be useful in a variety of situations, then I post it in its own topic/thread. This makes the contribution easy to find.

If the new thread is a solution to another thread, then I add a link from the problem thread to the solution thread and mark the thread “[Solved]”
If the problem thread has a straight forward solution specific to that problem and is not a generic general use program or a work around for Arduino hardware limitations, then I post the solution in its own thread and mark the problem thread “[Solved]”,

This way members who might skip over my thread because they do not have the same problem, might find some (hopefully valuable) solution that solves some other problem they have.

For instance a member might disregard a thread titled “Getting Servo and Buzzer to Work Together on a Zumo”, because they do not have that problem, but find a thread titled “Software Servo Library Using Timer 2” has just the solution they need to make their mechanical arm work.

-Shawn-

P.S. Thanks again Jeremy for all of your help! :smiley:

.

Some of the points you made are valid. However, editing or removing content from a post or thread, regardless of how correct it might be, might make it difficult for someone else on the forum to follow the progression of the thread. If you want to update the status of your thread, I recommend adding content to the discussion. For example, you could edit a post to include a link that you found useful or post again as a reply to your original thread laying out anything you might have learned. You could also add a note in large red text that lets other forum users know that the code in that post does not work. Leaving the original problem and/or the erroneous code could also lead to others suggesting better solutions.

- Jeremy