7

I have an Arduino Nano with an 328P and need all 6 PWM pins.

Thus, I had to adjust the prescaler and WGM Mode of Timer0.

It is now in phase correct PWM mode with a prescaler of 1.

TCCR0A = _BV(COM0A1) | _BV(COM0B1) | _BV(WGM00);
TCCR0B = _BV(CS00);

Now I need a working time calculation for other libraries, but since Timer0 had that duty everything is out of order now.

I tried adjusting the wiring.c

// the prescaler is set so that timer0 ticks every 64 clock cycles, and the
// the overflow handler is called every 256 ticks.
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))

to this

#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(1 * 510))

But it's like I didn't change anything. (tested other settings that were changed so it was compiled anew )

Whole Code:

void setup() {

  // Set Timer 0, 1 and 2
  // Register A: Output A and B to non-inverted PWM and PWM mode to phase correct.
  // Register B: Pre Scaler to 1.
  TCCR0A = _BV(COM0A1) | _BV(COM0B1) | _BV(WGM00);
  TCCR0B = _BV(CS00);

  TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM10);
  TCCR1B = _BV(CS10);

  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);
  TCCR2B = _BV(CS20);

  pinMode(8, OUTPUT);


}
void loop() {

  digitalWrite(8, LOW);
  delay(65000);
  digitalWrite(8, HIGH);
  delay(65000);

}
Splitframe
  • 173
  • 5

4 Answers4

4

Fixing the timekeeping functions with your PWM settings is not so simple. You should at least try to rewrite ISR(TIMER0_OVF_vect), micros(), and probably delay(). Here is why:

First, there is a rounding problem. Time is kept using two global variables:

volatile unsigned long timer0_millis;
static unsigned char timer0_fract;

The first one is what millis() returns. The second one keeps track of how much time has passed since the last full millisecond, and it does so in units of 8 µs. The two variables are incremented by ISR(TIMER0_OVF_vect) like this:

m += MILLIS_INC;  // temporary copy of timer0_millis
f += FRACT_INC;   // temporary copy of timer0_fract

On a normal Uno configuration, the ISR is called every 1024 µs. Then MILLIS_INC is 1 and FRACT_INC is 3. With your timer configuration, the ISR is called every 31.875 µs (510 cycles), then MILLIS_INC should be 0 and FRACT_INC should be 3.984375. But since we are dealing with integers, it will be rounded down to 3, and your millis() will tick about 25% too slow.

A simple fix would be to

#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(1 * 512))

in order for FRACT_INC to be 4 and millis() to be 0.4% too fast. Or you could make timer0_fract a 16-bit variable and have it count clock cycles, just to avoid this error. Either option should fix millis(), but you still have a problem with micros().

micros() works by reading both timer0_overflow_count (incremented by 1 in the ISR) and the actual counter value. Since your counter is now going alternatively up and down, it will be harder to compute a microsecond count from these readings. Maybe you could take two consecutive readings of the counter, just to know whether it is going up or down...

And then there is delay(), which relies on micros(). If you fix micros(), delay() should work fine. If not, You could rewrite delay() to use millis() instead, which should be easy but you will loose some accuracy.

Edgar Bonet
  • 45,094
  • 4
  • 42
  • 81
0

You have set MICROSECONDS_PER_TIMER0_OVERFLOW to a proper value, however, this is only ever used by MILLIS_INC, which in turn is only ever used by millis(). This means that the other timing functions, such as micros(), delay(), delayMicroseconds() break when timer0 is changed. This is kind of a bug, and it may be fixed in a future version, but for now, the Arduino libraries are expecting you to leave timer0 alone. The best workaround is to only use millis() for your timing critical functions.

Jake C
  • 1,099
  • 7
  • 18
0

THIS IS AN INCOMPLETE ANSWER - Edgar knows what he's talking about, listen to him

I am currently working with this same thing but in an ATMega2560. This website helped me understand the millis() function better. The timer overflows every 510 counts (see datasheet, pg 123 for the atmega2560). I believe it is 510 and not 512 because it counts up and down, but does not repeat a count at the top or bottom - for example if you count starting from 1 up to 10 and then back down (without repeating 10) you will have counted 19 times, not 20. This counter starts at 0, counts to 255, and then back to 0, triggering an overflow before hitting 0 again. This is is 256 (count up including 0 and 255) + 254 (count down, not including 255 or 0) = 510. I wrote a python script to visualize the effect of these changes and trying to account for them. This counts to 10 million, so it takes time to run, but by the end, even with scaling, the error if simply adjusting millis() after the fact could be ~7 seconds.

#! /usr/bin/env python
import numpy as np

realT = np.zeros(10000000)
timer0_millis = np.zeros(10000000)
timer0_fract = np.zeros(10000000)
i=0

for i in range (1,10000000):
    timer0_millis[i] = timer0_millis[i-1] + 1
    timer0_fract[i] = timer0_fract[i-1] + 3
    realT[i] = i*31875
    if timer0_fract[i-1] >= 125:
        timer0_fract[i] = timer0_fract[i-1] - 125
        timer0_millis[i] = timer0_millis[i-1] + 1

adjusted = timer0_millis*510/(256*64)

print "after 100 timer interrupts millis() will read %d, actual millis is %.6f, adjusted millis is %.6f" % (timer0_millis[99], realT[99]/1000000, adjusted[99])
print "after 1000 timer interrupts millis() will read %d, actual millis is %.6f, adjusted millis is %.6f" % (timer0_millis[999], realT[999]/1000000, adjusted[999])
print "after 10000 timer interrupts millis() will read %d, actual millis is %.6f, adjusted millis is %.6f" % (timer0_millis[9999], realT[9999]/1000000, adjusted[9999])
print "after 100000 timer interrupts millis() will read %d, actual millis is %.6f, adjusted millis is %.6f" % (timer0_millis[99999], realT[99999]/1000000, adjusted[99999])
print "after 1000000 timer interrupts millis() will read %d, actual millis is %.6f, adjusted millis is %.6f" % (timer0_millis[999999], realT[999999]/1000000, adjusted[999999])
print "after 10000000 timer interrupts millis() will read %d, actual millis is %.6f, adjusted millis is %.6f" % (timer0_millis[9999999], realT[9999999]/1000000, adjusted[9999999])
Nerbsie
  • 53
  • 6
0

Thanks to Edgar's answer and Nebsie's explaination on how the counter exactly works I could come up with my own implementation of how to rectify delay and micros() that - so far - seems to work fine on my Arduino Uno.

I'm not saying that this is the most precise implementation, especially I have my doubts on micros() if for example an timer overflow occours between reading t1 and t2, but it is one that so far for me works well.

First thing - according to Edgar's recommendation I defined:

#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(1 * 512)) 

However this doesn't affect timing of micros() or delay().

Function micros() I changed to read two times from TCNT0 - to determine whether it is counting upwards or downwards. This is done with the "if (t1 > t2)" clause. The overflow count "m" is multiplied by 510, because the counter elapses after 510 steps. Then the calculated counter value "t" is added to this, divided by the number of clocks per Microsecond. (Note: Prescaler = 1, therefore no further multplication).

unsigned long micros() {
    unsigned long m;
    uint8_t oldSREG = SREG, t1, t2;
    uint16_t t;

    cli();
    m = timer0_overflow_count;
#if defined(TCNT0)
    t1 = TCNT0;
#elif defined(TCNT0L)
    t1 = TCNT0L;
#else
    #error TIMER 0 not defined
#endif

#if defined(TCNT0)
    t2 = TCNT0;
#elif defined(TCNT0L)
    t2 = TCNT0L;
#else
    #error TIMER 0 not defined
#endif

    if (t1 >= t2) {
        t = 510 - t2;       // counter running backwards
    } else {
        t = t2;             // counter running upwards
    }       

#ifdef TIFR0
    if ((TIFR0 & _BV(TOV0)) && (t2 > 1))        // if overflow flag is set -> increase m
        m++;
#else
    if ((TIFR & _BV(TOV0)) && (t2 > 1))
        m++;
#endif

    SREG = oldSREG;

    return ((m * 510) + t)  / clockCyclesPerMicrosecond();          
}

HOWEVER - this seems to work fine but I had issues to begin with (before editing this again). Due to the counter running much faster, every 268 seconds the unsigned long datatype of micros() overflows and starts from zero again. This led to an unwanted lockup of delay(), especially if long delay times, like in my case delays of one second per loop iterations are used. Therefore I also had to add an overflow detection to the delay() function in arduino wiring.c.

If the overflow occurs, timing might not be very precise. But since this is once overy 268 seconds it may be acceptable. So far with this change the delay function works fine again on my side.

void delay(unsigned long ms)
{
    uint32_t start = micros();
    uint32_t elapsed = 0;   

    while (ms > 0) {
        yield();
        while ( ms > 0 && (micros() - 1000) >= (start + elapsed)) {         
            ms--;
            elapsed += 1000;            
        }
        if( start > micros() ) {    // overflow detected
            start = micros();       // reset start
            elapsed = 0;            // reset elapsed
        }
    }
}
MJtheK
  • 1
  • 1