Problems with Arduino mega 1 microsecond timer.

Hi Everyone!

I am trying to implement a timer that can count with 1-microsecond accuracy. I have used these links: https://timer-interrupt-calculator.simsso.de/ and Arduino Timer Interrupts Calculator
And I believe the best way to do it is by using a CTC mode with Prescaler = 1 so with 16Mhz clock the compare match register OCRnA must be equal to 15 according to the famous formula: 16MHz/(1 prescaler * 1000000(interrupt frequency))- 1
After that, I tried to test my code by incrementing a counter (micro_sec) in the interrupt service routine, then printing when it reaches 1000000 to check if it counts correctly. I saw a weird response so I opened the timestamp option in the serial print, then I saw that it prints approximately every 4 seconds. I don’t know why does that happens, but it is clear that the timer ticks with 4 microseconds period instead of 1 microsecond regardless of the value of the OCRnA register if it is less than 15.

Here is my code for review:

uint32_t micro_sec = 0;
uint32_t time1 = 0;

void setup() {
  Serial.begin(115200);
  timer();
}

void loop() {
  if(micro_sec >= 250000){   //now it is working with 1 second period -> 1/250000 = 4us but this num should be 1000000 for 1us


    Serial.println(micro_sec);
    micro_sec = 0;
  }
}

void timer(){
  cli();
  TCCR5A = 0;     // set entire TCCR5A register to 0
  TCCR5B = 0;     // same for TCCR5B
  TCNT5 = 0;

  OCR5A = 15;            // compare match register (16MHz/(1 prescaler * 1000000(interrupt frequency))- 1) // 1 microsec = 1000000 Hz // https://timer-interrupt-calculator.simsso.de/

  TCCR5B |= (1 << WGM52);   // CTC mode

  TCCR5B |= (1 << CS50);
  TCCR5B &= ~(1 << CS51);    // clkI/O /8 prescaler 
  TCCR5B &= ~(1 << CS52);

  TIMSK5 |= (1 << OCIE5A);  // enable timer compare interrupt

  sei();             // enable all interrupts
  micro_sec = 0;
}

 ISR(TIMER5_COMPA_vect){          // timer compare interrupt service routine
   micro_sec++;  // one increment = 1 microsec
}

A couple of things here:
micro_sec should be qualified as “volatile”, and it can’t be accessed atomically, so should be copied with interrupts disabled.

Having to handle an interrupt every 16 instruction cycles is going to be a heavy burden on your processor. You would be better off letting the timer hardware do the counting. On a 16-bit timer you only have to count an overflow interrupt every 65536 instruction cycles. The combination of overflow count and timer count register will give you time in 16ths of a microsecond. For longer periods you can use a prescale of 8 and count in half-microsecond intervals.

@TheMemberFormerlyKnownAsAWOL

Regarding the first part yes you are right I forgot to make micro_secvolatile. But I didn’t understand what did you mean by "so should be copied with interrupts disabled " ? I tried to implement a callback function so that I wouldn’t use the micro_sec variable in the ISR. Here is my new code:

volatile uint32_t micro_sec = 0;
uint32_t time1 = 0;



void timer5_callback(void (*ptr)()) 
{ 
    (*ptr) (); 
} 

void increment_us() {  
  micro_sec++;
}

void setup() {
  Serial.begin(115200);
  timer();
}

void loop() {
//  Serial.println(micro_sec);
  if(micro_sec >= 1000000){
    Serial.println(micro_sec);
    micro_sec = 0;
  }
}

void timer(){
  cli();
  TCCR5A = 0;     // set entire TCCR5A register to 0
  TCCR5B = 0;     // same for TCCR5B
  TCNT5 = 0;

  OCR5A = 15;            // compare match register (16MHz/(1 prescaler * 1000000(interrupt frequency))- 1) // 1 microsec = 1000000 Hz // https://timer-interrupt-calculator.simsso.de/

  TCCR5B |= (1 << WGM52);   // CTC mode

  TCCR5B |= (1 << CS50);
  TCCR5B &= ~(1 << CS51);    // clkI/O /8 prescaler 
  TCCR5B &= ~(1 << CS52);

  TIMSK5 |= (1 << OCIE5A);  // enable timer compare interrupt

  sei();             // enable all interrupts
  micro_sec = 0;
}

 ISR(TIMER5_COMPA_vect){          // timer compare interrupt service routine
   void (*cb)() = &increment_us;
   timer5_callback(cb);  // one increment = 1 microsec
}

but it gave me the same response.

@johnwasser

I saw that many people were using the TIMER5_OVF_vect ISR , but I don't know how exactly to implement it. So if you could help me or give a guide on how to implement it for 1-microsecond resolution I would be grateful.

micro_sec is a 32 bit value, the processor handles only eight bits, so the value of micro_sec cannot be read in a single instruction, and so its value could change whilst you are reading it.

So, in your main program (non-interrupt context) disable interrupts, take a copy of its value, and reenable interrupts, and refer only to the copy.

@TheMemberFormerlyKnownAsAWOL

If you mean that I should do this:

void loop() {
  cli();
  micro_sec_cpy = micro_sec;
  sei();
  if(micro_sec_cpy >= 1000000){   //now it is working with 1 second period -> 1/250000 = 4us but this num should be 1000000 for 1us
    Serial.println(micro_sec_cpy);
    micro_sec_cpy = 0;
    micro_sec = 0;
  }
}

this also gives me the same response.

micro_sec = 0; Remember what I said about 32 bit accesses?

@TheMemberFormerlyKnownAsAWOL

void loop() {
  cli();
  micro_sec_cpy = micro_sec; 
  if(micro_sec >= 1000000){
    micro_sec = 0;
  }
  sei();
  
  if(micro_sec_cpy >= 1000000){   //now it is working with 1 second period -> 1/250000 = 4us but this num should be 1000000 for 1us
    Serial.println(micro_sec_cpy);
  }
}

This gives the same result.

I too am having problems with this. Nothing is shown in the console, no matter the baud rate I choose.

volatile unsigned long microseconds = 0;

unsigned long microsecondsCopy = 0;

// --- Interruption Service Routine ---
ISR(TIMER1_COMPA_vect)
{ 
  TCNT1 = 0;      //restart TIMER1
  microseconds++;
} //end ISR

void setup()
{
   cli();
   TCCR1A = 0; // set entire TCCR1A register to 0
   TCCR1B = 0; // same for TCCR1B
   //Initializing registers
   TCNT1 = 0;

   //Interrupt every 1 microsecond, or 0.000001s (time*crystal/prescaler), using prescaler 8
   OCR1A = 1;

   // turn on CTC mode
   TCCR1B |= (1 << WGM12);

   //Prescaler 1:8
   //CS12 CS11  CS10
   // 0    1     0
   TCCR1B &= ~(1 << CS12);
   TCCR1B |=  (1 << CS11);
   TCCR1B &= ~(1 << CS10);

   //Enable Interrupt on Timer1
   TIMSK1 = (1 << OCIE1A);
   sei();

   Serial.begin(115200);

} //end setup


void loop()
{
  cli();
  microseconds = 0;
  sei();

  delay(10000); //10 seconds delay

  cli();
  microsecondsCopy = microseconds;
  sei();

  Serial.print("Time passed in microseconds: ");
  Serial.println(microsecondsCopy);
} //end loop

What am I doing wrong? If I change the compare value so that interrupts will be at 50 microseconds, it works, and shows values around 193953, which multiplied by 50 results in 9.697.650 microseconds, pretty close to the 10.000.000 it should be.

//Interrupt every 50 microseconds, or 0.00005s (time*crystal/prescaler) - 1, using prescaler 8
OCR1A = 99;

You really, really, can’t interrupt a 16MHz processor every microsecond…

Here’s the code produced for that ISR:

00000480 <__vector_11>:
 480:   1f 92           push    r1
 482:   0f 92           push    r0
 484:   0f b6           in      r0, 0x3f        ; 63
 486:   0f 92           push    r0
 488:   11 24           eor     r1, r1
 48a:   8f 93           push    r24
 48c:   9f 93           push    r25
 48e:   af 93           push    r26
 490:   bf 93           push    r27
 492:   10 92 85 00     sts     0x0085, r1      ; 0x800085 <__DATA_REGION_ORIGIN__+0x25>
 496:   10 92 84 00     sts     0x0084, r1      ; 0x800084 <__DATA_REGION_ORIGIN__+0x24>
 49a:   80 91 41 01     lds     r24, 0x0141     ; 0x800141 <microseconds>
 49e:   90 91 42 01     lds     r25, 0x0142     ; 0x800142 <microseconds+0x1>
 4a2:   a0 91 43 01     lds     r26, 0x0143     ; 0x800143 <microseconds+0x2>
 4a6:   b0 91 44 01     lds     r27, 0x0144     ; 0x800144 <microseconds+0x3>
 4aa:   01 96           adiw    r24, 0x01       ; 1
 4ac:   a1 1d           adc     r26, r1
 4ae:   b1 1d           adc     r27, r1
 4b0:   80 93 41 01     sts     0x0141, r24     ; 0x800141 <microseconds>
 4b4:   90 93 42 01     sts     0x0142, r25     ; 0x800142 <microseconds+0x1>
 4b8:   a0 93 43 01     sts     0x0143, r26     ; 0x800143 <microseconds+0x2>
 4bc:   b0 93 44 01     sts     0x0144, r27     ; 0x800144 <microseconds+0x3>
 4c0:   bf 91           pop     r27
 4c2:   af 91           pop     r26
 4c4:   9f 91           pop     r25
 4c6:   8f 91           pop     r24
 4c8:   0f 90           pop     r0
 4ca:   0f be           out     0x3f, r0        ; 63
 4cc:   0f 90           pop     r0
 4ce:   1f 90           pop     r1
 4d0:   18 95           reti

I count “about” 60 cycles; almost 4 microseconds.

I’d go so far as to say that you shouldn’t count on being able to interrupt ANY processor at microsecond intervals; as the chips get faster, the overhead of an interrupt tends to get higher…

If you do not require WiFi, an ESP32 could do the job. With 2 cores, using freeRTOS, and 4 hardware timers. One of the timers can easily produce 1uS pulses, the pulse handler can be assigned, easily, to core1, with the rest of the code operating under core0. The smallest pulse that a hardware timer can produce is 12.5nS.

ESP32 API API Reference - ESP32 - — ESP-IDF Programming Guide latest documentation.

Producing pulses is not the same as causing an interrupt.
An AVR can easily configure a counter to increment every microsecond, and interrupt/SW-count every 256 or larger interval (if you have a 16bit counter), and you can then measure microseconds by doing some calculations with the SW count plus reading the current HW counter value.
But that hasn’t been what people in this thread seem to be doing!

(This is the way that the current microseconds() function works., except that each counter tick is 4us rather than 1us. In theory, you could take the existing timer0 code and microseconds() function, and just change the prescaler to create faster ticks. (you can probably simplify the ISR code as well, since you wouldn’t need to try to get “milliseconds” out of a 1024uS tick… If this is some sort of assignment, that’s probably what they have in mind.)

Here’s a function that will return the current time in 16ths of a microsecond. Just call “StartFastTimer()” to get it running and then call FastTimer() every time you want the time. It overflows every 268.435456 seconds (about 4.5 minutes). If you want to time much longer intervals, change ‘Overflows’ to 32 bits and get a 48-bit result. That will give you 203 days before an overflow.

void StartFastTimer()
{
  // For testing, uncomment one of these lines and connect
  // Pin 3 or Pin 5 to Pin 8
  // analogWrite(3, 64);  // 512.00, 1528.00, 2040.00, 25.10%, 490.20 Hz
  // analogWrite(5, 64);  // 260.00, 764.00, 1024.00, 25.39%, 976.56 Hz


  noInterrupts ();  // protected code
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  TIMSK1 = 0;

  TIFR1 = _BV(TOV1); // clear Overflow Flag so we don't get a bogus interrupt
  TIMSK1 = _BV(TOIE1); // Enable Timer 1 Overflow Interrupt
  TCCR1B = _BV(CS10); // start Timer 1, no prescaler

  interrupts ();
}


volatile uint16_t Overflows = 0;


ISR(TIMER1_OVF_vect)
{
  Overflows++;
}


unsigned long FastTimer()
{
  unsigned long currentTime;
  uint16_t overflows;
  
  noInterrupts();
  overflows = Overflows;  // Make a local copy


  // If an overflow happened but has not been handled yet
  // and the timer count was close to zero, count the
  // overflow as part of this time.
  if ((TIFR1 & _BV(TOV1)) && (ICR1 < 1024))
    overflows++;


  currentTime = overflows; // Upper 16 bits
  currentTime = (currentTime << 16) | ICR1;
  interrupts();


  return currentTime;
}

westfw:
You really, really, can’t interrupt a 16MHz processor every microsecond…

Here’s the code produced for that ISR:

00000480 <__vector_11>:

480:  1f 92          push    r1
482:  0f 92          push    r0
484:  0f b6          in      r0, 0x3f        ; 63
486:  0f 92          push    r0
488:  11 24          eor    r1, r1
48a:  8f 93          push    r24
48c:  9f 93          push    r25
48e:  af 93          push    r26
490:  bf 93          push    r27
492:  10 92 85 00    sts    0x0085, r1      ; 0x800085 <DATA_REGION_ORIGIN+0x25>
496:  10 92 84 00    sts    0x0084, r1      ; 0x800084 <DATA_REGION_ORIGIN+0x24>
49a:  80 91 41 01    lds    r24, 0x0141    ; 0x800141
49e:  90 91 42 01    lds    r25, 0x0142    ; 0x800142 <microseconds+0x1>
4a2:  a0 91 43 01    lds    r26, 0x0143    ; 0x800143 <microseconds+0x2>
4a6:  b0 91 44 01    lds    r27, 0x0144    ; 0x800144 <microseconds+0x3>
4aa:  01 96          adiw    r24, 0x01      ; 1
4ac:  a1 1d          adc    r26, r1
4ae:  b1 1d          adc    r27, r1
4b0:  80 93 41 01    sts    0x0141, r24    ; 0x800141
4b4:  90 93 42 01    sts    0x0142, r25    ; 0x800142 <microseconds+0x1>
4b8:  a0 93 43 01    sts    0x0143, r26    ; 0x800143 <microseconds+0x2>
4bc:  b0 93 44 01    sts    0x0144, r27    ; 0x800144 <microseconds+0x3>
4c0:  bf 91          pop    r27
4c2:  af 91          pop    r26
4c4:  9f 91          pop    r25
4c6:  8f 91          pop    r24
4c8:  0f 90          pop    r0
4ca:  0f be          out    0x3f, r0        ; 63
4cc:  0f 90          pop    r0
4ce:  1f 90          pop    r1
4d0:  18 95          reti




I count "about" 60 cycles; almost 4 microseconds.



I'd go so far as to say that you shouldn't count on being able to interrupt ANY processor at microsecond intervals; as the chips get faster, the overhead of an interrupt tends to get higher...

Thanks, it makes a lot of sense. It’s not an assignment, I want a source of time to measure one way delays of UDP packages on a wireless network.

Anyway, by changing the prescaler to 64 and compare register to 4, thus getting a tick every 20 microseconds, I was able to get closer and, for a 10 seconds delay, I’ve got 20*ticks = 9.998.800 seconds.

However, I’ve tested @johnwasser code and it seems to work. For a 10 seconds delay, I’ve got 160.038.912 ticks of 62,5 nanoseconds (every time, INCREDIBLY precise). I’m still studying his code because some parts aren’t yet clear to me.

When I said precise, I meant it. I didn’t know Arduino boards could be so precise with that level of granularity. I copied this from the console, take a look:

Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 11
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 11
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336
Time passed in seconds (RTC DS3231): 10
Time passed in microseconds (Counter): 9998336

This was how I outputted it:

Serial.println((unsigned long) (FastTimer()*62.5/1000));

johnwasser:
Here’s a function that will return the current time in 16ths of a microsecond. Just call “StartFastTimer()” to get it running and then call FastTimer() every time you want the time. It overflows every 268.435456 seconds (about 4.5 minutes). If you want to time much longer intervals, change ‘Overflows’ to 32 bits and get a 48-bit result. That will give you 203 days before an overflow.

void StartFastTimer()

{
  // For testing, uncomment one of these lines and connect
  // Pin 3 or Pin 5 to Pin 8
  // analogWrite(3, 64);  // 512.00, 1528.00, 2040.00, 25.10%, 490.20 Hz
  // analogWrite(5, 64);  // 260.00, 764.00, 1024.00, 25.39%, 976.56 Hz

noInterrupts ();  // protected code
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  TIMSK1 = 0;

TIFR1 = _BV(TOV1); // clear Overflow Flag so we don’t get a bogus interrupt
  TIMSK1 = _BV(TOIE1); // Enable Timer 1 Overflow Interrupt
  TCCR1B = _BV(CS10); // start Timer 1, no prescaler

interrupts ();
}

volatile uint16_t Overflows = 0;

ISR(TIMER1_OVF_vect)
{
  Overflows++;
}

unsigned long FastTimer()
{
  unsigned long currentTime;
  uint16_t overflows;
 
  noInterrupts();
  overflows = Overflows;  // Make a local copy

// If an overflow happened but has not been handled yet
  // and the timer count was close to zero, count the
  // overflow as part of this time.
  if ((TIFR1 & _BV(TOV1)) && (ICR1 < 1024))
    overflows++;

currentTime = overflows; // Upper 16 bits
  currentTime = (currentTime << 16) | ICR1;
  interrupts();

return currentTime;
}

It seems to work pretty well. I’m running some tests and trying to better understand what you’ve done different, since my code was unable to get a 1 microsecond tick even though it works for 20 microseconds ones.

genilsonisrael:
I’m running some tests and trying to better understand what you’ve done different, since my code was unable to get a 1 microsecond tick even though it works for 20 microseconds ones.

OOPS! That “currentTime = (currentTime << 16) | ICR1;” should be “currentTime = (currentTime << 16) | TCNT1;”
The hardware counts 16 MHz clock ticks in the 16-bit TCNT1 register. That register overflows and signals an interrupt every 4096 microseconds. Each overflow is counted by software and the count is used for the upper 16 bits of the 32-bit result.
An interrupt every 4096 microseconds MUCH easier on the hardware than an interrupt every microsecond.

@johnwasser

Thank you very much for the code. It really helped me throughout my project. Even sometimes the timer is not that accurate however I know it's an inevitable hardware problem.

mgf_24:
@johnwasser

Thank you very much for the code. It really helped me throughout my project. Even sometimes the timer is not that accurate however I know it’s an inevitable hardware problem.

I just noticed another place where I used ICR1 when I should have used TCNT1:

  // If an overflow happened but has not been handled yet
  // and the timer count was close to zero, count the
  // overflow as part of this time.
  if ((TIFR1 & _BV(TOV1)) && (ICR1 < 1024))
    overflows++;

That should be:

  // If an overflow happened but has not been handled yet
  // and the timer count was close to zero, count the
  // overflow as part of this time.
  if ((TIFR1 & _BV(TOV1)) && (TCNT1 < 1024))
    overflows++;

Sorry about that. The code was a quick adaptation of a sketch that used the Input Capture Register (ICR1) for timing a signal.