简体   繁体   中英

Setting up multiple timers with AVR

I am trying to set up two timer interrupt routines with Teensy 2.0 Microcontroller (which is based on ATMEGA32U4 8 bit AVR 16 MHz) for independent control of two servo motors

After much trial - I was able to set one up on pin 7 of port C, but

  1. How do I set up the second ISR to be initialized and called independently of the first?
  2. Do I need to setup the second timer and, if so, what would such code look like?

Here is the setup code:

int main(void)
{
    DDRE = 0xFF; 

    TCCR1A |= 1 << WGM12;       // Configure timer 1 for CTC mode 
    TCCR1B = (1<<WGM12) | (1<<CS11) ;

    OCR1A = 1000;   // initial            

    TIMSK1 |= 1 << OCIE1A;      // Output Compare A Match Interrupt Enable 
    sei();                      // enable interrupts 

    // ...code that sets pulseWidth based on app logic variable. 
    // Not showing as its not important
}


ISR(TIMER1_COMPA_vect)
{ 
    if (0 == pulseWidth)
    {
        return;
    }

    static uint8_t state = 0;
    int dutyTotal = 20*1000;    

    if (0 == state)
    {
        PORTC |= 0b10000000; 
        OCR1A = pulseWidth; 
        state = 1;
    }
    else if (1 == state)
    {
        PORTC &= 0b01111111; 
        OCR1A = dutyTotal - pulseWidth; 
        state = 0;
    }
}

While it's difficult to give a definitive answer without knowing more about your application (eg what kind of servo/motor, - I'm guessing model RC type with 1-2ms pule?) there are two approaches to solving the problem:

Firstly, In your code you seem to be manually generating a PWM signal by toggling PC7. You could add another output by increasing your number of states - you need one more than the number of servos to give the gap which sets the pulse repetition frequency. This is a common technique when you need to drive a lot of servos, since most RC servos don't care about pulse phasing or frequency (within limits), only the pulse width, so you can generate a bunch of different pulses one after the other on different outputs while only using one timer like this (in a sort of pseudo-code state diagram):

State 0:
Turn on output 1
Set timer TOP to pulse duration 1.
Go to state 1:

State 1:
Turn off output 1
Turn on output 2
Set timer TOP to pulse duration 1.
Go to state 2:

State 2:
Turn off output 2
Set timer TOP to pulse duration 3.
Go to state 0:

"Pulse duration 3" sets the PRF (pulse repetition frequency). If you want get fancy, you can set this to 1/f-pd1-pd2, to give a constant frequency.

[ "TOP" is AVR-speak for the thing that sets the wrap (overflow) rate of the timer. See data sheet. ]

Secondly, there is a much easier way if you're only using two servos - use the hardware PWM functionality of the timer. The AVR timers have a built-in PWM function to do the pin-toggling for you. Timer1 on the mega32 has two PWM output pins, which could work great for your two servos and you then don't (necessarily) need an interrupt handler at all. This is also the right solution if you are PWM driving motors directly (eg through an H-Bridge.)

To do this, you need to put the timer into PWM mode and enable the OC1A and OC1B output pins, eg

/*
 * Set fast PWM mode on OC1A and OC1B with ICR1 as TOP
 * (Mode 14)
 */
TCCR1A = (1 << WGM11) | (1 << COM1B1) | (1 << COM1A1);
TCCR1B = (3 << WGM12);

/*
 * Clock source internal, pre-scale by 8
 * (i.e. count rate = 2MHz for 16MHz crystal)
 */
TCCR1B |= (1 << CS11);

/*
 * Set counter TOP value to set pulse repetition frequency.
 * E.g. 50Hz (good for RC servos):
 * 2e6/50 = 40000. N.B. This must be less than 65535.
 * We count from t down to 0 so subtract 1 for true freq.
 */
ICR1 = 40000-1;

/* Enable OC1A and OC1B PWM output */
DDRB |= (1 << PB5) | (1 << PB6);

/* Uncomment to enable TIMER1_OVF_vect interrupts at 50Hz */
/* TIMSK1 = (1 << TOV1); */

/*
 * Set both servos to centre (1.5ms pulse).
 * Value for OCR1x is 2000 per ms then subtract one.
 */
OCR1A = 3000-1;
OCR1B = 3000-1;

Disclaimer - this code fragment compiles but I have not checked it on an actual device so you may need to double-check the register values. See the full datasheet at http://www.atmel.com/Images/doc7766.pdf

Also, you perhaps have some typos in your code, bit WGM12 doesn't exist in TCC1A (you've actually set bit 3, which is FOC1A - "force compare", see datasheet.) Also, you are writing DDRE to enable outputs on port E but toggling a pin on port C.

Halzephron, thank you so much for your answer. I dont have the high enough reputation to mark your answer, hopefully someone else will.

Details below:

You are absolutely right about being able to use a single IRS to control a number of servos - embarrassing I did not think of that, but given how well your simpler solution worked - I'll just use that.

... Also, you are writing DDRE to enable outputs on port E but toggling a pin on port C.

Thanks, I commented out that line, and it works the same - (so I dont need to enable output at all?)

Bit WGM12 doesn't exist in TCC1A (you've actually set bit 3, which is FOC1A - "force compare", see datasheet.)

I removed that too, but leaving the rest of my code unchanged - it results in servos moving slower, with way less torque and jittering as they do, even after arriving at desired position. Servo makes a weird "shaky" noise (frequency ~10-20, I'd say) and the arm it trembling, so for the reasons I don't understand - setting this bit seems necessary.

I suspected that a timer per motor is highly inelegant, and so I really like your second approach (built - in timer-generated PWM),

... This is also the right solution if you are PWM driving motors directly (eg through an H-Bridge.)

Very curious, why? Ie what's the difference between using this method vs, say, timer-generated PWM.

In your code, I had to change below line to get 50HZ, otherwise I was getting 25HZ before (verified with a scope) ICR1 = 20000-1; // was 40000 - 1;

One other thing I noticed with scope was that the timer-generated PWM code I have - produces less "rectangular" shape than the PWM code snippet you attached. It takes about 0.5 milliseconds for the signal to drop off to 0 with my code, and its absolutely instantaneous with yours (which is great). This solved another problem I had been bashing my head against: I could get analog servos to work fine with my code (IRS-generated PWM), but any digital servo I tried - just did not move, as if it was broken. I guess the shape of the signal is critical for digital servos, I never read this anywhere. Or maybe its something else I dont know.

As a side note, another weirdness I spent a bunch of time on - I uncommented the TIMSK1 = (1 << TOV1); line, thinking I always needed it - but what happened, my main function would get frozen, blocked forever upon first call to delay_ms(...) not sure what it is - but keeping it commented out unblocks my main loop (where I read the servo positin values from the USB HID using Teensy's sample code)

Again, many thanks for the help.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM