Timing interval between output and input on pins

I'm working on a project (Arduino Uno) where I have to measure the time between I send a signal out one pin and when it arrives back into another pin (on same chip). So, for the simplest of examples: hooking a wire from an output pin into an input pin and measuring the delay. I've done this the "brute-force" way earlier by just incrementing a number in a loop, and in each iteration checking the input pin and noting the number. This worked.

I figured I'd use timers instead. I've fumbled around with those for some hours now, looking at tutorials reading thoroughly through the ATmega328p documentation about all the registers. A lot of it makes sense, but, either the timer is way too quick (or I am way too slow), or maybe I'm just using the registers wrong. I hope someone can take a look at the code and give me some guidance.

void setup() {
  Serial.begin(9600);
  DDRD = 0b00000100;
  PORTD |= (1 << 2);

    TCCR0B |= (1 << CS01) | (1 << CS00); // Start timer (prescaler = 64)
}

void loop() {
  if (PIND & (1 << 4)) { // The input pin
    Serial.println(TCNT0); // The 8 bit clock counter
    PORTD &= ~(1 << 2); // Clear output pin (is this necessary?)

    while ( (TIFR0 & (1 << TOV0) ) > 0); // Wait for TCNT0 overflow
  
    TIFR0 |= (1 << TOV0); // Clear overflow flag
    PORTD |= (1 << 2);
  }
}

To clarify, I am setting the output pin high, then trying to start the timer, checking the input pin continuously, and when it goes high, checking the clock counter.

Maybe micros() is enough in your case. micros() - Arduino Reference
Capture the value of micros() at the start. At the end, print out the difference between the current value of micros() and the stored value.

I don't know if that's optimal, as this code will run for at least a few years continuously, or maybe I'm wrong.

I tested with micros(), but it's way too slow to capture it.

If you send a signal from an output pin to an input pin on the same chip, this will take less time than a clock cycle on the chip and therefor it will be impossible to measure. You probably need to figure out the speed of electrons across a certain distance and map that to the length of the wire and use that as a constant since it will not change.

I have to measure the time between I send a signal out one pin and when it arrives back into another pin

What is the shortest time that you expect the gap between send and receive to be on the real system ?

I'm attaching a capacitor in parallel with the resistor between them, so I managed to get time down pretty far. Sorry I didn't mention that. So, it should be possible.

That is critical info.. If micros does not catch it nothing will. You may try to go down to the lowest level and count the amount of clock cycles using assembly. Is this what you are trying to do?

Exactly, that's what I was doing. Just didn't manage to get above one microsecond. The only thing I can change is the resistor, and the cap (which is my finger and metal on the other side of glass) is 1 nF at most.

Danois90:
If micros does not catch it nothing will.

Capture compare hardware should get you down to 62.5ns.

The only way I can think of is to try to count the amount of cycles with assembly. This will give a resolution of 0.0625us (or 62.5ns) with a 16mHz processor. But the instructions required for counting and looping will give an overhead of at least 3 cycles.

Danois90:
The only way I can think of is to try to count the amount of cycles with assembly. This will give a resolution of 0.0625us (or 62.5ns) with a 16mHz processor. But the instructions required for counting and looping will give an overhead of at least 3 cycles.

sp. "16 MHz"

I tried with a 1M resistor. That should get me to 0.0000208 seconds. Suddenly I got around thousand micros once, but then it just disappeared on the next run and never showed up again. Is my code okay, or do you see some obvious mistakes?

  Serial.begin(9600);
  DDRD = 0b00000100;  
  PORTD |= (1 << 2);
  int prev_micro = micros();
  while(!((PIND >> 4) & 1));
  int delay = micros() - prev_micro;
  Serial.println(delay);

You need to disable interrupts if it has to be accurate. On 16mHz one cycle is 0.0625 micros, you can try this (untested) code which will use pin 4 as output and pin 5 as input. It will increment a byte every 4 cycles and return the value which can be converted to microseconds by multipling with 0.25.

byte count_cycles()
{
    volatile byte result = 0;
    asm volatile (
        "cli \n"                        //     Clear interrupts
        "sbi %[port], %[pinout] \n"     //2    Set pin to high
        "0: \n"                         //     Loop start
        "inc %[count] \n"               //1    Increment counter
        "sbis %[port], %[pinin] \n"     //1-3  Skip next instruction if input is high
        "rjmp 0b \n"                    //2    Jump to loop start
        "sei \n"                        //     Turn interrupts on again
        "cbi %[port], %[pinout] \n"     //2    Set pin to low
        :
        [count] "+r" (result)
        :
        [port] "I" (_SFR_IO_ADDR(PORTD)), //Use PORTD
        [pinout] "I" (4),                 //Bit 4 in PORTD is output
        [pinin] "I" (5)                   //Bit 5 in PORTD is input
    );
    return result;
}

Please note that the counter will roll over every 63.5 microseconds which is then the maximum time a reliable measurement may take. And if the input pin never gets high the code will be stuck in the loop.

EDIT: Changed "sbr" to "sbi" and added "cbi" to set the output to low when done.
EDIT2: Changed "sbrs" to "sbis".

Danois90:
You need to disable interrupts if it has to be accurate. On 16mHz one cycle is 0.0625 micros, you can try this (untested) code which will use pin 4 as output and pin 5 as input. It will increment a byte every 4 cycles and return the value which can be converted to microseconds by multipling with 0.25.

byte count_cycles()

{
    volatile byte result = 0;
    asm volatile (
        "cli \n"                        //    Clear interrupts
        "sbr %[port], %[pinout] \n"    //2    Set pin to high
        "0: \n"                        //    Loop start
        "inc %[count] \n"              //1    Increment counter
        "sbrs %[port], %[pinin] \n"    //1-3  Skip next instruction if input is high
        "rjmp 0b \n"                    //2    Jump to loop start
        "sei \n"                        //    Turn interrupts on again
        :
        [count] "+r" (result)
        :
        [port] "I" (_SFR_IO_ADDR(PORTD)), //Use PORTD
        [pinout] "I" (4),                //Bit 4 in PORTD is output
        [pinin] "I" (5)                  //Bit 5 in PORTD is input
    );
    return result;
}




Please note that the counter will roll over every 63.5 microseconds which is then the maximum time a reliable measurement may take.

Cool, didn't know I could do it like that.

If you are interrested in AVR assemebly, look at the inline assembler cook book and get a copy of the instruction set manual :slight_smile:

I finally got it working. I had to use two 1M resistors in series in order to slow the signal down enough. Then I used the micros() function and managed to get a reading of somewhere around 4-12 ms.

Danois90:
If you are interrested in AVR assemebly, look at the inline assembler cook book and get a copy of the instruction set manual :slight_smile:

Thank you for the link. I just recently started programming them in c++. I'm mostly used to programming PIC mcu's in assembly. Usually most of them don't have enough memory for c/c++. That's mostly the reason I wanted to use timers originally, because I find it more interesting working directly with the registers than with "high level" Arduino functions like micros(). But sometimes it can be a bit too much. You guys saved me a lot of time, so I'm very thankful :slight_smile:

Great that you got it working, you should disable interrupts when doing these measurements and you should also note that millis() and micros() are "accurate enough" for most things. If you want the highest possible accuracy with small intervals you need to count cycles. I've updated the assembler code, it didn't work due to a wrong instruction selected :-/

Danois90:
Great that you got it working, you should disable interrupts when doing these measurements and you should also note that millis() and micros() are "accurate enough" for most things. If you want the highest possible accuracy with small intervals you need to count cycles. I've updated the assembler code, it didn't work due to a wrong instruction selected :-/

That's good to know. I will probably need it in the future some time.