0

I have written the following code, attempting to re-implement the Arduino micros function:

main.c

#include <avr/interrupt.h>
#include <stdbool.h>

#define CLOCK_CYCLES_PER_MICROSECOND (F_CPU / 1000000L)

volatile unsigned long timer0_overflow_count = 0;

ISR(TIMER0_OVF_vect) {
  timer0_overflow_count++;
}

unsigned long micros() {
  // Save SREG so we can restore it later.
  uint8_t sreg = SREG;

  // Disable interrupts so we aren't reading the timer whilst it's being
  // written.
  cli();

  // Store the overlfow count and timer value so we can restore SREG.
  unsigned long overflow_count = timer0_overflow_count;
  uint8_t timer_value = TCNT0;

  // If we've seen an overflow and the timer has been reset, increment the
  // overflow count. This handles a case where the counter has been reset but
  // the interrupt handler has not yet been called.
  if (TIFR0 & _BV(TOV0) && timer_value != 255) {
    overflow_count++;
  }

  // Restore SREG.
  SREG = sreg;

  // The timer ticks once every 64 clock cycles, so we need to divide the
  // number of cycles accumulated in the timer by the number of cycles per
  // microsecond.
  return ((overflow_count << 8) + timer_value) * (64 / CLOCK_CYCLES_PER_MICROSECOND);
}

int main() {
  // Set pin 13 (8 in DDRD + 5 in DDRB = 13) to an output.
  DDRB |= _BV(PIN5);

  // Increment the timer every 64 clock cycles.
  TCCR0B |= _BV(CS01) | _BV(CS00);

  // Enable overflow interrupt for timer zero.
  TIMSK0 |= _BV(TOIE0);

  sei();

  unsigned long next = 0;

  while (true) {
    while (micros() < next);

    PORTB ^= _BV(PIN5);

    next = micros() + 10;
  }
}

Which provides a pretty steady 14μs clock:

14μs clock

Now I want to move this micros function into a separate file:

micros.h

#pragma once

unsigned long micros();

micros.c

#include <avr/interrupt.h>

#define CLOCK_CYCLES_PER_MICROSECOND (F_CPU / 1000000L)

volatile unsigned long timer0_overflow_count = 0;

ISR(TIMER0_OVF_vect) {
  timer0_overflow_count++;
}

unsigned long micros() {
  // Save SREG so we can restore it later.
  uint8_t sreg = SREG;

  // Disable interrupts so we aren't reading the timer whilst it's being
  // written.
  cli();

  // Store the overlfow count and timer value so we can restore SREG.
  unsigned long overflow_count = timer0_overflow_count;
  uint8_t timer_value = TCNT0;

  // If we've seen an overflow and the timer has been reset, increment the
  // overflow count. This handles a case where the counter has been reset but
  // the interrupt handler has not yet been called.
  if (TIFR0 & _BV(TOV0) && timer_value != 255) {
    overflow_count++;
  }

  // Restore SREG.
  SREG = sreg;

  // The timer ticks once every 64 clock cycles, so we need to divide the
  // number of cycles accumulated in the timer by the number of cycles per
  // microsecond.
  return ((overflow_count << 8) + timer_value) * (64 / CLOCK_CYCLES_PER_MICROSECOND);
}

main.c

#include <avr/interrupt.h>
#include <stdbool.h>
#include "micros.h"

int main() {
  // Set pin 13 (8 in DDRD + 5 in DDRB = 13) to an output.
  DDRB |= _BV(PIN5);

  // Increment the timer every 64 clock cycles.
  TCCR0B |= _BV(CS01) | _BV(CS00);

  // Enable overflow interrupt for timer zero.
  TIMSK0 |= _BV(TOIE0);

  sei();

  unsigned long next = 0;

  while (true) {
    while (micros() < next);

    PORTB ^= _BV(PIN5);

    next = micros() + 10;
  }
}

However this provides a wildly fluctuating clock, ranging from 12μs to 18μs:

Fluctuating clock

It is even easier to see when the clocks are placed side by side:

Clock comparison

Why would moving the interrupt handler and micros function to a separate file cause this fluctuation?

Here are the commands I'm using to compile and link the code:

$ avr-gcc -Iinclude -mmcu=atmega328p -DF_CPU=16000000 -std=c17 -O3 -Wall -Wextra -c src/main.c -o obj/main.o
$ avr-gcc -Iinclude -mmcu=atmega328p -DF_CPU=16000000 -std=c17 -O3 -Wall -Wextra -c src/micros.c -o obj/micros.o
$ avr-gcc -mmcu=atmega328p obj/main.o obj/micros.o -o bin/main.elf

This is running on an Arduino Uno.

  • 1
    Moving a function (and a global variable) into a separate file might significantly impede compiler's optimization opportunities and change the generated code. Inlining becomes impossible, for one example. Did you try declaring your timer0_overflow_count as static inside the second file (in addition to volatile)? – AnT May 12 at 2:08
  • I was thinking that it could be related to optimisation, as you said it becomes harder for the compiler to optimise code when it is linked in multiple stages like this. I've declared the overflow count as static but it does not seem to make a difference. I guess there may not be a solution to this? I'll have to test the official Arduino micros (with the Arduino IDE) to see if it suffers the same linking issues. – Jack Wilsdon May 12 at 2:16

Your Answer

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

Browse other questions tagged or ask your own question.