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

this is what I'm looking for..thank you. Where is that macro defined? Was it in the code @johnwasser provided and I missed it? That is the reference documentation I"m inquiring about ---where do I find all these instructions /macros available to the ARduino.?

https://www.nongnu.org/avr-libc/user-manual/group__avr__sfr.html

1 Like

@jreminton,

I think you misunderstand me. I don't know what you mean by "Input Capture Mode does not depend on interrupts". I'm using an signal to generate an interrupt which I will use to stop the counter and get its current value. The counter is not generating the interrupt (except perhaps the overflow one which I will have to handle).
I'm also not measuring any pulse width, I"m measuring the the time for an event to occur after I enable it and wait for it to occur.

I'm using an signal to generate an interrupt which I will use to stop the counter and get its current value.

To avoid interrupt latency, stop doing that and use Input Capture Mode.

Good luck with your project.

1 Like

If you look at the block diagram of timer1 you will see it has dedicated inputs so the trigger and count go directly to the timer without any "high level" commands to cause a delay.
The Timer1 will create interrupts for when things happen with the timer, like overflow etc.

1 Like

@LarryD - so , is the avr-lib automatically loaded and accessible to the Arduino IDE? The compiler understands this C lib? I didn't realize this.

There is no need for the user to include the library, done for us.

If remembering correctly bit(n) might be better as larger shifts can be accommodated.

example: bit(24)

All of the #include files needed are already included by Arduino.h.

The _BV(x) is a macro in one of those files, roughly equivalent to (1 << x) but may be limited to 8 bits.

You can generally use the C-language examples in the datasheet. You can usually read and write 8-bit and 16-bit registers as if they were global variables. Pin names (PB0-7, PD0-7) are just bit numbers (0-7). That's why _BV(x) or (1 << x) is used to make them a bitmask. Some registers have weird behaviors. For example, in most Interrupt Flag registers you CLEAR the flag by writing a 1 bit in the bit location. The locations written with 0 bits do nothing.

Print out a copy of the Arduino board pinout so you can translate between Arduino pin numbers and AVR ports and bits. For example, on the UNO and Nano (ATmega328P) pins 0 through 7 are PD0 through PD7 and pins 8 through 13 are PB0 through PB5. That will be different on other processors like the MEGA and Leonardo.

1 Like

I think you get a lot of benefit from loading @johnwasser 's code and playing with it. For me things become clearer when I'm interacting with the code.

@JohnRob -Agreed...in the process of doing so.

Btw, reviewing NIck Gammon's examples (reply #12 on Nick's webpage), it seems he's using instruction bit() set individual bits in various registers. However, from the Arduino reference documentation, the bit() instruction is used to read the register since, as I understand it, it returns the bit's value. I noticed that @johnwasser's code uses the _BV macro instead. Has the functionality of bit() changed since Nick created his examples? or am I totally misunderstanding the code?

Thanks!

Good discussion of _BV and bit() here
https://forum.arduino.cc/t/bit-versus-_bv/267975

Nick Gammon explains his preference for bit() in reply #4.

1 Like

Well, my first attempt at implementing a solution is giving me problems, and I can't figure out what may be going on. I've added various Serial.println() to track where the code is going wrong, and for some reason, the monitor shows that I go through function "setup_comparator" , but gets messed up (prints messed up characters to the serial monitor) as it starts on function "reset_capacitor" and somehow, I enter function "setup_comparator" again. I don't know how this can happen without going through a reset -- so this is where I'm focusing to find my bug(s).

Anyway, I'm attaching the code to see if I can get some help. I decided to use the analog comparator with the bandgap reference. My intent is to

  • set Timer 1 to input capture mode.

  • then I set up the analog comparator- using the bandgap ref on AIN0 ) and generating an interrupt to the timer1 on the falling edge.

  • then drive an I/0 pin low holding the node between a resistor and a capacitor low to reset the external capacitor. I initially wanted to use the same AIN1 pin as a digital output first to hold the capacitor in reset, and then switch it to the comparator input after I started the timer, but I thought this might be part of the behavior I was seeing, so I decided to have a separate I/O reset the capacitor. This is not what I prefer, since now I'm adding a second I/O's capacitance to the external capacitor (the input capacitances of AIN1 and PD5) which I'm measuring, but at least I can get it working this way first.

  • I then start the timer, enable interrupts, and switch the Digital I/O (PD5) to "release" the external cap and allow it to begin charging.

I set up the comparator and reset the cap first in setup(), and then in loop, I start the timer and wait for the interrupt to occur.
When the interrupt occurs, I calculate the time, and call the reset_cap() after some delay to prepare the capacitor to be measured again.

From what I can tell, I don't seem to come out of setup() correctly.

Here's the code:

// Arduino Input: Digital Pin 8 (ICP1), AVR pin PB0
// If using analog comparator input, use AIN1 (PD7, inverting input), AIN0(PD6,non-inverting)

volatile unsigned long dlyTime;
volatile boolean triggered;

// These constants won't change:
const int COMPIN = 7;    // PD7 used as input to comparator
const int RST_B = 5;    // PD7 used for reseting capacitor


ISR (TIMER1_OVF_vect) // timer overflows (every 65536 counts)
{
  Serial.println ("TimerCounter OVRFLW ");
}  // end of TIMER1_OVF_vect

ISR (TIMER1_CAPT_vect)
  {
  // grab counter value before it changes any more
  dlyTime = ICR1;  // see datasheet, page 117 (accessing 16-bit registers)
  TCCR1B = 0;// Timer 1 control register 8-bit -- Normal Mode/Clock source stopped
  triggered = true;
  TIMSK1 = 0;    // no more interrupts for now
  }  // end of TIMER1_CAPT_vect
  
void setup_comp ()
  {
  Serial.println("in setup_comp");
  //Set up Comparator
  //reference voltage AIN1, sense on AIN0 (using bandgap ref on AIN0, interrupt on falling edge of COMPOUT)
  ADCSRB = 0;  // disable mux--AIN1 connected to neg input of comparator
  ACSR =(0<<ACD)|(1<<ACBG)|(1<<ACIC)|(1<<ACIS1);//enable comparator, bandgap selected, 
  DIDR1 = (0<<AIN1D)|(1<<AIN0D);
  Serial.println("leaving setup_comp");
  return;
  }  // end of setup_comp
  
void reset_cap ()
  {
  noInterrupts (); //protected code
  Serial.println("in reset_cap");
  pinMode(RST_B,OUTPUT);
  digitalWrite(RST_B,0);  //drive output low to reset sense capacitor
  
  TCNT1 = 0; //reset counter
  TIFR1 = (1<<ICF1)|(1<<TOV1); // clear flags so we don't get a bogus interrupt
  ACSR |= (1<<ACI); //clear any comparator interrupt
  triggered = false;
  return;
  } // end reset_capacitor

void start_timer ()
  {
   TIMSK1=(1<<ICIE1)|(1<<TOIE1);  //interrupt on Timer 1 input capture and overflow
   TCCR1B = (1<<CS10);  //timer started
   ACSR |=(1<<ACIE);//enable comparator interrupt\
   interrupts (); 
   pinMode(RST_B,INPUT);  // switch port to comparator input
   
   return;
  }
  
void setup (){
  Serial.begin(9600);       
  Serial.println("Tau");
  delay(1000);
  
  TCCR1A = 0; // Timer 1 control register 8-bit -- Normal Mode
  TCCR1B = (0<<ICES1);// Timer 1 control register 8-bit -- Normal Mode/Clock source stopped; falling edge
  
  setup_comp();
  delay(1000);
  reset_cap ();
  delay(1000);

} // end of setup

void loop () 
  {
  Serial.println("Timer Started...");
  Serial.println("*");
  
  start_timer ();
  Serial.println(triggered);
  //wait until interrupt
  while (!triggered)
  {
    Serial.println("Waiting");
  }
   
  // Calculate time from counts
  float elapsedTime = F_CPU * float (dlyTime);  // each tick is 62.5 ns at 16 MHz
  
  Serial.print ("Time =:  ");
  Serial.println (elapsedTime);

  delay(10000);
  reset_cap ();
  
 }   // end of loop

I'm am a HW guy trying to write code my first Arduino program, so please bear that in mind. I appreciate any help I can get.

Thanks!

Why are you multiplying 'dlyTime' in clock ticks by 16 million? Shouldn't you divide by 16 million to get time in seconds? Or divide by 16 to get time in microseconds?

yes, of course -- it was late last night .. ---but that is the least of my problems!

You can not put Serial.print() functions inside your ISR. Interrupts are off when the routine executes and Serial uses interrupts to clock out the data. It will work partially, but no guarantees.

1 Like

@blh64, that would explain the weird behavior...I'll fix that and try again later today...
thank you.

You can, but you probably shouldn't. Everything will be fine until the output buffer fills up.

When the buffer is full and interrupts are disabled (as they are in an ISR), the serial output goes from interrupt-driven to polling. It will wait ages for the hardware buffer to be available and then move one character out of the software buffer. That will make room for another character of your output. It repeats the waiting until the last of your characters reaches the buffer.

1 Like

The last function call in setup is to reset_cap() where you call noInterrupts().

I don't see where they are renabled.

I enable the interrupts in start_timer (). I copied the code to this post in the "wrong" state when I was debugging it.
With it enabled, I still get the same kind of behavior. It is as if the Arduino were resetting at some point during the execution of the "reset_cap" function, since it seems like the setup () code is executing again. I repeatedly see "in setup_comp" and "leaving setup_comp", followed by "in reset_cap", but I don't see "Timer Started...." nor "Waiting". I will be checking the signals with an oscilloscope tomorrow when I have some time. In the meantime, if anyone sees something blatantly wrong with the code, please let me know.

Thanks!

@johnwasser, @jremington, @johnRob, and anyone else, I need help. I can't figure out what is going on. The Arduino seems to be going through reset at random points. However, when I scope out the RESET pin, it never moves, but I do see the execution of code that should only have happened once. Perhaps memory is getting corrupted somehow?

So, for debug purposes, I've put all the code (except for the timer/capture ISR) in the setup() function:

volatile unsigned long dlyTime;
volatile boolean triggered;

// These constants won't change:
const int COMPIN = 7;    // PD7 used as input to comparator
const int RST_B = 5;    // PD5 used for reseting capacitor
const int comp_int=4;   // PD4



ISR (TIMER1_CAPT_vect)
{
  // grab counter value before it changes any more
  dlyTime = ICR1;  // see datasheet, page 117 (accessing 16-bit registers)
  TCCR1B = 0;// Timer 1 control register 8-bit -- Normal Mode/Clock source stopped
  triggered = true;
  digitalWrite(comp_int,0);
  //TIMSK1 = 0;    // no more interrupts for now
  return;
}  // end of TIMER1_CAPT_vect
  
void setup() {
  pinMode(comp_int,OUTPUT);
  digitalWrite(comp_int,0); //  debug interrupt pin
  
  Serial.begin(9600);       
  Serial.println("Tau");
                                                
  TCCR1A = 0; // Timer 1 control register 8-bit -- Normal Mode
  TCCR1B = (1<<ICNC1)|(1<<ICES1);// Timer 1 control register 8-bit -- Normal Mode/Clock source stopped; falling edge

  //Setup Comparator
  Serial.println("in setup_comp");
  //Set up Comparator
  //reference voltage AIN1, sense on AIN0 (using bandgap ref on AIN0, interrupt on falling edge of COMPOUT)
  ADCSRB = 0;  // disable mux--AIN1 connected to neg input of comparator
  ACSR |=(1<<ACBG)|(1<<ACIC)|(1<<ACIS1); // bandgap selected
  delay(100);
  ACSR &=~(1<<ACD); //enable comparator by clearing ACD bit
  Serial.print("ACSR = 0x");
  Serial.println(ACSR,HEX);
  DIDR1 = (1<<AIN1D)|(1<<AIN0D);
  Serial.println("leaving setup_comp");

  //Reset the capacitor
  Serial.println("in reset_cap");
  pinMode(RST_B,OUTPUT); 
  digitalWrite(RST_B,1);
  delay(200);
  digitalWrite(RST_B,0);  //drive output low to reset sense capacitor
  TCCR1B = 0;
  TCNT1 = 0; //reset counter
  TIFR1 = (1<<ICF1)|(1<<TOV1); // clear flags so we don't get a bogus interrupt
  ACSR  |= (1<<ACI); //clear any comparator interrupt
  triggered = false;

 //Start the timer
 Serial.println("Timer Started...");
   TIMSK1=(1<<ICIE1)|(1<<TOIE1);  //interrupt on Timer 1 input capture and overflow
   //TCCR1B = (1<<CS10);  //timer started
   ACSR |=(1<<ACIE);//enable comparator interrupt
   TCNT1=0;
   Serial.print("TimerCount = 0x");
   Serial.println(TCNT1,HEX);
   TCCR1B |= (1<<CS10);  //timer started
   pinMode(RST_B,INPUT);  // switch port to comparator input
  
  //wait until interrupt
  while(!triggered)
  {
    Serial.print("ACSR = ");
    Serial.println(ACSR,HEX);
  }
  Serial.println("interrupt received, timercnts = 0x");
  Serial.println(ICR1,HEX);

}

void loop() {
  // put your main code here, to run repeatedly:

}

Here's a capture of the Comms console:

All I have connected to the Ardunion UNO is an RC network from 5V to GND, with the common node connected to pins PD7 and PD5 on the Arduino UNO. I'm powered off of USB.

I would appreciate any suggestions!
Thank you.