1. Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.
    Dismiss Notice

PWM for servo control

Discussion in 'AVR' started by magvitron, Mar 1, 2013.

  1. magvitron

    magvitron Active Member

    Joined:
    Jan 11, 2013
    Messages:
    257
    Likes:
    28
    Location:
    John Lennon !magine land.
    Hi Im doing a small project for servo control using the avr mega 16. I am doing a phase correct PWM with timer 2 of the avr. the thing is driving me crazy.
    here's the code:
    F_CPU = 11.0592Mhz
    Code (text):
    void t2_fastpwm_init()  // initialization for Phase Correct PWM signal using timer 2
    {      
        // WGM2[1:0]= 11, for Fast PWM mode
        // COM2[1:0]= 10, to select non inveting mode
        // CS2[2:0] =010. for prescaler=8
       // TCCR1A  = (0<<COM1A0)|(1<<COM1A1)|(0<<COM1B0)|(0<<COM1B1)|(0<<FOC1A)|(0<<FOC1B)|(1<<WGM11)|(0<<WGM10);
        //TCCR1B =  (0<<ICNC1)|(0<<ICES1)|(1<<WGM13)|(1<<WGM12)|(0<<CS12)|(0<<CS11)|(1<<CS10);
        TCCR2=(1<<FOC2)|(0<<WGM20)|(1<<WGM21)|(0<<COM20)|(1<<COM21)|(2<<CS20);
        DDRD|=(1<<PD7);     // selcet OC2 as output pin
        TIMSK|=(1<<OCIE2);  //enable Output compare interrupt
    }
    how can i get a 50Hz wave for the control. and how can i control that. i have done the same with timer 1 with 1 Mhz Internal clock. but need this odd frequency for a better UART communication. and timer 1 is used as a counter, so I cant access that too. any help?
     
  2. dougy83

    dougy83 Well-Known Member

    Joined:
    May 18, 2008
    Messages:
    2,675
    Likes:
    215
    Location:
    Brisbane, Australia
    If you use phase-correct or fast-pwm modes, the closest frequency will be 42Hz. To get a 1ms output pulse, OCR2 would be set to 10 and for a 2ms pulse OCR2 would be 21. You will then only have 12 discrete position steps over this range - is this ok?

    If you want an exact 50Hz, you can use CTC mode with a prescaler of 1024, OCR2=TOP=215. You then have to use an compare interrupt to create the pulse, during which you set the prescaler to 128 and OCR2 between 172 and 64 for a pulse between 1ms and 2ms. This allows you to have a better resolution of your pulse width; 109 steps rather than the 12 steps above.

    Regarding your code:
    FOC2 should be 0 for PWM mode.
    You have specified CTC, not phase-correct with WGM20/21 = 0/1
     
  3. magvitron

    magvitron Active Member

    Joined:
    Jan 11, 2013
    Messages:
    257
    Likes:
    28
    Location:
    John Lennon !magine land.
    hi thanks for the reply dougy83.. i modified the code but its not working :(
    Code (text):


    //-------------------------

    #include <avr/io.h>
    #include<avr/interrupt.h>
    #include<util/delay.h>
    int count;
    int main(void) {

    //Enable internal pull ups

    PORTD=0xFF;
    sei();
    DDRD=0xFF;
    DDRB=0xff;

    //TOP=ICR1;

    //ICR1=20000 defines 50Hz PWM
    TCCR2|=(0<<FOC2)|(1<<WGM20)|(0<<WGM21)|(1<<COM20)|(1<<COM21)|(1<<CS20)|(1<<CS21)|(1<<CS22);
        DDRD|=(1<<PD7);     // selcet OC2 as output pin
        TIMSK|=(1<<OCIE2);  //enable Output compare interrupt

    //TCCR2|=(0<<FOC2)|(1<<WGM21)|(0<<WGM20)|(0<<COM21)|(0<<COM20)|(1<<CS20)|(1<<CS20)|(1<<CS20);
    OCR2=215;
    while(1) {
    //OCR2=62;
     
    }
    }

    ISR(TIMER2_COMP_vect)
    {
    TCCR2|=(0<<FOC2)|(1<<WGM20)|(0<<WGM21)|(1<<COM20)|(1<<COM21)|(1<<CS20)|(0<<CS21)|(1<<CS22);
    OCR2=172;
    //TCCR2|=(
       
        PORTB ^= 0x01;
       
    }


     
    can you help me??!
     
  4. dave

    Dave New Member

    Joined:
    Jan 12, 1997
    Messages:
    -
    Likes:
    0


     
  5. misterT

    misterT Well-Known Member Most Helpful Member

    Joined:
    Apr 19, 2010
    Messages:
    2,697
    Likes:
    368
    Location:
    Finland

    For servo control you don't need accurate 50Hz. The signal can be faster or slower. The important part is the length of the pulse. That is what controls the servo position. I would try fast PWM with prescaler 256.

    The pulse rate is ~169Hz.. That might be too fast for your servo. I can show you a trick how to reduce the frequency if this code does not work. Please try it and report back.

    Code (text):

    #include <avr/io.h>

    int main(void) {
        DDRB|=(1<<PB3); // selcet OC0 as output pin

        // Fast PWM, TOP = 255, Prescale = 256, F_CPU = 11.0592Mhz
        TCCR0 |= (0<<FOC0)|(1<<WGM00)|(1<<WGM01)|(0<<COM00)|(1<<COM01)|(0<<CS00)|(0<<CS01)|(1<<CS02);

        // This gives you ~1ms pulse. 43.2 is accurate for 1ms.
        OCR0=43;
       
            while(1) { }
    }
     
    NOTE: This uses Timer/Counter0, The output will be on pin PB3.
     
    Last edited: Mar 6, 2013
  6. misterT

    misterT Well-Known Member Most Helpful Member

    Joined:
    Apr 19, 2010
    Messages:
    2,697
    Likes:
    368
    Location:
    Finland
    Removed for complete re-write.
     
    Last edited: Mar 6, 2013
  7. dougy83

    dougy83 Well-Known Member

    Joined:
    May 18, 2008
    Messages:
    2,675
    Likes:
    215
    Location:
    Brisbane, Australia
    It's not really an issue as the interrupt rate is so low, but if you modify the prescaler rather than skipping output pulses for a constant interrupt rate there will be less time wasted in the interrupt. Additionally, a lower prescaler may be used for the actual pulse to provide finer resolution of the pulse width.

    There are two interrupts for Timer2 - the overflow (vector 5) and compare (vector 4).
     
  8. misterT

    misterT Well-Known Member Most Helpful Member

    Joined:
    Apr 19, 2010
    Messages:
    2,697
    Likes:
    368
    Location:
    Finland
    You are right.. I got mixed up with all the pages when reading the datasheet. Sorry about that. I really don't know what happened in my head.

    I don't see how your method gives finer resolution. You still have the same limits.. max OCR2 is 255. My flaw was to use the Timer0, which does not have a prescaler of 128.

    My method gives more accurate and consistent pulses, because the ISR call overhead does not affect the timing, unlike in your method. My method is also more convenient and safer, because you can modify OCR2 at anytime and anywhere in the code in order to change the pulse width. In your method you can only modify OCR2 when the interrupt is triggered. Which means that you need a global variable to hold the actual PWM value you want (and you need to disable interrupts every time you modify that global).

    Here is my code that uses Timer/Counter2.
    Code (text):

    #include <avr/io.h>
    #include<avr/interrupt.h>

    // Volatile keyword is important
    volatile char count=0;

    // We use this ISR to leave only every sixth pulse and remove the rest.
    // This reduces the PWM frequency to ~56Hz.
    ISR(TIMER2_COMP_vect) {
        if (count){ // Disconnect the OC2 to reduce pulse rate
            TCCR2 &= ~(1<<COM21);
            count--;
        }
        else { // Connect the OC2 (PD7) for this pulse
            TCCR2 |= (1<<COM21);
            count=6;
        }
    }

    int main(void) {
        DDRD|=(1<<PD7); // selcet OC0 as output pin
        // Fast PWM, TOP = 255, Prescale = 128, F_CPU = 11.0592Mhz
        TCCR2 |= (0<<FOC2)|(1<<WGM20)|(1<<WGM21)|(0<<COM20)|(1<<COM21)|(1<<CS20)|(0<<CS21)|(1<<CS22);
        // This gives you ~1ms pulse. (86.4 is accurate value).
        OCR2=86;
       
        // Setup interrupt so we can remove pulses from the pulse train
        TIMSK |= OCIE2;
        sei();
       
            while(1) {
             
            }
    }
     
     
    Last edited: Mar 6, 2013
  9. dougy83

    dougy83 Well-Known Member

    Joined:
    May 18, 2008
    Messages:
    2,675
    Likes:
    215
    Location:
    Brisbane, Australia
    It only gives finer resolution if you're using a prescaler of 256. The pulses are just as accurate with either method; the gaps can be less consistent with the method I outlined, but the frequency can be adjusted more readily. I don't see any added safety in accessing OCR2 directly from main code rather than the interrupt. I don't think it's particularly more convenient either.

    In summary, either approach is completely usable.

    Code (text):
    #include <stdint.h>

    #define XTAL            11059200
    #define LOW_PWM     (uint8_t)(0.001 * XTAL / 128 - 0.5)
    #define HIGH_PWM    (uint8_t)(0.002 * XTAL / 128 - 0.5)
    #define COUNT_50HZ  (uint8_t)(0.020 * XTAL / 1024 - 0.5)

    #define setPwmPercent(pc)   do{pwmValue = LOW_PWM + pc * (HIGH_PWM - LOW_PWM) * 5 / 512;}while(0)

    volatile uint8_t pwmValue;

    void initPwm()
    {
        PORTD &= ~(1<<7);                           // output defaults to low when PWM disabled
        DDRD |= (1<<7);
        setPwmPercent(50);                      // center pulse width
        TCCR2 = (1<<WGM20) | (1<<WGM21) |           // fast PWM
            (1<<CS22) | (1<<CS21) | (1<<CS20);  // 1:1024 prescaler
        TIMSK |= (1<<OCIE2);
    }

    ISR(TIMER2_COMP_vect)
    {
        if(TCCR2 & (1<<COM21))      // just finished a pulse?
        {
            TCCR2 = (1<<WGM20) | (1<<WGM21) |           // fast PWM
                (1<<CS22) | (1<<CS21) | (1<<CS20);  // 1:1024 prescaler
            OCCR2 = COUNT_50HZ;     // interrupt again in 50ms
            TCNT = 255;             // force rollover
        }
        else
        {
            OCR2 = pwmValue;
            TCCR2 = (1<<WGM20) | (1<<WGM21) |           // fast PWM
                (1<<COM21) |                        // non-inverted PWM
                (1<<CS22) | (1<<CS20);              // 1:128 prescaler     
        }  
    }

    int main()
    {
        initPwm();
       
        while(1)
        {
            setPwmPercent(** some value **);
            .. do some other stuff..
        }

        return 0;
    }
     
    Last edited: Mar 6, 2013
  10. misterT

    misterT Well-Known Member Most Helpful Member

    Joined:
    Apr 19, 2010
    Messages:
    2,697
    Likes:
    368
    Location:
    Finland
    The point was that with your method the user can't access the OCR2 directly. If he does that accidentally it messes up the signal. The Update of
    OCR2 in CTC mode is immediate. In Fast PWM it is "double buffered".
     
  11. dougy83

    dougy83 Well-Known Member

    Joined:
    May 18, 2008
    Messages:
    2,675
    Likes:
    215
    Location:
    Brisbane, Australia
    Silly users... they can just use the setPwmPercent() function provided then, and not touch things they don't understand.
     
  12. misterT

    misterT Well-Known Member Most Helpful Member

    Joined:
    Apr 19, 2010
    Messages:
    2,697
    Likes:
    368
    Location:
    Finland
    ..and the convenience point. Everytime you need to change the pwm in your method, you need to make that operation atomic:

    Code (text):

    cli(); //disable interrupts
    setPwmPercent(** some value **);
    sei(); // enable interrupts
     
    and forgetting to do that is a big safety issue also.. it messes up the signal again.
     
    Last edited: Mar 6, 2013
  13. dougy83

    dougy83 Well-Known Member

    Joined:
    May 18, 2008
    Messages:
    2,675
    Likes:
    215
    Location:
    Brisbane, Australia
    What, in case only 3 of the 8 bits have been copied over? The byte load and store commands are atomic, there's no need for that craziness.
     
  14. misterT

    misterT Well-Known Member Most Helpful Member

    Joined:
    Apr 19, 2010
    Messages:
    2,697
    Likes:
    368
    Location:
    Finland
    Here is the summary.

    - Both have the same resolution.
    - My code produces accurately timed pulses, your pulses have jitter.
    - My ISR is simpler and shorter than yours.
    - My code produces half the interrupts than yours.
    - In my method it is possible to write directly to the OCR2 register to change the pwm.
    - My method is (marginally) safer.

    Sorry for battling over this.. again I'm little bored here. Waiting for the OP to answer now.
     
  15. dougy83

    dougy83 Well-Known Member

    Joined:
    May 18, 2008
    Messages:
    2,675
    Likes:
    215
    Location:
    Brisbane, Australia
    >> - Both have the same resolution.
    Yes.
    >> - My code produces accurately timed pulses, your pulses have jitter.
    This is not an issue. Pulses have the same width.
    >> - My ISR is simpler and shorter than yours.
    There's one extra line in mine... which can be removed. As far as complexity goes, I had no issue understanding either implementation.
    >>- My code produces half the interrupts than yours.
    That's incorrect. My code has two interrupts per pulse. Yours requires six interrupts per pulse.
    >- In my method it is possible to write directly to the OCR2 register to change the pwm.
    That's irrelevant.
    >>- My method is (marginally) safer.
    That's incorrect. Your definition of safety was also kinda retarded: "what if the user changes OCR2". Well, what if "user" sets TCCR2, DDRD, TIMSK or SFIOR incorrectly? Or if they disable interrupts? Or connect the battery backwards?

    >>Sorry for battling over this.. again I'm little bored here. Waiting for the OP to answer now.
    Haha.. it seems like it. I'm not sure the OP will necessarily appreciate our excessive additions to his thread however.

    Oh, not that I'm picking or anything, but your comment "volatile keyword is important" is incorrect. It's superfluous in that context as count is only used in the ISR. It would be required if accessed from both the main ISR and non-ISR code. You could have (and should have) declared count as a static local variable within the ISR if it's not used elsewhere (for "safety reasons" ;P ).
     
  16. magvitron

    magvitron Active Member

    Joined:
    Jan 11, 2013
    Messages:
    257
    Likes:
    28
    Location:
    John Lennon !magine land.
    Thanks both of you for the replies.. i found both the codes are working.. :) the odd clock frequency messed me up ( i chose that for a better UART error rate) So i have a question, in avr gcc there are many Interrupt vectors. where can i get a list of all interrupt vectors? 'cse I some times get an error misspelled interrupt handler.. or something.
     
  17. misterT

    misterT Well-Known Member Most Helpful Member

    Joined:
    Apr 19, 2010
    Messages:
    2,697
    Likes:
    368
    Location:
    Finland
    That would be the Avr-libc documentation.. which is the library you are using:
    http://www.nongnu.org/avr-libc/user-manual/index.html

    Library reference:
    http://www.nongnu.org/avr-libc/user-manual/modules.html

    Direct link to the Interrupts -reference:
    http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html
     
  18. magvitron

    magvitron Active Member

    Joined:
    Jan 11, 2013
    Messages:
    257
    Likes:
    28
    Location:
    John Lennon !magine land.
    avr simulator version is AVR Simulator: 1, 0, 2, 1 is that an issue?! i use the older avr studio avr studio 4. I kinda want the newer version, is there any new things in that? excluding the newer cores and things how about the editor?
     
  19. misterT

    misterT Well-Known Member Most Helpful Member

    Joined:
    Apr 19, 2010
    Messages:
    2,697
    Likes:
    368
    Location:
    Finland
    I don't think you will encounter any issues.. at least not before you switch to using some newer chips. ATmega16 is not very new chip.

    The lates "Atmel Studio 6" is a lot different from the AVR Studio 4.. some like it some don't.
     
  20. magvitron

    magvitron Active Member

    Joined:
    Jan 11, 2013
    Messages:
    257
    Likes:
    28
    Location:
    John Lennon !magine land.
    in the code:
    Code (text):
    #include <avr/io.h>
    #include<avr/interrupt.h>
    #include<util/delay.h>

     
    // Volatile keyword is important
    volatile char count=0;
     
    // We use this ISR to leave only every sixth pulse and remove the rest.
    // This reduces the PWM frequency to ~56Hz.
    ISR(TIMER2_COMP_vect) {
        if (count){ // Disconnect the OC2 to reduce pulse rate
            TCCR2 &= ~(1<<COM21);
            count--;
        }
        else { // Connect the OC2 (PD7) for this pulse
            TCCR2 |= (1<<COM21);
            count=6;
        }
    }
     
    int main(void) {
        DDRD|=(1<<PD7); // selcet OC0 as output pin
        // Fast PWM, TOP = 255, Prescale = 128, F_CPU = 11.0592Mhz
        TCCR2 |= (0<<FOC2)|(1<<WGM20)|(1<<WGM21)|(0<<COM20)|(1<<COM21)|(1<<CS20)|(0<<CS21)|(1<<CS22);
        // This gives you ~1ms pulse. (86.4 is accurate value).
        OCR2=86;
     
        // Setup interrupt so we can remove pulses from the pulse train
        TIMSK |= OCIE2;
        sei();
     
            while(1)
            {
             OCR2=OCR2+10;
             _delay_ms(200);
             if(OCR2==160)
             {
               OCR2=86;
             }
            }
    }
     
    for the servo to move from 0 to 180; which values do i have to write to OCR2?
    is the equation for that: f_desired=f_cpu/2N(1+OCR2) ??
     
  21. misterT

    misterT Well-Known Member Most Helpful Member

    Joined:
    Apr 19, 2010
    Messages:
    2,697
    Likes:
    368
    Location:
    Finland
    Most servos can take pulses from 0.75ms to 2.25ms, where the shortest pulse is "0 degrees" and the longest is "180 degrees".

    In my code the 0.75ms pulse is:
    OCR2 = 65;

    and the 2.25ms pulse is
    OCR2 = 195;

    Anything between those values maps linerily between 0...180 degrees.

    EDIT:
    The equation would be:
    OCR2 = 65 + ((130 * degrees) / 180);

    Different models of servos behave little differently, so that is only approximately 0 to 180 degrees.
     
    Last edited: Mar 6, 2013

Share This Page