5

For a driving application I need the PWM mode to be phase correct and the prescaler to be 1. The issue is that the board I am working with has the load hooked up to a PWM pin reliant on timer0. The firmware running on this board makes use of the delay(), delayMicroseconds(), and millis() functions from wiring.c. The goal is to make the timer0 change, shown below, and then make small modifications where the firmware calls the wiring.c timing functions. My approach was to find a scaling factor to be applied to the call of these functions. The scaling factor I came to was 32.12549, calculated by taking the wiring.c expected timer0 behavior: prescaler=64 and TOV0 flag every 256 counts, and comparing it to the actual values after my change: prescaler=1 and TOV0 flag every 510 counts

Calculation: 64*256/510=32.12549

Timer0 change:

// Set timer0 to phase correct PWM
TCCR0A = TCCR0A & 0b11111100 | 0x01;

// Set prescaler for timer0 to 1
TCCR0B = TCCR0B & 0b11111000 | 0x01;

The other timers are all configured in phase correct PWM mode, with a prescaler of 1.

...When I apply this scaling factor to a value returned from millis() to keep time it works well. For example the two pieces of code below proved to be equivalent through the timer0 change:

Use of millis() before timer0 change:

//current time
long unsigned now = millis();

//delay for 1 second
while(millis() - now < 1000){}

Use of millis() after timer0 change:

//current time
long unsigned now = millis();

//delay for 1 second, with x32.125 scaling coefficient
while(millis() - now < 32125){}

The two snippets of code above both delayed for 1 second

Now comes the problem: when I try to apply that same scaling factor to delay() or delayMicroseconds() it seems to give unreasonable delays...

Use of delayMicroseconds() before timer0 change:

// wait 1.5ms for mux to switch
delayMicroseconds(1500);

...this delays for 1.5ms

Use of delayMicroseconds() after timer0 change:

// wait 1.5ms for mux to switch, with x32.125 scaling coefficient
delayMicroseconds(48188);

...this delays for ~20ms.

Assuming this is still linearly scalable: if 48188 -> 20ms, therefore 1.5ms -> 3614

But when the following code is used...

// wait 1.5ms for mux to switch, scaled
delayMicroseconds(3614);

...this delays for ~4.5ms

Why is it that delayMicroseconds() and delay() cannot be linearly scaled to account for a timer0 change? Furthermore, is there any simple modification to how these functions are called or utilized that can account for the effect of changing timer0?

  • 1
    You might want to check the Arduino source code. It seems like delayMicroseconds doesn't even use timer0. Does your code use any interrupts, as that would skew the result of delayMicroseconds? Did you run out of PWM pins on the Mega, that you need to use timer0? – Gerben Oct 13 '16 at 15:23
  • Yes, timer0 must be used here. The code does use interrupts, which might be effecting this (I hadn't notice that delayMicroseconds() doesn't utilize micros(), which blocks interrupts; I assumed it did since delay() does) - although I am using a scope to measure the output (toggling a pin on either side of the delay) and the delay is repeatable, where-as our interrupts may happen at any time during the code. If it is an interrupt causing the issue I would expect the delay to vary as the loop runs, which it does not. I'll do my best to read through the source code for delayMicroseconds() – Nerbsie Oct 13 '16 at 18:00
  • I think you are right actually, and I think the interrupt that is causing this mistake could actually be the timer overflow interrupts. By changing the clock speeds (I actually increased the clock speed of all PWM timers) I caused each clocks TOVn interrupt flag to be raised every 31.875us, as opposed to the standard operation which is every 1024us. – Nerbsie Oct 13 '16 at 19:02
3

The millis() roll over

By making Timer 0 faster, your millis() will roll over much more often than normal: every 37.1 hours instead of every 49.7 days. Thus, when you “scale” millis(), you should be careful to do it in a rollover-safe way. From the code you show, it appears that you are doing exactly the right thing. Well done! Don't change anything. Don't be tempted to scale the value returned by millis().

The overflow in delayMicroseconds()

As this function just burns cycles in a busy loop, it is not affected by your configuration of Timer 0. You can just use it as is, with no scaling. Beware however that it's argument should always be smaller than 16384 (on a 16 MHz board). Otherwise it will overflow when it is multiplied by 4 in order to get a loop count.

For example, when you call delayMicroseconds(48188);, the function computes the number of loop iterations to make as

48188 × 4 − 5 = 61675 (modulo 216)

Then, it burns about 15 ms of CPU time (0.25 µs per iteration), which is close to the 20 ms you see. For the difference, see below.

The cycles burned by the Timer 0 ISR

This ISR, which keeps the millis() counter updated, is supposed to run every 1024 µs. Each time it runs it burns a few cycles, but it's usually not a big deal. With your new timer configuration, it is now running far more often: every 31.875 µs.

From the numbers you give in the question, I would say the ISR is eating about 20% of your CPU power: when you call delayMicroseconds(3614);, that function burns 3614 µs of CPU time, yet you measure a delay of about 4500 µs. The difference is taken by the Timer 0 ISR.

Use another timer for millis()

If you can spare a timer, you could both restore the normal behavior of millis() and avoid wasting so much CPU in the Timer 0 ISR:

  • configure Timer 0 to not trigger interrupts
  • configure your spare timer just like the Arduino core would normally configure Timer 0
  • write an overflow ISR for your spare timer that just calls the Timer 0 overflow ISR.

This way your spare timer is basically replacing Timer 0. Example using Timer 2:

// This Timer 2 overflow ISR simply forwards the call to the Timer 0
// overflow ISR from Arduino core.
ISR(TIMER0_OVF_vect);
ISR(TIMER2_OVF_vect, ISR_NAKED) {
    TIMER0_OVF_vect();
    asm volatile("reti");

    // Alternatively, just:
    // asm volatile("jmp __vector_16");
}

void setup()
{
    // Set timer0 to phase correct PWM
    TCCR0A = (TCCR0A & 0b11111100) | 0x01;

    // Set prescaler for timer0 to 1
    TCCR0B = (TCCR0B & 0b11111000) | 0x01;

    // Disable Timer 0 interrupts.
    TIMSK0 = 0;

    // Configure Timer 2 to replace Timer 0.
    TCCR2A = _BV(WGM20)   // fast PWM, TOP = 0xff
           | _BV(WGM21);  // ditto
    TCCR2B = _BV(CS22);   // clock at F_CPU / 64
    TIMSK2 = _BV(TOIE2);  // enable overflow interrupt

    pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{
    // Blink the LED at 1 Hz.
    uint32_t now = millis();
    static uint32_t last_toggle;
    if (now - last_toggle >= 500) {
        last_toggle += 500;
        digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    }
}

I wrote a “naked” ISR to avoid having plenty of CPU registers saved and restored twice (once by each ISR in the call chain), but then I had to put the return instruction explicitly in assembly (ret and reti would be equivalent in this instance). The version with jmp is slightly more efficient, but you have to know the “real” name of the ISR, which is MCU-dependent (__vector_16 on an Uno).

Notice that with this technique micros() will not work, as it reads TCNT0 directly. And delay() will not work either because it relies on micros().

  • Fantastic response, thank you. It seems delayMicroseconds() is not going to be reliable, and based on the length delays my project requires I think relying exclusively on millis() would be fine. We are using all the timers for PWM outputs at this point, and as you can probably deduce from that we have a fairly weighed down microcontroller. I believe the other timers are 16-bit, so would roll-over way less often - I'm starting to think we cannot handle timer0 being sped up and still manage the communications and calculations in a reliable fashion. – Nerbsie Oct 14 '16 at 14:20
  • Out of interest, how did you come to this conclusion: "From the numbers you give in the question, I would say the ISR is eating about 20% of your CPU" ? – Nerbsie Oct 14 '16 at 14:23
  • 1) How did you configure the other timers? Could they work in fast PWM mode at 977 Hz? You may want to add this info to the question. Arduino configures the only 16-bit timer in 8-bit mode anyway. 2) Out of 4500 µs, 3614 are spent inside delayMicroseconds(3614). The rest, namely 886 µs (i.e. 20%) is eaten by that ISR. – Edgar Bonet Oct 14 '16 at 14:59
  • Things are changing as I dig into this, and it looks like getting away from timer0 is important enough to merit hardware modifications. Also, I am using an ATMega2560, which has 4x 16-bit timers (1,3,4,5) and 2x 8-bit timers (0,2). I have to go back through the firmware and verify that we have them set as 16-bit (does Arduino default all timers to 8-bit?), but it seems you have given me more than enough reason to dedicate my effort to working away from timer0. – Nerbsie Oct 14 '16 at 20:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.