PWM, servo and jitter

Go To Last Post
18 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What is the problem with jitter and PWM?
How can I filter a normal i/o, so that I have no more jitter?
I need 12 PWM signals, I can take 12 tiny's or 2 mega128's, then I have real PWM outputs, but cheaper is take a single AVR.
I have 8 PWM signals on PORTD of a mega8, but it seems that I have jitter, and I cant to control servo's. Is there some way to filter this jitter? (software filter seems impossible because the pin switches.)

:roll:

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Once you have "jitter", you are pretty much stuck with it.

How are the PWMs with jitter created? Code or timer hardware?

If its code, then you need to make sure that the time delay between the timer and the port pin is always the same. That may mean allowing only one int (the timer) and minimizing paths with varying execution sequences. One way to do this is "precompute" the action on the next timer int. Once the port-pin is changed (right at the top of the ISR), then determine what will be done on the next int. Since this does not add to the delay between the timer and the port change, it markedly reduces jitter.

Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've not done it myself (not on an AVR anyway) but if you do what ka7ehk suggests, and if you can get all your other stuff done in time, then it may be possible to put the AVR into a sleep mode, which should give you a predictable latency. (I think).

Four legs good, two legs bad, three legs stable.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It sounds like you're trying to generate 8 synchronized PWM signals. Real world RC receivers aren't synchronized they're chained. Simply generate a servo pulse and then just run it on each channel individually one after another. The servo pulses are (shouldn't be) more than about 2.25ms apart (and that's at 150% throws) you can get 8 channels easily into that period of time at a 50hz refresh rate. This means that each channel will be at least 1ms or 2ms delayed compared to the previous one but again this is how the majority of RC receivers work.You could do this with a single timer interupt. I think if you were very careful and staggered a second timer 1ms ahead of the first one you might be able to eck out another 8 channels but I can't see the code timeing in my head well enough to be sure there wouldn't be any jitter. Basically you just have to garuntee that no matter when a pulse starts or stops no other pulse starts or stops near enough to it to get into timeing contention.

-Curiosity may have killed the cat
-But that's why they have nine lives

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If I understand your situation correctly, what you want to do is very easy.
Am I correct that you want 8 pwm outputs that have a pulse repition rate
of 50 Hz, and that you want the leading edges (starts) of all 8 to be outputted
on the port at the exactly the same time, and that the trailing edges of each
pulse occur at whatever time you specify?

For example, to implement eight 0-100% 50 Hz channels:

1) setup a single timer interrupt service at a 200 us interval
2) implement a "flag" byte that will represent the 8 output bits
3) implement 8 software counters that get reset simultaneously
every 20 ms, and otherwise are incremented every 200 us
4) every 200 us, go through 8 conditional statements that compare each
"timer" byte with each channel's "setting" byte, setting or clearing
each channel flag bit according to whether that channel's
desired pulse width time has been reached
5) at the top of the service routine, copy the "flag" byte to the port.

Even though the timer resettings, incrementings, conditionals, etc.
are "strung out", the changes on the port pins are perfectly
sychronized and essentially jitter free. All this happens within the
interrupt service routine in the "background". The foreground portion
of your program only needs to stick the desired servo position
values into the "setting" variables as needed

Tom Pappano
Tulsa, Oklahoma

Tom Pappano
Tulsa, Oklahoma

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

200us would only give you 10 steps of resolution.. that's not particularly useful, you'd need at least 10us to be useful (200 steps) At 16mhz this would work but your interupt service routine would have to be less than 160 instructions to keep the 10us polling rate. This wouldn't leave much time left for other code, but would work. Keep in mind unless you're doing some pretty serious stuff there's no reason to generate servo signals in sync like this.

-Curiosity may have killed the cat
-But that's why they have nine lives

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
200us would only give you 10 steps of resolution.. that's not particularly useful, you'd need at least 10us to be useful (200 steps) At 16mhz this would work but your interupt service routine would have to be less than 160 instructions to keep the 10us polling rate. This wouldn't leave much time left for other code, but would work. Keep in mind unless you're doing some pretty serious stuff there's no reason to generate servo signals in sync like this.

50 Hz has a 20 ms period, which contains 100 200us increments. To process eight,
16, or more channels this way is fairly trivial for an Avr even running at 1 or 2 Mhz. The
main exec won't even notice it's happening.

Tom Pappano
Tulsa, Oklahoma

Tom Pappano
Tulsa, Oklahoma

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

50 Hz has a 20 ms period, which contains 100 200us increments.

True, but I believe that the hobby servos use a pulse up to 2ms during each 20ms period. Thus the "200us is 10 steps" [in 2ms].

Lee

Lee

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
True, but I believe that the hobby servos use a pulse up to 2ms during each 20ms period. Thus the "200us is 10 steps" [in 2ms].

Ahh... that changes things a little! My first scheme, which was a "full range"
synchronous setup, would use about 50 cpu cycles. This would indeed keep
a 8 Mhz cpu about 75% busy running a 10us interrupt for 1% positional resolution.

Now I'm liking a 8 channel "jitter-free" sequential method better.

TP

Tom Pappano
Tulsa, Oklahoma

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Something like this should work pretty efficiently and be "jitter free"

// Atmega8 Pwm Hobby Servo Controller
// Software pwm on pb0-7 modulate servos 0-7
// Pulse width range 1-2ms
// 8 Mhz clock
// By Thomas Pappano 2005

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

volatile uint8_t     srv0pwm=0; // servo 0 PWM setting 200=2ms
volatile uint8_t     srv1pwm=0; // servo 1 PWM setting
volatile uint8_t     srv2pwm=0; // servo 2 PWM setting
volatile uint8_t     srv3pwm=0; // servo 3 PWM setting
volatile uint8_t     srv4pwm=0; // servo 4 PWM setting
volatile uint8_t     srv5pwm=0; // servo 5 PWM setting
volatile uint8_t     srv6pwm=0; // servo 6 PWM setting
volatile uint8_t     srv7pwm=0; // servo 7 PWM setting

register uint8_t  srvmod asm("r14"); // Force use of registers for speed
register uint8_t channel asm("r15"); //
register uint8_t   flags asm("r16"); // Use bit operable register for speed

#define t0rate            176  // timer0 80 count setting for 10us
#define srvtop            250  // pwm top count for 50 Hz servo pwm rate

#define srv0             0x01  // servo 0 on PB0
#define srv1             0x02  // servo 1 on PB1
#define srv2             0x04  // servo 2 on PB2
#define srv3             0x08  // servo 3 on PB3
#define srv4             0x10  // servo 4 on PB4
#define srv5             0x20  // servo 5 on PB5
#define srv6             0x40  // servo 6 on PB6
#define srv7             0x80  // servo 7 on PB7

SIGNAL (SIG_OVERFLOW0)                         // Timer0 interrupt service 10us
{                                              // (+17 cycles)
    TCNT0 = t0rate+TCNT0;                      // reset timer0
    PORTB = flags;                             // update pwm outputs
    if(channel==0)
    {
        if(++srvmod>srvtop)                    // Channel 0 finished?
        {                                      // 
            srvmod=0;                          //
            flags &= ~srv1;                    // Turn *next* output on
            if(++channel>7)channel=0;
        }                                      //
        else
        {                                      //
            if(srvmod>srv0pwm)flags |= srv0;   // Turn *this* output off? 
        }
    }
    if(channel==1)
    {
        if(++srvmod>srvtop)                    // Channel 1 finished?
        {                                      // 
            srvmod=0;                          //
            flags &= ~srv2;                    // Then turn *next* output on
            if(++channel>7)channel=0;
        }                                      //
        else
        {                                      //
            if(srvmod>srv1pwm)flags |= srv1;   // Turn *this* output off? 
        }
    }
    if(channel==2)
    {
        if(++srvmod>srvtop)                    // Channel 2 finished?
        {                                      // 
            srvmod=0;                          //
            flags &= ~srv3;                    // Then turn *next* output on
            if(++channel>7)channel=0;
        }                                      //
        else
        {                                      //
            if(srvmod>srv2pwm)flags |= srv2;   // Turn *this* output off? 
        }
    }
    if(channel==3)
    {
        if(++srvmod>srvtop)                    // Channel 3 finished?
        {                                      // 
            srvmod=0;                          //
            flags &= ~srv4;                    // Then turn *next* output on
            if(++channel>7)channel=0;
        }                                      //
        else
        {                                      //
            if(srvmod>srv3pwm)flags |= srv3;   // Turn *this* output off? 
        }
    }
    if(channel==4)
    {
        if(++srvmod>srvtop)                    // Channel 4 finished?
        {                                      // 
            srvmod=0;                          //
            flags &= ~srv5;                    // Then turn *next* output on
            if(++channel>7)channel=0;
        }                                      //
        else
        {                                      //
            if(srvmod>srv4pwm)flags |= srv4;   // Turn *this* output off? 
        }
    }
    if(channel==5)
    {
        if(++srvmod>srvtop)                    // Channel 5 finished?
        {                                      // 
            srvmod=0;                          //
            flags &= ~srv1;                    // Then turn *next* output on
            if(++channel>6)channel=0;
        }                                      //
        else
        {                                      //
            if(srvmod>srv5pwm)flags |= srv5;   // Turn *this* output off? 
        }
    }
    if(channel==6)
    {
        if(++srvmod>srvtop)                    // Channel 6 finished?
        {                                      // 
            srvmod=0;                          //
            flags &= ~srv7;                    // Then turn *next* output on
            if(++channel>7)channel=0;
        }                                      //
        else
        {                                      //
            if(srvmod>srv6pwm)flags |= srv6;   // Turn *this* output off? 
        }
    }
    if(channel==7)
    {
        if(++srvmod>srvtop)                    // Channel 7 finished?
        {                                      // 
            srvmod=0;                          //
            flags &= ~srv0;                    // Then turn *next* output on
            if(++channel>7)channel=0;
        }                                      //
        else
        {                                      //
            if(srvmod>srv7pwm)flags |= srv7;   // Turn *this* output off? 
        }
    }
}

void ioinit (void)            // Register initialization
{
    EEAR    = 0x7d;           // move 2mhz cal byte from e2 to eear
    EECR   |= 0x01;           // start eeprom read operation
    while(EECR&0x01);         // wait until byte ready
    OSCCAL  = EEDR;           // then write it to osccal
    TCCR0   = 0x01;           // start timer0, ck/1 (.125us ticks)
    TCNT0   = t0rate;         // 10us t0 irq rate
    TIMSK  |= 0x01;           // enable timer0 overflow interrupt
    ACSR    = 0x80;           // analog comp off
    DDRB    = 0xff;           // servo 0-7 pwm outputs
    PORTB   = 0xff;           // servo outputs initially off
    flags   = 0xff;
    sei();
}

int main (void)
{
    ioinit();
    for (;;)
    {
                              // Main exec goes here
    }
}



Tom Pappano
Tulsa, Oklahoma

Tom Pappano
Tulsa, Oklahoma

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Searching for PWM Jitter on the forums found me this antique thread (2005!), but didn't solve my problem. I had a nice snappy little interrupt routine (all it did was toggle a pin - "sbi PINC, 3" and "reti") and no program at all ("MAIN: rjmp MAIN") and it still jittered all over.

However, I did manage to fix it, and thought I should put in my solution for anyone looking for the same:

Use an external crystal.

My jitter went from ~40uS to less than 100ns (one clock cycle) when I used a proper external Xtal (a full can). Apparently the internal AVR RC oscillator is really lousy for frequency stability. Of course, this isn't mentioned in the spec sheets...
(At least, I didn't find it in there). Additionally, now I can go even faster!

Scroungre

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

RoBSki! wrote:
What is the problem with jitter and PWM?
How can I filter a normal i/o, so that I have no more jitter?
I need 12 PWM signals, I can take 12 tiny's or 2 mega128's, then I have real PWM outputs, but cheaper is take a single AVR.
I have 8 PWM signals on PORTD of a mega8, but it seems that I have jitter, and I cant to control servo's. Is there some way to filter this jitter? (software filter seems impossible because the pin switches.)

:roll:

Have you looked at:
http://www.lynxmotion.com/Product.aspx?productID=395&CategoryID=52

and this:
http://www.lynxmotion.com/Product.aspx?productID=624&CategoryID=52

and most importantly:
http://www.lynxmotion.com/images/html/proj078.htm

You can avoid reality, for a while.  But you can't avoid the consequences of reality! - C.W. Livingston

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

For best performance, implement all PWMs internally, collect their outputs in a register and write that register periodically to the port (double buffering). This ensures that all outputs change state simultaneously.

You could do this buffering in an external transparent latch as well.

This is a lot easier if your PWMs have some coherent relationship with one another. Otherwise you lose resolution depending on the update rate you select.

If you think education is expensive, try ignorance.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

I had a nice snappy little interrupt routine (all it did was toggle a pin - "sbi PINC, 3" and "reti") and no program at all ("MAIN: rjmp MAIN") and it still jittered all over.

Quote:

Additionally, now I can go even faster!

It would be easier to comment if the aim was known.

Internal oscillator jitter was thrashed-out quite thourouhly a while back. Tell which AVR model you are using.

Now tell more about "go faster". If this is some kind of interrupt source that is repeatedly triggering, then in the general case you will always have jitter looking at the pin, since the current instruction must be completed.

Is your aim to toggle a pin as fast as possible? Then many models have CLKO pin. CTC can give you clk/2 with no jitter. What is your goal again?

On the AVRs with a PLL & fast PWM, can that frequency be set faster than the AVR clock speed itself?

Lee

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

http://www.lynxmotion.com/images/html/proj078.htm

32 servo outputs, jitter free...

Or is that just too simple?

You can avoid reality, for a while.  But you can't avoid the consequences of reality! - C.W. Livingston

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Folks,

What I was doing was making my own control board for a LynxMotion arm. It works, and involves lots more funky control algorithms (think reverse kinematics done in the AVR) than the LynxMotion board, although I'll grant fewer channels.

I noticed the LynxMotion board also uses an external crystal.

What I meant by 'Go Faster' was that the AVR internal oscillator (at the time I was using a Tiny2313) isn't nearly as fast as the chip itself can run. With an external crystal, I can run my funky algorithms faster, and thus get a larger servo update per pulse time.

I'm using 16-bits of position precision, so calculating fast is a good thing.

Anyhow, I'd almost forgotten this thread - Thought I should wrap it up, since I lit it up again a year and a half ago.

S.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

PS - The real problem was that the servo pulse itself wasn't stable in length for a constant-width command with a flaky clock speed. The arm wobbled. With a crystal, suddenly it got all calm and quiet.

S.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I had the same problem with the internal clock; switching to a real crystal removed all jitter that wasn't being caused by code bugs :)

Sounds like we're working on similar projects; I'm in the process of finishing a custom servo controller for my hexapod robot project. My controller allows for moving groups of servos together so they all arrive at the same time, querying where servos are (even if they're currently moving from one position to another), moving servos at various user controllable speeds and stopping servos mid move (useful for when a sensor on a leg tells you that you really dont want to try and finish that step you're taking). Details of my design decisions and AVR assembler source code here: http://www.lhexapod.com/blog/