Go Down

Topic: Servo library reference needs amending for Mega (Read 1 time) previous topic - next topic

mem

There have been a couple of recent  threads reporting problems with the Servo library on the Mega. The documentation for the servo library should mention that on the Mega the pins used are 11 and 12, not 9 and 10.

The existing text could be amended to something like this:

This library uses functionality built-in the hardware on pins 9 and 10 (11 and 12 on the Mega) of the microcontroller to control the servos without interfering with other code running on the Arduino. If only one servo is used, the other pin cannot be used for normal PWM output with analogWrite().

mellis


mem

#2
Jun 05, 2009, 01:32 pm Last Edit: Jun 05, 2009, 01:35 pm by mem Reason: 1
It looks like there is also a problem with the library code. The attach method tests for a valid pin by checking if these these are 9 or 10

Perhaps change:
[font=Courier New]  if (pinArg != 9 && pinArg != 10) return 0;[/font]

To  something like:
[font=Courier New]
#if defined(__AVR_ATmega1280__)
 if (pinArg != 11 && pinArg != 12) return 0;
#else
 if (pinArg != 9 && pinArg != 10) return 0;
#endif[/font]

mellis

There are some other lines in the code that need changing too.  Can you submit a patch here: http://code.google.com/p/arduino/issues/detail?id=26?  Otherwise, I'll do it when I have a chance.  Thanks.

mem

Not sure how you do patches but here is the modified Servo.cpp file that I have tested on ATmega168 and ATmega1280 boards.

Code: [Select]
#include <avr/interrupt.h>
#include <wiring.h>
#include <Servo.h>

/*
 Servo.cpp - Hardware Servo Timer Library
 Author: Jim Studt, jim@federated.com
 Copyright (c) 2007 David A. Mellis.  All right reserved.
 modifed for Mega - mem

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#if defined(__AVR_ATmega1280__)
 #define _PIN_A 11
 #define _PIN_B 12
#else
 #define _PIN_A 9
 #define _PIN_B 10
#endif


uint8_t Servo::attached9 = 0;
uint8_t Servo::attached10 = 0;

void Servo::seizeTimer1()
{
 uint8_t oldSREG = SREG;

 cli();
 TCCR1A = _BV(WGM11); /* Fast PWM, ICR1 is top */
 TCCR1B = _BV(WGM13) | _BV(WGM12) /* Fast PWM, ICR1 is top */
 | _BV(CS11) /* div 8 clock prescaler */
 ;
 OCR1A = 3000;
 OCR1B = 3000;
 ICR1 = clockCyclesPerMicrosecond()*(20000L/8);  // 20000 uS is a bit fast for the refresh, 20ms, but
                                                 // it keeps us from overflowing ICR1 at 20MHz clocks
                                                 // That "/8" at the end is the prescaler.
#if defined(__AVR_ATmega8__)
 TIMSK &= ~(_BV(TICIE1) | _BV(OCIE1A) | _BV(OCIE1B) | _BV(TOIE1) );
#else
 TIMSK1 &=  ~(_BV(OCIE1A) | _BV(OCIE1B) | _BV(TOIE1) );
#endif

 SREG = oldSREG;  // undo cli()    
}

void Servo::releaseTimer1() {}

#define NO_ANGLE (0xff)

Servo::Servo() : pin(0), angle(NO_ANGLE) {}

uint8_t Servo::attach(int pinArg)
{
 return attach(pinArg, 544, 2400);
}

uint8_t Servo::attach(int pinArg, int min, int max)
{
 if (pinArg != _PIN_A && pinArg != _PIN_B) return 0;
 
 min16 = min / 16;
 max16 = max / 16;

 pin = pinArg;
 angle = NO_ANGLE;
 digitalWrite(pin, LOW);
 pinMode(pin, OUTPUT);

 if (!attached9 && !attached10) seizeTimer1();

 if (pin == _PIN_A) {
   attached9 = 1;
   TCCR1A = (TCCR1A & ~_BV(COM1A0)) | _BV(COM1A1);
 }
 
 if (pin == _PIN_B) {
   attached10 = 1;
   TCCR1A = (TCCR1A & ~_BV(COM1B0)) | _BV(COM1B1);
 }
 return 1;
}

void Servo::detach()
{
 // muck with timer flags
 if (pin == _PIN_A) {
   attached9 = 0;
   TCCR1A = TCCR1A & ~_BV(COM1A0) & ~_BV(COM1A1);
   pinMode(pin, INPUT);
 }
 
 if (pin == _PIN_B) {
   attached10 = 0;
   TCCR1A = TCCR1A & ~_BV(COM1B0) & ~_BV(COM1B1);
   pinMode(pin, INPUT);
 }

 if (!attached9 && !attached10) releaseTimer1();
}

void Servo::write(int angleArg)
{
 uint16_t p;

 if (angleArg < 0) angleArg = 0;
 if (angleArg > 180) angleArg = 180;
 angle = angleArg;

 // bleh, have to use longs to prevent overflow, could be tricky if always a 16MHz clock, but not true
 // That 8L on the end is the TCNT1 prescaler, it will need to change if the clock's prescaler changes,
 // but then there will likely be an overflow problem, so it will have to be handled by a human.
 p = (min16*16L*clockCyclesPerMicrosecond() + (max16-min16)*(16L*clockCyclesPerMicrosecond())*angle/180L)/8L;
 if (pin == _PIN_A) OCR1A = p;
 if (pin == _PIN_B) OCR1B = p;
}

uint8_t Servo::read()
{
 return angle;
}

uint8_t Servo::attached()
{
 if (pin == _PIN_A && attached9) return 1;
 if (pin == _PIN_B && attached10) return 1;
 return 0;
}

I only changed the bare minimum code - you may want to rename the variables:attached9 and attached10 to something more generic (such as: attachedA and attachedB)

Also the example sketch must be modified for the correct pin numbers when building for the Mega so you may want to add a comment in the sketch and documentation.

You can eliminate the pin number problem and add a lot of functionality if you replaced the current Servo code with my MegaServo library. It is functionally compatible with the Servo library and can use any digital pin so the same sketch will work with any board. Supports 12 servos on a 168 and up to 48 servos on a Mega board.
See: http://www.arduino.cc/playground/Code/MegaServo

mellis

Wow, the MegaServo library is impressive!  Is there any reason not to replace the current Servo library with it?  If not, can you email the developers list and suggest the change?  I might have some minor suggestions, but it's probably best to discuss them on the list.

Paul Stoffregen

I would like to propose an alternate implementation.

This code supports 3 servo motors on chips with 3 timer1 PWM channels (such as the Arduino Mega).

It also defines names SERVO_PIN_A and SERVO_PIN_B which can be used instead of 9 and 10, if you want to write sketches that work on multiple boards.  Of course, it's fully compatible with the old API and you can still use hard-coded pin numbers in your sketch, if you like.

The changes to the original code are based on Zach's approach.  This code supports all '168 and '328 based Arduinos, the Arduino Mega, Sanguino, and both Teensy boards, and it is designed to make adding other boards easy.  The board-specific #defines are in only one location inside servo.h, rather than scattered throughout the actual code.

I'll post this to the developer list too.

Servo.cpp
Code: [Select]

#include <avr/interrupt.h>
#include <wiring.h>
#include <Servo.h>

/*
 Servo.h - Hardware Servo Timer Library
 Author: Jim Studt, jim@federated.com
 Copyright (c) 2007 David A. Mellis.  All right reserved.

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/


uint8_t Servo::attachedA = 0;
uint8_t Servo::attachedB = 0;
#ifdef SERVO_PIN_C
uint8_t Servo::attachedC = 0;
#endif

void Servo::seizeTimer1()
{
 uint8_t oldSREG = SREG;

 cli();
 TCCR1A = _BV(WGM11); /* Fast PWM, ICR1 is top */
 TCCR1B = _BV(WGM13) | _BV(WGM12) /* Fast PWM, ICR1 is top */
 | _BV(CS11) /* div 8 clock prescaler */
 ;
 OCR1A = 3000;
 OCR1B = 3000;
 ICR1 = clockCyclesPerMicrosecond()*(20000L/8);  // 20000 uS is a bit fast for the refresh, 20ms, but
                                                 // it keeps us from overflowing ICR1 at 20MHz clocks
                                                 // That "/8" at the end is the prescaler.
#if defined(__AVR_ATmega8__)
 TIMSK &= ~(_BV(TICIE1) | _BV(OCIE1A) | _BV(OCIE1B) | _BV(TOIE1) );
#else
 TIMSK1 &=  ~(_BV(OCIE1A) | _BV(OCIE1B) | _BV(TOIE1) );
#endif

 SREG = oldSREG;  // undo cli()    
}

void Servo::releaseTimer1() {}

#define NO_ANGLE (0xff)

Servo::Servo() : pin(0), angle(NO_ANGLE) {}

uint8_t Servo::attach(int pinArg)
{
 return attach(pinArg, 544, 2400);
}

uint8_t Servo::attach(int pinArg, int min, int max)
{
 #ifdef SERVO_PIN_C
 if (pinArg != SERVO_PIN_A && pinArg != SERVO_PIN_B && pinArg != SERVO_PIN_C) return 0;
 #else
 if (pinArg != SERVO_PIN_A && pinArg != SERVO_PIN_B) return 0;
 #endif

 min16 = min / 16;
 max16 = max / 16;

 pin = pinArg;
 angle = NO_ANGLE;
 digitalWrite(pin, LOW);
 pinMode(pin, OUTPUT);

 #ifdef SERVO_PIN_C
 if (!attachedA && !attachedB && !attachedC) seizeTimer1();
 #else
 if (!attachedA && !attachedB) seizeTimer1();
 #endif

 if (pin == SERVO_PIN_A) {
   attachedA = 1;
   TCCR1A = (TCCR1A & ~_BV(COM1A0)) | _BV(COM1A1);
 }

 if (pin == SERVO_PIN_B) {
   attachedB = 1;
   TCCR1A = (TCCR1A & ~_BV(COM1B0)) | _BV(COM1B1);
 }

 #ifdef SERVO_PIN_C
 if (pin == SERVO_PIN_C) {
   attachedC = 1;
   TCCR1A = (TCCR1A & ~_BV(COM1C0)) | _BV(COM1C1);
 }
 #endif
 return 1;
}

void Servo::detach()
{
 // muck with timer flags
 if (pin == SERVO_PIN_A) {
   attachedA = 0;
   TCCR1A = TCCR1A & ~_BV(COM1A0) & ~_BV(COM1A1);
   pinMode(pin, INPUT);
 }

 if (pin == SERVO_PIN_B) {
   attachedB = 0;
   TCCR1A = TCCR1A & ~_BV(COM1B0) & ~_BV(COM1B1);
   pinMode(pin, INPUT);
 }

 #ifdef SERVO_PIN_C
 if (pin == SERVO_PIN_C) {
   attachedC = 0;
   TCCR1A = TCCR1A & ~_BV(COM1C0) & ~_BV(COM1C1);
   pinMode(pin, INPUT);
 }
 #endif

 #ifdef SERVO_PIN_C
 if (!attachedA && !attachedB && !attachedC) releaseTimer1();
 #else
 if (!attachedA && !attachedB) releaseTimer1();
 #endif
}

void Servo::write(int angleArg)
{
 uint16_t p;

 if (angleArg < 0) angleArg = 0;
 if (angleArg > 180) angleArg = 180;
 angle = angleArg;

 // bleh, have to use longs to prevent overflow, could be tricky if always a 16MHz clock, but not true
 // That 8L on the end is the TCNT1 prescaler, it will need to change if the clock's prescaler changes,
 // but then there will likely be an overflow problem, so it will have to be handled by a human.
 p = (min16*16L*clockCyclesPerMicrosecond() + (max16-min16)*(16L*clockCyclesPerMicrosecond())*angle/180L)/8L;
 if (pin == SERVO_PIN_A) OCR1A = p;
 if (pin == SERVO_PIN_B) OCR1B = p;
 #ifdef SERVO_PIN_C
 if (pin == SERVO_PIN_C) OCR1C = p;
 #endif
}

uint8_t Servo::read()
{
 return angle;
}

uint8_t Servo::attached()
{
 if (pin == SERVO_PIN_A && attachedA) return 1;
 if (pin == SERVO_PIN_B && attachedB) return 1;
 #ifdef SERVO_PIN_C
 if (pin == SERVO_PIN_C && attachedC) return 1;
 #endif
 return 0;
}




Servo.h
Code: [Select]

#ifndef Servo_h
#define Servo_h

/*
 Servo.h - Hardware Servo Timer Library
 Author: Jim Studt, jim@federated.com
 Copyright (c) 2007 David A. Mellis.  All right reserved.

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include <inttypes.h>

#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) // Arduino
 #define SERVO_PIN_A 9
 #define SERVO_PIN_B 10
#elif defined(__AVR_ATmega1280__) // Arduino Mega
 #define SERVO_PIN_A 11
 #define SERVO_PIN_B 12
 #define SERVO_PIN_C 13
#elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__) // Sanguino
 #define SERVO_PIN_A 13
 #define SERVO_PIN_B 12
#elif defined(__AVR_AT90USB162__) // Teensy
 #define SERVO_PIN_A 17
 #define SERVO_PIN_B 18
 #define SERVO_PIN_C 15
#elif defined(__AVR_AT90USB646__) // Teensy++
 #define SERVO_PIN_A 25
 #define SERVO_PIN_B 26
 #define SERVO_PIN_C 27
#else
 #define SERVO_PIN_A 9
 #define SERVO_PIN_B 10
#endif

class Servo
{
 private:
   uint8_t pin;
   uint8_t angle;       // in degrees
   uint8_t min16;       // minimum pulse, 16uS units  (default is 34)
   uint8_t max16;       // maximum pulse, 16uS units, 0-4ms range (default is 150)
   static void seizeTimer1();
   static void releaseTimer1();
   static uint8_t attachedA;
   static uint8_t attachedB;
   #ifdef SERVO_PIN_C
   static uint8_t attachedC;
   #endif
 public:
   Servo();
   uint8_t attach(int);
                            // pulse length for 0 degrees in microseconds, 544uS default
                            // pulse length for 180 degrees in microseconds, 2400uS default
   uint8_t attach(int, int, int);
                            // attach to a pin, sets pinMode, returns 0 on failure, won't
                            // position the servo until a subsequent write() happens
                            // Only works for 9 and 10.
   void detach();
   void write(int);         // specify the angle in degrees, 0 to 180
   uint8_t read();
   uint8_t attached();
};

#endif



mem

Paul, did you have a look at the MegaServo library?  

0X_R

Hi, there... I'm using Mega32 and tried MegaServo's example: Sweep. But it gives me error when i compiled it. It said that TIMSK1 and TIFR1 was not declared. Can anyone help me on this?

mem

The ATmega32 is not explicitly supported but it may work with the updated version of this library which is now part of the official Arduino distribution (as servo.h)

0X_R

Already use the latest libraries, but still encounter the same problem. Is there any way to control servo without using this library?

mem

there is a software servo library in the playground that does not use timers, see http://www.arduino.cc/playground/ComponentLib/Servo

Go Up