Capturing timer value based on external interrupt , leveraging the 62.5nsec (16MHz) resolution) of the ATMEGA328 on the Arduino Uno

Hi, I want to use the 16-bit timer in the UNO (ATMEGA328), so I can use the 16MHz resolution, ie, I need to count in 62.5nsec. I found all these timer functions, but I still don't see how can can access the "hardware" counter directly, so I capture it's value based on an external interrupt.

I need to reset the timer, and let it go on some event, and then stop it (at 62.5nsec resolution) on some interrupt and capture its value. I would appreciate any hints or referrals to documentation that may explain to me how to do this. Thank you.
Jorge

Have you consulted the ATMega328 datasheet yet? What you want to do is all documented there.

I'm not entire sure what you want here. clear some counter, enable some external interrupt source and then when the interrupt source triggers, see how many clock "ticks" have happened?

The simplest method would be to use micros(), but that only have a resolution of 4usec on an Uno. If you move to A Teensy board, you can get 1usec resolution.

If that is not sufficient and you ned to 62.5nsec resolution, then the "hardware" is simply the Timer1/Counter1 register TCT1. All the registers in the datasheet are defined in the IDE so you just read/write them like a pre-defined variable. But this may be much more difficult to implement correctly.

Why ?

That would be "16-bit Timer/Counter1 with PWM"

You would not use a normal interrupt pin but a pin going directly to the time. However to measure a pulse width using this timer you have to:

  1. setup the registers
  2. set the edge detector to rising
  3. once the rising edge is detected you then have to change the edge detector to falling.
  4. read the results or keep count of the overflows and then stopped read results.

image

Here is an example sketch I wrote to demonstrate the use of the Input Capture Register of Timer1:

// Measures the HIGH width, LOW width, frequency, and duty-cycle of a pulse train
// on Arduino UNO Pin 8 (ICP1 pin).  

// Note: Since this uses Timer1, Pin 9 and Pin 10 can't be used for
// analogWrite().

void setup()
{
  Serial.begin(115200);
  while (!Serial);

  // 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(ICF1); // clear Input Capture Flag so we don't get a bogus interrupt
  TIFR1 |= _BV(TOV1); // clear Overflow Flag so we don't get a bogus interrupt

  TCCR1B = _BV(CS10) | // start Timer 1, no prescaler
           _BV(ICES1); // Input Capture Edge Select (1=Rising, 0=Falling)

  TIMSK1 |= _BV(ICIE1); // Enable Timer 1 Input Capture Interrupt
  TIMSK1 |= _BV(TOIE1); // Enable Timer 1 Overflow Interrupt
  interrupts ();
}

volatile uint32_t PulseHighTime = 0;
volatile uint32_t PulseLowTime = 0;
volatile uint16_t Overflows = 0;

ISR(TIMER1_OVF_vect)
{
  Overflows++;
}

ISR(TIMER1_CAPT_vect)
{
  static uint32_t firstRisingEdgeTime = 0;
  static uint32_t fallingEdgeTime = 0;
  static uint32_t secondRisingEdgeTime = 0;

  uint16_t overflows = Overflows;

  // 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++;

  if (PulseLowTime == 0)
  {
    if (TCCR1B & _BV(ICES1))
    {
      // Interrupted on Rising Edge
      if (firstRisingEdgeTime)  // Already have the first rising edge...
      {
        // ... so this is the second rising edge, ending the low part
        // of the cycle.
        secondRisingEdgeTime = overflows; // Upper 16 bits
        secondRisingEdgeTime = (secondRisingEdgeTime << 16) | ICR1;
        PulseLowTime = secondRisingEdgeTime - fallingEdgeTime;
        firstRisingEdgeTime = 0;
      }
      else
      {
        firstRisingEdgeTime = overflows; // Upper 16 bits
        firstRisingEdgeTime = (firstRisingEdgeTime << 16) | ICR1;
        TCCR1B &= ~_BV(ICES1); // Switch to Falling Edge
      }
    }
    else
    {
      // Interrupted on Falling Edge
      fallingEdgeTime = overflows; // Upper 16 bits
      fallingEdgeTime = (fallingEdgeTime << 16) | ICR1;
      TCCR1B |= _BV(ICES1); // Switch to Rising Edge
      PulseHighTime = fallingEdgeTime - firstRisingEdgeTime;
    }
  }
}

void loop()
{
  noInterrupts();
  uint32_t pulseHighTime = PulseHighTime;
  uint32_t pulseLowTime = PulseLowTime;
  interrupts();

  // If a sample has been measured
  if (pulseLowTime)
  {
    // Display the pulse length in microseconds
    Serial.print("High time (microseconds): ");
    Serial.println(pulseHighTime / 16.0, 2);
    Serial.print("Low time (microseconds): ");
    Serial.println(pulseLowTime / 16.0, 2);

    uint32_t cycleTime = pulseHighTime + pulseLowTime;
    Serial.print("Cycle time (microseconds): ");
    Serial.println(cycleTime / 16.0, 2);

    float dutyCycle = pulseHighTime / (float)cycleTime;
    Serial.print("Duty cycle (%): ");
    Serial.println(dutyCycle * 100.0, 2);

    float frequency = (float)F_CPU / cycleTime;
    Serial.print("Frequency (Hz): ");
    Serial.println(frequency, 2);
    Serial.println();

    delay(1000);  // Slow down output

    // Request another sample
    noInterrupts();
    PulseLowTime = 0;
    interrupts();
  }
}
3 Likes

what do you mean why? Because my application requires it. I'm measuring events that are in hundreds of nanoseconds and the mill() or micro() won't do. The AVR on the UNO is running at 16MHz, so that is the best I can hope for. If it were running at 32MHz, I'd want that resolution instead.

1 Like

If you want to time things down to this level use dedicated timing circuit.

When coding gets involved, your response times are in the garbage, especially on a slow 16MHz controller.

Thanks @JohnRob. Would the resolution of this method be the system clock, ie, 16MHz? I'll dig into it.
To follow up on my question, though, if I were programming the AVR in assembler, I think I would be able to reset the 16-bit timer (not necessarily use the PWM function), and start it counting based on 16MHz clocks. The ISR from the edge interrupt from pin 2, for example, would then stop the timer, disable interrupts, capture its value, and reset it back to 0. I would then restart the timer sometime later, and repeat the whole thing.
I'm "newish" to using the Arduino, and I am not sure how to use the abstraction the Arduino language provides to access the low level registers in the micro so that I can use its resources beyond what Arduino language provides (or maybe the Arduino language already does provide this mechanism).
Anyway, what I'm trying to do is pretty straightforward. There is an external circuit that is enabled after the internal 16-bit timer is enabled followed by the interrupts. When the external circuit detects an event, it drives pin 2 high triggering its ISR. In that ISR, I want to capture the value of the timer/counter. I want the counter to increment at the 16MHz rate. Is this possible with the Arduino?

Is the micro not running at 16MHz? then the timer should be able to be clocked at that rate - or am I missing something?
That's the point, there is no coding --I"m trying to use the hardware timer in the micro. The only timing error should be due to the interrupt latency.

What is the minimum interval you need to react to, then how often might this repeat itself ?

Give this a read:

The capture timing in hardware Input Capture Mode does not depend on interrupts. An interrupt flag is set when the timer's job is done, though.

+1 for Gammon's excellent tutorial on Input Capture Mode at the link in post #12 above.

@johnwasser -- I think this code will help understand how to program the Arduino --it will help me a lot- thank you.

Did you have to import some library so that the compiler could "understand" the register names?
What is _BV? Is that an Atmel instruction or an Arduino instruction?

Can you point me to any reference material where I could see how to access the ATMEGA328 at the the register level with the Arduino language? I'll keep looking through their documentation. Thanks again.

No. All the register names, as given in the processor data sheet, are incorporated when you specify the Arduino board for compilation.

You really should go through Nick Gammon's page. And, of course, study the processor data sheet, timer section.

I believe the post from @johnwasser does exactly what you are asking for.

As for registers, looking at johnwasser's code the register names can be used to read or write (if it can be written to) i.e. TCCR1B

image

_BV is a macro which returns the “value” of a bit . It is: #define _BV(bit) (1 << (bit))

the repeat interval can be as slow as I want, but the minimum interval from starting the timer to the external event occuring can be a minimum of around 9microseconds, but can vary by 100s of nanoseconds and I'm interested in that variation-- thus the need for the 62.5nsec/count granularity ot the counter.

You should look into using an ESP8266 which runs at 80Mhz

1 Like

great...Thank you. I'm familiar with the data sheet and the timers in AVR assembler, but was unsure about accessing the low level registers of the micro with the Arduino "higher level" language . It is the Arduino side of things that I was most unsure about.