Measuring Pulse Width

Hello everyone.

I have developed an algorithm through which I am measuring pulse width by giving the pulses to the digital pin D8, my code takes one count after every 4 microseconds and then I have applied a formula in my code through which I am measuring pulse widths but the problem I am facing is that I have to measure pulse width of 10 microsecond but when I applied 10 microsecond pulse to arduino it either measures 8 microsecond or 12 microsecond. I would be extremely greatful if someone can help me out with detecting the pulse widths accurately. Here is my code

#define ICP PINB0
//Variables holding three timestamps
volatile uint16_t ov_counter, rising, falling;
//capture Flag
volatile unsigned int flag = 0;
volatile uint32_t counts;
volatile uint16_t time;
int val = 0; // variable to store the read value
int lastPulse = LOW;
//Initialize timer
float pulse_width= 0.0;

//capture ISR
ISR(TIMER1_CAPT_vect)
{

//if(ICP)
if (digitalRead(8) == HIGH)

{

//save start time

rising=ICR1;

//set to trigger on falling edge

//TCCR1B&=~(1<<ICES1);
TCCR1B &= 0xBF;
//reset overflow counter

ov_counter=0;

}

else

{

//save falling time

falling=ICR1;
TCNT1 = 0;
//rising edge triggers next

//TCCR1B|=(1<<ICES1);
TCCR1B |= 0x40;

counts=(uint32_t)falling-(uint32_t)rising + (uint32_t)ov_counter;

/you can convert coutns to seconds and send to LCD/

}
}
//Overflow ISR
ISR(TIMER1_OVF_vect)
{
//increment overflow counter
ov_counter++;
}

void setup()
{
Serial.begin(9600) ;
delay(100);
Serial.println("test");
//Set Initial Timer value
TCNT1=0;
//First capture on rising edge
TCCR1B|=(1<<ICES1)| (1<<CS10) | (1<<ICNC1);
//TCCR1B|=(1<<ICES1)| (1<<CS10);
//Enable input capture and overflow interrupts
TIMSK1|=(1<<ICIE1)|(1<<TOIE1);
//Enable global interrutps
sei();
}

void loop()
{

val = digitalRead(8); // read the input pin
if (val != lastPulse)
{

lastPulse = val;

if (val == LOW)
{

// cli();
pulse_width = counts*4;
Serial.print("width = ");
Serial.println(pulse_width);
//clear overflow counters;
ov_counter=0;

//clear interrupt flags to avoid any pending interrupts
// TIFR1=(1<<ICF1)|(1<<TOV1);
//enable input capture and overflow interrupts
// TIMSK1|=(1<<ICIE1)|(1<<TOIE1);
// highCounter++;

// sei();

}

}

}

Timer granularity is 4 microseconds.
Please go back to your post, click on "modify", then highlight the code, then click on the # icon on the toolbar, then click on "save"

hey

This is the highest which you can get I don't think that you can further make the counter faster than this

If you wrote a loop to busy-wait for the end of the pulse, the loop count would correlate to the pulse length. I think a resolution on the order of 4 or 5 instructions -- a few hundred nanoseconds could be achieved.

You can get pretty accurate readings, but you have to work hard for it. Declare some variables:

volatile unsigned short onTime;
volatile unsigned short offTime;
volatile bool measuring;
volatile bool gotPulse;

First, you have to set up timer 1 to tick at least once per microsecond.
Second, you have to arrange to get interrupts at both "pulse on" and "pulse off" events.
Third, your "on" event will check if "gotPulse" is false, and if so, set "measuring" to true and put the value of timer 1 into variable "onTime"
Fourth, your "off" event will check if "measuring" is true, and if so, put the value of timer 1 into variable "offTime" and also set "gotPulse" to true and "measuring" to false.
Finally, your loop() function will continually test if "gotPulse" is true. If it is, it will calculate "offTime-onTime" and then set "gotPulse" to false.
The offTime - onTime calculation is the number of timer ticks for the duration of the pulse, up to the max count of the timer (which for timer1 is 16 bits -- 65535 ticks)

You have to make a compromise here since the width of the pulses are too small in your case and I don't see that you can measure the widths accurately

@ gardner and jwatte

Thanks for the reply and actually I am still stuck with my code so can anyone of you please modify it accordingly I have to move on to other things after detecting pulse widths and right now I am stuck with this problem and could not able to solve it so I would be greatful if you could modify my program?

Looking forward and thanks in advance

If you want me to write code fore you, even though I described exactly how you should write it, I am available for consulting. PM me for my rate and term sheets.

Should this go to Gigs and Collaborations?

Your code hints are rather clear to me, however :

jwatte:
First, you have to set up timer 1 to tick at least once per microsecond.

That sounds interesting.

  • How can I do that ?
  • What's the maximum timer speed for a 16MHz Arduino ? ( 1s/16,000,000 is obviously too fast )
  • How many commands can a interrupt routine handle at that speed ?
    How many cycles take a dozen of 'if' and ++ commands, roughly estimated,
    what else has to be added to get control moved to the IR and back to the main loop ?
  • What happens if another interrupt arrives before the routine has finished ?
  • Can loop() detect an overload ?

I saw an average of 11µs per loop on my idle Arduino UNO (counting 1,000,000 loops in 11 sec)
Working in the < 1 µs range looks fascinating.

@Michael_X

You meant that you were able to count as fast as less than 1 us? is it so?

you were able to count as fast as less than 1 us? is it so?

No. (Not yet. I found in playground Arduino Playground - Timer1, but I'm not sure how to set a prescale of 8 or even 1) Sounds fascinating, though.

I'm a newbie and all I know is:
One can measure in 4 µs units using the micros() method and I noticed that a dummy loop() usually takes 12 µs, which one can verify like this:

long count=0;
loop()
{
   if (++count == 1000000)
   {
      double speed = (double)millis() / 1000. ;  // about 11 millis per 1000 loops
      Serial.Write (speed);
      Serial.WriteLine(" microseconds per loop().");
   
   }
}

This message appears after a couple of seconds (11) .

You should use directport manipulation - Arduino Reference - Arduino Reference -

This code waits until PIN 3 goes HIGH, and then starts counting

//
//    FILE: PulseWidthMeter.pde
//  AUTHOR: Rob Tillaart
//    DATE: 2012-mar-20
//
// PUPROSE: 
//

unsigned long count = 0;
void setup()
{
  Serial.begin(9600);
  Serial.println("pulse width meter 0.1");

  pinMode(3, INPUT);  
}

void loop()
{
  count = 0;
  while ((PIND & B00001000) == B00000000); // wait for HIGH
  unsigned long start = micros();                 // this line influences the reading...
  while ((PIND & B00001000) == B00001000) count++; // start counting until LOW
  unsigned long stop = micros();  

  Serial.print("CNT: ");
  Serial.println(count, DEC);
  Serial.print(stop-start, DEC);
  Serial.println(" microseconds ");
  Serial.print((1.0*count)/(stop-start), 3);
  Serial.println(" count per microseconds ");

  delay(1000);
}

The output:

CNT: 9447
5348 microseconds 
1.766 count per microseconds 
CNT: 9852
5584 microseconds 
1.764 count per microseconds 
CNT: 13755
7784 microseconds 
1.767 count per microseconds 
CNT: 3182753
1801088 microseconds 
1.767 count per microseconds 
CNT: 5843119
3306556 microseconds 
1.767 count per microseconds 
CNT: 905900
512644 microseconds 
1.767 count per microseconds 
CNT: 7752
4388 microseconds 
1.767 count per microseconds 
CNT: 16149
9144 microseconds 
1.766 count per microseconds 
CNT: 5033
2852 microseconds 
1.765 count per microseconds 
CNT: 4023
2276 microseconds 
1.768 count per microseconds

Note from the code that the setting of start=micros() influences the measurement!! as this is in the measurement loop

As one microsecond = 16 instructions one can see that this count++ loop with direct port manipulation uses 9/16 usec per count. (16/9 = 1.778 which is about the constant found)

So this way you can measure pulsewidth's to almost 0.5 uSec accurate ...

As the unsigned long will overflow @ 4 billion++ so it will overflow after 2415 seconds ~ 40 minutes.

michael_x:

  • What's the maximum timer speed for a 16MHz Arduino ? ( 1s/16,000,000 is obviously too fast )

Look at this data sheet: http://www.atmel.com/Images/doc8025.pdf
Chapter 16 talks about Timer1, which can count up to 65535. Doing that at a frequency of main clock is possible (clock source == 1)
Note that you don't actually generate interrupts on this timer, only from the rising/falling edges of your pulse. (This is in the context of the original question -- your use case may be different)
Also pay attention to the note about disabling interrupts while reading/writing 16-bit registers from the main code. (Interrupts are already disabled inside an interrupt handler)

  • How many commands can a interrupt routine handle at that speed ?

Not many. There are several microseconds just to enter and leave the interrupt handler. You typically just want to assign a global variable or two (as I said in the description)

  • What happens if another interrupt arrives before the routine has finished ?

It gets queued. If you are too slow to keep up over time, you will miss some interrupts.

  • Can loop() detect an overload ?

No, because if you're overloaded with interrupts, loop() doesn't execute at all. You will need the watchdog timer to detect this.

Note that the time through your main loop() is not very critical, as long as pulses arrive less often than the time of the loop, and/or as long as it's OK to miss measuring certain pulses. (Again, within the context of the original question)

Variation on previous code, now with an unsigned int as counter iso unsigned long.

//
//    FILE: PulseWidthMeter.pde
//  AUTHOR: Rob Tillaart
//    DATE: 2012-mar-20
//
//    LINK: http://arduino.cc/forum/index.php?action=post;topic=96971.0
//

unsigned int count = 0;
void setup()
{
  Serial.begin(9600);
  Serial.println("pulse width meter 0.1");

  pinMode(3, INPUT);  
}

void loop()
{
  count = 0;
  while ((PIND & B00001000) == B00000000); // wait for HIGH
  unsigned long start = micros();
  while ((PIND & B00001000) == B00001000) count++; // start counting until LOW
  unsigned long stop = micros();  

  Serial.print("CNT: ");
  Serial.println(count, DEC);
  Serial.print(stop-start, DEC);
  Serial.println(" microseconds ");
  Serial.print((1.0*count)/(stop-start), 3);
  Serial.println(" count per microseconds ");

  delay(1000);
}

Output:

CNT: 40466
4416148 microseconds 
0.009 count per microseconds 
CNT: 11842
2575768 microseconds 
0.005 count per microseconds 
CNT: 29149
233520 microseconds 
0.125 count per microseconds 
CNT: 28208
10648 microseconds 
2.649 count per microseconds 
CNT: 26950
10168 microseconds 
2.650 count per microseconds 
CNT: 23250
8768 microseconds 
2.652 count per microseconds 
CNT: 20794
7852 microseconds 
2.648 count per microseconds 
CNT: 18987
7168 microseconds 
2.649 count per microseconds

As you can see the int counter overflows much faster but had a greater accuracy => 16/2.650 ~= 6 clock cylces (where the long has 9)
So one can measure pulsewidth in steps of 6/16 usec = 0.375 usec.

Overflow is at 65536 => 24.575,625 usec -> about 24.5 millis()

Advanced:
ALthough the counter overflowed we can derive from the microseconds how often it overflowed!

Example from output above:

CNT: 29149
233520 microseconds
0.125 count per microseconds

233520 / 24575,625 ~= 9.50... => 9 overflows

So the total counts become = 9 x 65536 + 29149 = 618973 counts

618973 x 6/16 usec = 232114,875 usec

Note that in practice the drift of the clock can be 10 ppm (or higher) so not all digits in the number above are significant.
Assuming 10ppm the number would be:

232114,875 usec ==> 232114 +-3 usec

@Robtillaart

Thank you so much for helping me out here but actually your code is giving me similar results as I was getting before the problem is that I have to measure the pulse widths between 10 to 15 microseconds accurately but your code as well as my code gives 8 or 12 microsecond for a 10 microsecond width input pulse and similarly it gives either 12 or 16 for pulse widths between 12 and 16 so that is the problem which I have been facing. The counter takes about 4 microsecond so is there any way to improve from this?

Thanks again

Hi,
I keep meaning to look into it myself and never get the time, but isn't the input capture feature of hardware timer1 exactly what you need to measure these small intervals ?

Duane B

rcarduino.blogspot.com

yes I need to measure that and it will in turn give me pulse width

Hi,
Just to be clear, I am referring to the input capture functionality of the timer, this is a timer configuration which will copy the exact timer count into a dedicated register when the pin state changes, you can then read this captured hardware timer value from your code. As this is using the hardware timer directly, its not reliant on or effected by any code that may be running.

Not sure if thats what you understood.

Duane B

rcarduino.blogspot.com

Thank you so much for helping me out here but actually your code is giving me similar results as I was getting before the problem is that I have to measure the pulse widths between 10 to 15 microseconds accurately but your code as well as my code gives 8 or 12 microsecond

The micros() was just a reference timing, find below a stripped pulsewidth meter for periods smaller than 24576 micros based on a tight software loop. One iteration takes 6/16 microsecond so we can 'measure" the following steps between 10 and 15 usec. So approx 15 steps.

26	9,750
27	10,125
28	10,500
29	10,875
30	11,250
31	11,625
32	12,000
33	12,375
34	12,750
35	13,125
36	13,500
37	13,875
38	14,250
39	14,625
40	15,000
41	15,375
//
//    FILE: PulseWidthMeter.pde
//  AUTHOR: Rob Tillaart
//    DATE: 2012-mar-20
//
//    LINK: http://arduino.cc/forum/index.php?action=post;topic=96971.0
//

unsigned int count = 0;
void setup()
{
  Serial.begin(9600);
  Serial.println("pulse width meter 0.2");

  pinMode(3, INPUT);  
}

void loop()
{
  count = 0;
  while ((PIND & B00001000) == B00000000); // wait for HIGH
  while ((PIND & B00001000) == B00001000) count++; // start counting until LOW

  float usec = 1.0 * count * 6/16;
 
  Serial.print("CNT: ");
  Serial.println(count, DEC);
  
  Serial.print(" equals ");
  Serial.print(usec, 2);
  Serial.println(" microseconds.");

  delay(1000);
}

With the hardware timer more accuracy should be possible.