Archive

Posts Tagged ‘servos’

Servo Control with an XMega

I’m not going to go into a long treatise on PWM servo signals – there are plenty of references for that already.  I’d like to focus on a simple way to do a bunch of servos with fairly accurate timings on an XMega.  Also, I know this isn’t an optimal implementation – there are efficiency improvement that could be made on almost every front.  But it took me almost no time to come up with, and that says a lot.  I’m more of an idoit than I let on…

The method I’m going to line out below can drive up to 7 servos completely in interrupts.  It’ll use up your timers, but it’s easy peasy and will save you time if you don’t need all your timers for other tasks.

So, we’ve got a standard servo signal that’s composed of basically three stages:

  1. A twenty millisecond low period.
  2. A one millisecond high period.
  3. A variable high period:  To turn the servo to one extreme, force the signal low immediately.  To turn the servo to the opposite extreme, force the signal low after a full millisecond.  To turn it to some point in between, just vary the signal at this point between zero and one milliseconds.

With the advent of the XMega, the AVR series of microcontrollers now have an plethora of peripherals, notably for this article eight 16-bit timers.  EIGHT!  That’s power, baby.  For this example, I’ll only do two servos, which will require three timers.  That seems like a lot because with most ATMegas, that was about it – you only had 4 timers, and only two of those were 16-bit (and 8-bit timers == headaches

I started from App Note AVR1306, which describes the timers.  BTW, app notes are the way to become familiar with the XMegas, especially if you’re coming up from the previous AVR families.  Download the drivers along with the PDF, they’re pretty useful.  Include these in your project.  I’ve used these with only a few modifications, and they’ve worked pretty well for me so far.

Set up a global array that can be used for servo position variables:

volatile uint16_t servo[3];

Note it’s declared volatile – this is necessary for all variables you’ll be using between the main loop and an interrupt service routine.  Also note that I declared 3 spaces for two servos – on this particular one, I liked labeling the servos “Servo1”, “Servo2”, etc. instead of base zero.  Personal preference, but adding the extra byte keeps the numbering consistent between labels and array index.

Next up is the timer initialization routine.  Let’s look at it first.  The comments are fairly explanatory, and I’ll go through it a little more afterwards:

/*! \brief This function initializes timers for use as servo PWM signal drivers.
 *
 *    This function initializes timers for use as servo PWM signal drivers.
 *    It probably wouldn't hurt to improve this by making it modular at some point...
 *
 *    - TimerD0: 20ms servo timer.  This timer triggers the rest of the servo position timers.
 *    - TimerD1: 1-2ms Servo1 position timer.
 *    - TimerE0: 1-2ms Servo2 position timer.
 *
 */
void init_timers()
{
    //! TimerD0: 20ms Servo Timer
    TC_SetPeriod( &TCD0, 2500 );                            // Set period (2500 ticks = 20ms)
    TC0_SetOverflowIntLevel( &TCD0, TC_OVFINTLVL_MED_gc );  // Set Interrupt on Overflow
    TC0_ConfigClockSource( &TCD0, TC_CLKSEL_DIV256_gc );    // Set clock and prescaler (8us per tick)

    //! TimerD1: Servo1 Position Timer
    TC1_SetOverflowIntLevel( &TCD1, TC_OVFINTLVL_OFF_gc );  // Set Interrupt on Overflow, but not yet
    TC1_ConfigClockSource( &TCD1, TC_CLKSEL_DIV256_gc );    // Set clock and prescaler (8us per tick)

    //! TimerE0: Servo2 Position Timer
    TC0_SetOverflowIntLevel( &TCE0, TC_OVFINTLVL_OFF_gc );  // Set Interrupt on Overflow, but not yet
    TC0_ConfigClockSource( &TCE0, TC_CLKSEL_DIV256_gc );    // Set clock and prescaler (8us per tick)

    PMIC.CTRL |= PMIC_MEDLVLEN_bm;
    sei();
}

So, as the above code lines out, TimerD0 is the 20 millisecond timer.  Every twenty milliseconds, it fires.  When it does, it raises the servo signal and then enables TimerD1 and TimerE0, which subsequently time out the 1ms period + position offset.  These functions all come from the application note mentioned above.  The three functions used to set up TimerD0 sets the period, enables the overflow interrupt at the medium tier, and then sets the prescaler.   Pretty straightforward if you use the driver functions.

Here’s the ISR for TimerD0:

/*! \brief Timer/CounterD0 Overflow interrupt service routine
 *
 *  TimerD0 overflow interrupt service routine.
 *  Set for a 20ms period.  Sets servo output port pin, enables TCD1 which resets the pin.
 */
ISR(TCD0_OVF_vect)
{
    uint16_t period;

    /// SERVO1 Timer Enable
    period = 63 + SERVO1_OFFSET + servo[1];                // Calculate period
    SET_SERVO1;                                            // Raise the servo pin
    TC1_SetOverflowIntLevel( &TCD1, TC_OVFINTLVL_MED_gc ); // Set Interrupt on Overflow
    TC_SetPeriodBuffered( &TCD1, period);                  // Start TCD1, set period
    TC1_ConfigClockSource( &TCD1, TC_CLKSEL_DIV256_gc );   // Set clock and prescaler (8us per tick)

    /// SERVO2 Timer Enable
    period = 63 + SERVO2_OFFSET + servo[2];                // Calculate period
    SET_SERVO2;                                            // Raise the servo pin
    TC0_SetOverflowIntLevel( &TCE0, TC_OVFINTLVL_MED_gc ); // Enable timer overflow interrupt
    TC_SetPeriodBuffered( &TCE0, period);                  // Start timer, set period
    TC0_ConfigClockSource( &TCE0, TC_CLKSEL_DIV256_gc );   // Set clock and prescaler (8us per tick)
}

So, it does the following for both servos:

  • Calculates the period.  If everything were a perfect world, the constant 63 should be 125 (and a #define ;) which, with the prescaler given and a 32MHz clock, should be 1ms.  However, between natural delays in the code and the particular servos I was using, I calibrated that offset to be 63 to get the maximum range.  YMMV.
  • Raises the servo signal pin.  This macro is defined in my header file, and is pretty simple: #define SET_SERVO1 PORTD.OUTSET = PIN5_bm
  • Enables the interrupt
  • Sets the period.  You must use the TC_SetPeriodBuffered for this particular application (can’t remember why off the top of my head).
  • Set the clock source to enable the timer.

Did you notice a subtle difference between the TimerD1 and TimerE0 code?  There are two types of timers in the XMegas – Timer0’s and Timer1’s.  Hence, there are TC0_ prefixes and TC1_ prefixes that must be used for appropriate timers.  I’ll have to refer you to the datasheet, because once again I can’t remember what the difference is – it’s not relevant for this example.

Last, let’s look at the position timer ISR code:

/*! \brief Timer/CounterD1 Overflow interrupt service routine
 *
 *  Timer D1 overflow interrupt service routine.
 *  Ends the Servo1 position pulse and disables itself.
 */
ISR(TCD1_OVF_vect)
{
    CLR_SERVO1;                                            // Reset the servo pin low
    TC1_SetOverflowIntLevel( &TCD1, TC_OVFINTLVL_OFF_gc ); // Clear Interrupt on Overflow
    TC1_ConfigClockSource( &TCD1, TC_CLKSEL_OFF_gc );      // Stop clock
}

There’s not much to it:  Reset the signal low, disable the timer interrupt, and disable the timer clock source.  This all gets re-enabled in about 18-19ms!

Now, from just about anywhere in your program, when you want to update the servo position, you just have to change the value of the appropriate servo[] index.  Here’s an example main loop:

int main(void) {
    init_clock();
    init_io();
    init_timers();

    int i;
    for (i=0 ; i<20 ; i+=10)
    {
        servo[1] = i;
        servo[2] = i;
        _delay_ms(1000);
    }
}

I should mention that you need to include “pmic_driver.h” from the PMIC app note AVR1305, and “TC_driver.h” from AVR1306.  Also copy in the matching .c files into the same folder.

That’s really about it.  And it’s easy to add on to this and control another servo:

  1. Add an extra index into the “servos[]” array.
  2. Pick an unused timer and add it to the “timer_init()” function.
  3. Cut, paste, and modify timer code for the new timer in the 20ms timer ISR.
  4. Add an ISR for the new timer, again cutting, pasting, and modifying for the new servo.

That about does it.  It seems to work pretty well for my uses.  Make sure and calibrate the limits on your servo variables so that you maximize your reach (for the servos I tested it with, it was between 0 and 270) .  One problem I had was erratic behavior when it collided with another timer occasionally – this was fixed by adding lifting this up from the “LO” level interrupts to the “MED” level interrupts.  It could also be moved even higher to avoid problems even further.  This is an issue with the XMega, where it wouldn’t necessarily have been with an ATMega, because XMega interrupts have changed and a higher level interrupt can interrupt a lower level interrupt.  Good to keep in the back of your mind as you code…

As always, let me know in the comments if you need any help setting this up.

Advertisements
Categories: How-To Tags: , , , ,

Hacking Tiny Servos

Thought I would blog about hacking the 9g servos from SparkFun and converting them to continuous rotation since I’m performing said hack for an upcoming project.  I’m sure there are more and better instructions on how to do this, but I’ve looked at tutorials before for larger servos and noticed these little guys are a bit different.

What I’m doing is changing a servo that normally has around 90-120 degrees of motion to have full 360 continuous rotation.  Instead of the servo reacting to the standard PWM signal as a position signal, it will now act as a velocity signal with full forward/stop/reverse control!  Best of all, when people tell you the servos on your YT-1300 look like a piece of junk, you can tell them you’ve made a few special modifications yourself.

Apology in advance for the images – I promise to never use my cell phone cam for blogging again!  It’s abysmally unfit for close up pictures of electronics.


Take the servo apart

The black circular piece with the shaft in it, next to the gears, is the potentiometer. Two stops are there, two more are on the case piece to the left of it.

Remove the sticky label (goo-be-gone is needed) and remove the screws.  You’ll likely need a jewelry screw driver.  Pull all three sections apart, remove the gears, and remove the white plastic cover over the potentiometer.  Easy peasy.

Cut out the stops

I’m running on memory, so feel free to correct me in comments if I’m wrong.  I recall this as the part that’s different from the standard sized servos.  There are stops on the potentiometer and you practically have to cut out half of the pot to get it to rotate properly.  This is because the pot shaft is used as the axis for the gears and external drive shaft.  Boo to the man that designed it this way, unless it made it significantly cheaper.  In that case, yay… but boo.

Warning: The plastic on the pot is EXTREMELY brittle.  If the pot case crumbles, your servo is trashed.

Proceeding onward.  You’ll see two plastic stops in the pot – cut it out in small pieces.  Once that’s gone, you’ve got to remove the potwiper.  The copper-colored middle terminal of the pot should come out with a little force at this point.  There are a couple of “C” shaped metal tabs that will come out with it.  Now cut the entire terminal off and leave the wire hanging for now.

On one of my servos, I cut it out without pulling the shaft out.  On another, the shaft hole cracked cand the whole shaft came out which made this easier – but I’ll have to report later as to whether it still works well or not.

You also need to cut out the springy piece.  You can’t remove it completely, it appears to be connected with the shaft itself, so you’ll need to cut it flush against the remaining metal flange.

The final stop that has to be cut is on the actual case, just inside the hole where the shaft exits the case.  Flush cutters make quick work of this, but an Xacto knife could probably work too.

Solder resistors in place of pot

With the middle terminal removed and cut off, solder resistors between the two terminals and then solder the middle terminal wire to the middle of the resistors.

So that the potentiometer has a reference point for its control loop, we need to solder resistors in place of the pot.  I used two 10K resistors, which will make the signal that would have centered the servo be the stop position.  Positions signals right from the signal will be forward, and positions left from the signal will be reverse.

Solder the two 10K resistors (surface mount or 1/8W or smaller should fit) in series between the two remaining posts.  Then solder the currently-floating middle terminal wire in between the two resistors.

Put the servo back together

Pretty straightforward.  Do step one in reverse.  The gears may feel like a puzzle, but there’s only one way they fit.  Lastly, be careful with the screws – they strip out pretty easy.


Shazaam!  Full control of a motor with great torque/gear ratio with only a half hour’s worth of effort.