ISR timer interrupt combined with fast PWM frequency

Hi, i've built a micro quadcopter drone and i'm trying to make it run smoother with timer interrupts. I'm using RemoteXY lib to communicate with HM-10 module. I2C devlibs is used for MPU6050 communication.

I need help with combining timer interrupt (ISR) and faster PWM frequency on pins 3, 9, 10 and 11.
I figured out that my brushed DC motors work very good when timer 1 and timer 2 are setup for 31.4 kHz PWM frequency. I found instructions here https://nerdytechy.com/how-to-change-the-pwm-frequency-of-arduino/

Now i'm trying to also use timer interrupt that would switch the "pidCompute" flag every 10 ms. I tried to do something similar to this https://www.instructables.com/Arduino-Timer-Interrupts/

When i uncomment interrupt code, UART communication stopps working properly. I recon i'm doing something wrong. Interrupt this short shouldn't completely mess up the UART, would it?

Code from the setup:

ISR(TIMER1_COMPA_vect) {
    pidCompute = 1;
}
/////
void setup() {
    RemoteXY_Init();

    // join I2C bus (I2Cdev library doesn't do this automatically)
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
    Wire.begin();
    TWBR = 24;  // 400kHz I2C clock
#elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
    Fastwire::setup(400, true);
#endif

    // Setup motor PWM frequency setup to 31.4 kHz
    TCCR1A = 0b00000001;  // Pins D9, D10 to 8bit
    TCCR1B = 0b00000001;  // x1 phase correct
    TCCR2B = 0b00000001;  // Pins D3, D11 to 8bit
    TCCR2A = 0b00000001;  // 1x phase correct

    // Motor pins setup to output
    DDRD |= B00001000;
    DDRB |= B00001110;

    // commented out TCCR1A = 0;  // set entire TCCR1A register to 0
    // commented out TCCR1B = 0;  // same for TCCR1B
    TCNT1 = 0;   // initialize counter value to 0
    // set compare match register for 1hz increments
    OCR1A = 2499;  // Set compare match register for 10 ms interrupt interval
    // turn on CTC mode (Clear Timer on Compare Match)
    TCCR1B |= (1 << WGM12);
    // Set CS11 and CS10 bits for 64 prescaler
    TCCR1B |= (1 << CS11) | (1 << CS10);
    // enable timer compare interrupt
    TIMSK1 |= (1 << OCIE1A);

No interrupt is required for such timing.

1 Like

You can't use Timer1 for both "31.4 kHx PWM" and 10 millisecond timer interrupts.

Thank you for your feedback guys. So it's best that i don't use interrupts in this case and simply rely on the millis() or micros() functions.
Just to be sure that i understand this correctly. Upper code that sets up the PWM frequency sets up TCCRAx register to 8-bit phase correct WGM setting and TCCRBx register to "no prescaling" setting.
That gives us desired PWM output, which is probably calculated like this:
16 MHz / 2^n, where n would be the number of bits.

Does this mean that this code actually gives us 65,5 kHz PWM and not 31.25 kHz?
Whouldn't 9 bit phase correct setting give us around 31 kHz?
16 MHz / 2^9 = 31.25

TCCR1A = 0b00000001;
 TCCR1B = 0b00000001;

The low-order bit of TCCR1A is "WGM10". Setting only that bit sets Timer1 to Waveform Generation Mode 1: PWM, Phase Correct, 8-bit.

The frequency formula for a phase-correct PWM mode is 16 MHz / (2 * prescale * TOP). For 8-bit PWM, TOP is 255. Your prescale is 1 so 31372.55 Hz.

If you want 62 kHz you could switch to a Fast PWM mode (like Mode 5). The frequency formula for that is 16 MHz / (prescale * (TOP+1)). TOP is still 255 and prescale is still 1 so 62500 Hz.
That would be:

 TCCR1A = 0b00000001;  // WGM10
 TCCR1B = 0b00001001;  // WGM13 | CS10

The equivalent mode on Timer2 is Mode 3:

 TCCR2A = 0b00000011;  // WGM21 | WGM20
 TCCR2B = 0b00001001;  // CS10
1 Like

Phase correct PWM modes count up then down, so the cycle takes twice the time of only count-up modes.

1 Like

Thank you very much guys, this was very helpful. I really appreciate your time and effort.
Cheers

If you use one of the TImer0 compare interrupts (without actually changing anything else about timer0), you'd get the ISR called every 1024us (it doesn't matter what the compare value is set to, since timer0 is set to wrap around continuously, generating the interrupt that millis() uses on overflow. but if you set the the compare register to 128, it would occur mid-way between the millis() interrupts.)

1 Like

Timer0 prescaler shouldn't be changed because it is used as a refference clock. Am i correct?

But i could create a counter variable with interrupt routine counting up to 10 (since interrupt happens every 1 ms). Is this a bad idea?

unsigned int counter{0};

ISR(TIMER0_COMPA_vect) {
    if (counter == 9){
        ... calculations
        counter = 0;
    } else {
            ++counter;
    }
}

I think it's a fine idea.
Use "uint8_t" for your counter variable, and it'll be somewhat faster.

The downside is that it'll be 10.24 ms instead of 10, which will matter for some things, but not others...

Timer0 prescaler shouldn't be changed because it is used as a refference clock.

Also correct.

1 Like

volatile unsigned int counter{0}; is safer

1 Like

Thank you for suggestions, much appreciated.
Now i have a weird problem. I think that i've accidentally turned on a timer interrupt that i'm unaware of. Arduino keeps rebooting midway setup.
Is there a way to force set default values to all interrupt and timer registers? I'm not sure about default values of these registers. Thank you in advance :grimacing: :slight_smile:

cli();
TCCR0A = ...
TCCR0B = ...
TIMSK0 = ...
OCR0A = ...
OCR0B = ...
TCNT0 = ...

TCCR1A=...
...
sei();
and probably some other registers i'm unaware of.

I think i figured it out. Now the system is stable again. Before that, nomatter what i uploaded, UART didn't work as it should have. I figured that it's probably an interrupt kicking in and interrupting the timing.

    cli();
    TCCR0B = 0b00000011;  // x64
    TCCR0A = 0b00000011;  // fast pwm
    TIMSK0 = 0b00000000;
    TCNT0 = 0b00000000;
    OCR0A = 0b00000000;
    OCR0B = 0b00000000;

    TCCR1A = 0b00000001;  // 8bit
    TCCR1B = 0b00000011;  // x64 phase correct
    TIMSK1 = 0b00000000;
    TCNT1 = 0b00000000;
    OCR1A = 0b00000000;
    OCR1B = 0b00000000;

    TCCR2B = 0b00000100;  // x64
    TCCR2A = 0b00000001;  // phase correct
    TIMSK2 = 0b00000000;
    TCNT2 = 0b00000000;
    OCR2A = 0b00000000;
    OCR2B = 0b00000000;
    sei();

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.