Home > front end >  speed up the AVR ISR
speed up the AVR ISR

Time:06-17

I am wondering if it is possible to speed up the ISR without changing the prescaler. I have a timer with 2 compare registers A and B.

COMPA is used for a PWM output from around 22% up to 100%. This has a fixed frequency and I am not allowed to change it at least not much.

Now I would like to use the COMPB with around 4 times the speed but with a fixed duty cycle of 50%.

If I set the OCIE0B bit in TIMSK0 for the attiny13 can I do the following to speed things up? Or am I misunderstanding something here?

ISR(TIM0_COMPB_vect){
    switch (timing){
        case 0:
            OCR0B = 63;
            PORTB ^= (1 << PB3);
            timing = 1;
            break;
        case 1:
            OCR0B = 127;
            PORTB ^= (1 << PB3);
            timing = 2;
            break;
        case 2:
            OCR0B = 191;
            PORTB ^= (1 << PB3);
            timing = 3;
            break;
        case 3:
            OCR0B = 255;
            PORTB ^= (1 << PB3);
            timing = 0;
            break;
    }
}

Any help appreciated. Thanx :D

CodePudding user response:

You can do this very efficiently by creatively using the Normal Mode.

The trick is to set the prescaller to get a clock period that is double what you want the variable-duty PWM signal to run at. So if, for example, you want that to PWM at 1Mhz, set the prescaller to 2Mhz.

Assume the variable duty cycle PWM is on pin A and the fixed 50% 4x clock signal is on pin B. (You can also swap these and and also update the code everything will still work)

  1. Enable interrupts for "On compare match B" and "Overflow".

  2. Force pin A high with a force compare match. (Alternately you can skip this step and instead use the inverse of the desired duty cycle in step 7)

  3. Set the COM bits for 'A' to Toggle on match.

  4. Leave the COM bits for B to off. Assumes you have DDR set for this pin to be normal GPIO.

  5. Set the OCR for B to 128.

  6. Set the WGM timer mode to 0 - "Normal Mode".

  7. Set the OCR for A whatever you want the variable duty cycle to be. Note that you might need to special case here for extreme values of 0 and/or 255 depending on what you want to have happen (just turn the pin ON of OFF with GPIO).

You can repeat step 6 anytime you want to change the duty cycle of A and it will update on the next TOP.

Once you do these steps, the A pin will output the desired duty cycle at 1/2 the prescaller clock and the B will output 50% duty at 2x the prescaller clock (which is the desired 4x of the A period).

Here is the ISR code (note that I am not sure what the TOV vector is called in the attiny13 headers [it is sometimes different across AVRs] so you might have to edit the TIM0_OVF_vect name)...

ISR(TIM0_COMPB_vect,TIM0_OVF_vect){
    PINB |= (1 << PB3);   // Compiles to a single cycle SBI
}

See how this works?

Note that setting a bit in the PIN register actually toggles the PORT bit. This is a quirk of the AVR GPIOs that is documented in the datasheets.

Hopefully this is fast enough. If you really want to squeeze every last cycle out, you can even potentially save the 2 cycles of the RJMP from the interrupt vector by putting the single SBI instruction that the ISR compiles down to directly into the interrupt vector table with a trailing RETI, but this is more complicated!

CodePudding user response:

Focusing solely on the C code aspects, then this can be trivially optimized as:

ISR(TIM0_COMPB_vect)
{
  static const uint8_t OCR[4] = {63,127,191,255};
  OCR0B = OCR[timing];

  PORTB ^= 1u << PB3;

  timing  ;
  if(timing==4)
    timing=0;
}

Disassembled on gcc AVR -O3 (with all variables/registers volatile) this brings down the amount of instructions from ~50 to ~20, so it's about twice as fast and takes less memory.

  • Related