Arduino PulseIn:How to Measure input
signal periods
using an Arduino. There are two Pulse-In functions. Which one you
should use for best accuracy? Find out why there two functions, why
interrupts must be on for one and off for the other, and why your
measurement might be inaccurate.
Arduino pulseIn:
Pulsein Measures high or low periods of a signal in microseconds.
Why is your pulseIn measurement inaccurate - Find out here.
Why does one function need interrupts On, while the other one needs them Off?
There are two functions pulseIn and pulseInLong...
Find out the difference between them here.
Find out why pulseInLong is uses the word 'Long' - hint: It's not a type definition.
You might be wondering why your Arduino pulseIn measurement is
slightly inaccurate, or even why there are two versions of the function
pulseIn and pulseInLong! You can find out all this and more on this
page.
The pulseIn function measures the time period of a
high or
low pulse input signal. A typical use is to measure the output from an
ultrasonic distance module (HC-SR04) which outputs a signal period
proportional to the round trip sound reflection time from which you can calculate distance.
The simple way to use these functions for measuring a high pulse is simply to insert them into the code.
pulseIn( <pin>, HIGH);
or pulseInLong( <pin>, HIGH);
Note: PulseInLong requires interrupts to be running.
The question now is WHY are there 2 functions? Each of which returns the same result!
You can find out in this page, which explores:
Why there are two functions.
How each function works.
Why each function may or may not be accurate.
Compares the accuracy of each function.
Note: The HIGH/LOW parameter lets you to measure the
high or low period of the signal. You can only perform a HIGH or LOW
measurement not both at the same time.
Measuring Frequency using pulseIn
You can also use pulseIn to measure frequency; two measurements
are needed one to measure the low period and one to measure the high
period (if the M:S ratio is not 1:1). The project in this page link uses pulseIn to measure the TCS320 frequency outputs.
The pulseIn function can measure pulses from
2-3us to 3minutes. It
provides a simple way to measure signal periods on any pin. There are
in fact two versions of the function explored below (pulseIn and
pulseInLong) one uses interrupts and one is coded in assembler.
Note: PulseIn measures the high
or low time period while a signal continuously repeats, and the input
signal must continuously repeat for these functions to work correctly.
The function also has a timeout - if this expires
then the function returns zero (The default timeout is 1 second).
For a different timeout use an unsigned long value (in microseconds) for the last parameter:
noInterrupts();
pulseIn( <pin>, HIGH, 10000000UL); // 10 second timeout.
interrupts();
Note: You can use pulseIn with interrupts active, but results are less accurate.
Using Arduino PulseIn
The following code expects a signal on pin 7 and uses the pulseIn
function to measure it, and then reports the results to the serial interface.
The timeout value is set to 3 seconds and the code also reports when that timeout fires indicating that no signal is present.
// pulseIn Example
constbyteINPIN=7;
voidsetup(){
Serial.begin(9600);
pinMode(INPIN,INPUT);
}
voidloop(){
unsignedlongres;
Serial.print("Time: ");
Serial.println(millis());
Serial.println("Measuring...");
Serial.flush();// Needed since serial requires interrupts to operate.
noInterrupts();
res=pulseIn(INPIN,HIGH,3000000UL); // 3 second timeoutinterrupts();
if(res){
Serial.print("Time high (us): ");
Serial.println(res);
}else
Serial.println("No signal present");
}
Here's the output with a 1kHz signal input to pin7:
Measuring...
Time high (us): 499
Time: 11817
Measuring...
Time high (us): 501
Time: 11869
Measuring...
Time high (us): 500
Time: 11919
Measuring...
Time high (us): 500
Time: 11970
Measuring...
Time high (us): 500
Time: 12020
Note: pulseIn can take up to 1.5 periods of input signal to determine a result.
The above results show that pulseIn is returning a result period of (500+500)us i.e. a frequency of 1kHz.
Warning: Turning off interrupts means millis returns the wrong time.
In the above results each measurement returns 500us as the high
period of the input signal. Since interrupts are turned off during this
period millis() will be out by
n * 500us after each loop (for n loops).
This is still using pulseIn but the reason that millis appears to
work is that the pulseIn function is not timing out, it is taking a
maximum of 1.5ms to return a result.
This shows you the principle reason that you may want to use
pulseInLong() - millis() will not work unless interrupts are
active i.e you can still do regular coding using the millis() function
if and-only-if you use pulseInLong() - because interrupts are active.
Change the
above code to use pulseInLong() to check (not forgetting to comment out
the interrupt control functions).
Arduino PulseIn Problem
The problem with pulseIn is that you really want to use it with
interrupts turned off for maximum accuracy. It is
written in fixed assembler code that works by path timing measurement
i.e. it works by assuming a 16MHz clock speed and measuring the number
of assembler instructions used - resulting in a measured time.
Here's the output of the program with no signal input:
Time: 0
Measuring...
No signal present
Time: 24
Measuring...
No signal present
Time: 70
Measuring...
No signal present
Time: 116
Do you see what's wrong? - No interrupts so millis() can not work in
fact the value should change by at least 3000ms between readings since
the pulseIn timeout time is 3000ms.
Conclusion - it's easy to forget that you stopped interrupts to get a
more accurate pulseIn measurement - all your timings will be out if you
use pulseIn and
millis(). The last reading above are not true time since the interrupts
are off for ~1.5ms while pulseIn completes.
Difference Between PulseIn and PulseIn Long
PulseIn or PulseInLong?
There are two versions of pulseIn:
pulseIn
pulseInLong
They both have the same specification and return the same result i.e.
measure pulse from 2-3us to 3 minutes but the first one uses an assembler
routine to make the measurement while the second one uses the Timer0 interrupt
to calculate the result.
The first one: pulseIn, can be used if interrupts are turned off (and if
they are off will return a more accurate result - since it won't be interrupted
while measuring), whereas the second one can not be used unless interrupts are
turned on!
TIP: Turn off interrupts for a more accurate pulseIn
result.
Arduino pulseIn vs pulseInLong Table
pulseIn vs pulseInLong
Function
Usage
Use with Interrupts
Range
pulseIn()
Best for short pulses. [*]
Must be off [**]
2-3us to 3min
pulseInLong()
Best for long pulses.
Must be on.
2-3us to 3min
* long pulse measurements will have greater error as the calibration is empirical.
** See experiments below showing effect of interrupts on pulseIn accuracy.
It would appear that pulseInLong is the better choice, since it relies
on a hardware interrupt. However if other interrupts fire, then this
routine would have to wait for them to complete e.g. a serial port
interrupt etc. so you may get better results with interrupts off and
using pulseIn().
However, pulseIn has been calibrated empirically (by observation) so its
output is an estimate but it seems to be quite a good one for lower
pulse widths. The problem with long pulse widths is, as the errors build
up, so the error in the reported pulse width will increase.
TIP: pulseInLong() is a better choice for measurement of long pusles as errors do not build up over time (true of pulseIn).
How does Arduino PulseIn work
Note: pulseIn and pulseInLong use the same algorithm to measure a pulse, so the description here applies to both functions
Lets assume that we are going to measure the high portion of a signal.
To measure a pulse, the function has to wait for any existing
pulse to disappear so that it can start a measurement from the beginning
of a complete pulse. So the first thing that the function does is to
check the current state of the input.
1. If the input is HIGH then wait for the signal to go LOW. i.e wait until the input is inactive.
2. Next, wait while the input is LOW (waiting for the active state HIGH).
3. When a HIGH input signal is found then start the count.
4. Wait for the signal to go inactive again (LOW).
5. Stop the count.
The count now represents the time period of the HIGH pulse period.
For a LOW pulse, just swap HIGH and LOW in the above description.
Arduino PulseIn Delay
You can see from the way that pulseIn works (see above) that it is
dependent on the input signal. If you start the function while the input
is active the function will have to wait. There's no way to know when a
signal is starting since the signal is an external asynchronous signal.
Note: This is why there is a timeout parameter - so that if a signal is
missing the function will not stay forever monitoring the input pin.
The function then waits for the entire time of the inactive pulse.
So for an input signal of frequency f (with equal mark to space
ratio) you could be waiting for a period of time from between 1.5T to
0.5T (where T = 1/f) before you get a result.
PulseIn Arduino Source Code
You can find the source code for pulseIn in the following directory:
The following is the source code that is used to generate the auto assembler
output:
/*
* The following routine was generated by avr-gcc 4.8.3 with the following parameters
* -gstabs -Wa,-ahlmsd=output.lst -dp -fverbose-asm -O2
* on the original C function
*
* unsigned long pulseInSimpl(volatile uint8_t *port, uint8_t bit, uint8_t stateMask, unsigned long maxloops)
* {
* unsigned long width = 0;
* // wait for any previous pulse to end
* while ((*port & bit) == stateMask)
* if (--maxloops == 0)
* return 0;
*
* // wait for the pulse to start
* while ((*port & bit) != stateMask)
* if (--maxloops == 0)
* return 0;
*
* // wait for the pulse to stop
* while ((*port & bit) == stateMask) {
* if (++width == maxloops)
* return 0;
* }
* return width;
* }
*
* some compiler outputs were removed but the rest of the code is untouched
*/
After the above comment the assembler output is presented. The
assembler output (not shown) is generated from the original C code shown
in the comment, so to understand what
the assembler output is doing just read the above c code.
Note: Generating assembler in this way means it is not
compiled by the C compiler and therefore means that it can not change when the
C compiler is "improved". So timings will be consistent even if the C code or
compiler changes things. The problem is that it will only operate on the avr so
that is why the directory structure shows avr - other processors will require
different equivalent code.
The code measures the pulse by incrementing the variable 'width' within the
last while loop. The previous two while loops are there to prepare for
measurement.
It is quite simple. if you think of a continuous square wave pulse, and lets
say you want to find the period of the high part of the signal then you would
set the variable 'state' (in the C code below) high which would set the
corresponding bit in statemask high.
The algorithm first finds an instance of
the signal being high (if present but since you don't know if you found it in
the middle of a high period you can not tell if you are at the start of the
pulse. The algorithm then waits for low to ensure that the next high input is the start of a
pulse.
So that is what the 1st two while loops are doing preparing to find the
start of the signal, and the third one performs the measurement while waiting
for the pulse to go low again.
While valid wait for invalid pulse polarity - Wait for end of previous
pulse.
While invalid wait for valid pulse polarity - Wait for pulse to
start.
While valid polarity increment width pulse - Wait for pulse to stop.
The operation is shown in the diagram below
The function generated by the code is:
countPulseASM
From the C part of the code we have the following prototype (or function
definition):
...in which you specify the pin to use as input and the pulse state required
i.e. whether you are looking for an active high or active low pulse, and a
timeout parameter in microseconds - the maximum time to wait for a pulse to
arrive (in microseconds).
The full code is shown below and is used as a wrapper routine around the
assembler code and defines bit, port and statemask as fixed quantities (so that
the assembler code does not have to use digitalWrite etc. and is therefore
fast).
A fudge factor of divide-by-16 takes care of converting from the timeout in
microseconds to cycles through one loop through the assembler code - the
comments say that the assembler routine takes approximately 16 cycles per
iteration.
Warning: The timeout fudge factor (/16) will only allow this
assembler version of pulseIn to operate correctly at 16MHz. This is probably
why there is an interrupt driven version (pulseInLong) - since that version
uses Timer0 it wont use a fudge factor.
At the end of the code, output from the assembler is cleaned up:in the event
of a timeout in the assembler code t return zero width.
unsignedlongpulseIn(uint8_tpin,uint8_tstate,unsignedlongtimeout)
{
// cache the port and bit of the pin in order to speed up the
// pulse width measuring loop and achieve finer resolution. calling
// digitalRead() instead yields much coarser resolution.
uint8_tbit=digitalPinToBitMask(pin);
uint8_tport=digitalPinToPort(pin);
uint8_tstateMask=(state?bit:0);
// convert the timeout from microseconds to a number of times through
// the initial loop; it takes approximately 16 clock cycles per iteration
unsignedlongmaxloops=microsecondsToClockCycles(timeout)/16;
unsignedlongwidth=countPulseASM(portInputRegister(port),bit,stateMask,maxloops);
// prevent clockCyclesToMicroseconds to return bogus values if countPulseASM timed out
if(width)
returnclockCyclesToMicroseconds(width*16+16);
else
return0;
}
You can see that the measurement is empirical where each loop around
the function countPulseASM (the assembler code) is measured as 16
instruction cycles - approximately - since there are condition actions
in that assembler that can cause slight timing variations (depending on
the input signal state).
PulseInLong Arduino Source code
The function pulseInLong follows the same algorithm as the assembler code
above working in a similar way to find the start of a valid pulse (see above
description). The only difference is that the timing mechanism uses the value
from micros() function. This function returns a value calculated from Timer0 and so
pulseInLong requires that interrupts are active.
The timing mechanism uses the standard technique of storing time in an
unsigned long and subtracting this from micros to get the elapsed time.
unsignedlongpulseInLong(uint8_tpin,uint8_tstate,unsignedlongtimeout)
{
// cache the port and bit of the pin in order to speed up the
// pulse width measuring loop and achieve finer resolution. calling
// digitalRead() instead yields much coarser resolution.
uint8_tbit=digitalPinToBitMask(pin);
uint8_tport=digitalPinToPort(pin);
uint8_tstateMask=(state?bit:0);
unsignedlongstartMicros=micros();
// wait for any previous pulse to end
while((*portInputRegister(port)&bit)==stateMask){
if(micros()-startMicros>timeout)
return0;
}
// wait for the pulse to start
while((*portInputRegister(port)&bit)!=stateMask){
if(micros()-startMicros>timeout)
return0;
}
unsignedlongstart=micros();
// wait for the pulse to stop
while((*portInputRegister(port)&bit)==stateMask){
if(micros()-startMicros>timeout)
return0;
}
returnmicros()-start;
}
Pulse Accuracy
The Arduino reference states that both functions are work from 10us
to 3minutes while the comments in the code state they are accurate from
2-3us to 3minutes.
Arduino PulseIn Accuracy
You can increase the accuracy of pulseIn if you turn off interrupts during a
measurement otherwise the code will get interrupted by any running interrupt
e.g. Timer0, changing the pulse measurement time and consequent output. Note
this is only for pulseIn and not for pulseInLong (pulseInLong relies on
interrupts).
The accuracy of the algorithm is hard to say as there is a fudge factor of
divide-by-16. The best you can say is that the code will return consistent
results for the same signal. However from real world measurements it provides useful accuracy.
The pulseIn function is more useful for short pulse measurement.
Arduino PulseinLong Accuracy
The algorithm depends only on the function micros() which is an
interrupt driven timer. Its accuracy is the same as the accuracy of the
timer used in the micros() function i.e. it will have the accuracy of
the main system clock.
This is better suited to measuring long pulses as it does not suffer from empirical accumulated error.
Arduino PulseIn Testing Sketch
The following sketch shows how to use pulseIn and allows you to compare the
operation for different function versions.
The following sketch measures the high and low periods of a signal attached
to pin 4 - you could use any digital pin. The code takes the average of 10
readings for the high period, and 10 readings for the low period, and averages
each set of results. It then adds them and uses floating point to calculate the
frequency of the input signal. Note the signal source is not a very stable one
- just an RC oscillator at 1kHz output frequency.
//
// PulseIn Test program.
//
// Measures 10 high and 10 low periods
// and calculates each average. Then calculates
// The freuqnecy of the signal.
//
// Two test controls are used:
// noPulseInterrupts when high stops interrupts during
// the measurements.
// usePulseInLong when high switches to pulseInLong function.
//
// Input is a 0~5V 1kHz squarewave to INPUTPIN.
//
// Copyright John Main - Free for non commercial use.
//
voidsetup(){
Serial.begin(115200);
}
#defineINPUTPIN4
// Controls
#definenoPulseInterrupts1
#defineusePulseInLong0
// Must have interrupts for pulseInLong
#ifusePulseInLong#definenoPulseInterrupts0
#endif
voidloop(){
uint8_ti;
uint16_tavg1,avg2;
staticunsignedintstop=0;
unsignedlongval;
floatf;
avg1=0;
if(stop==0){
for(i=0;i<10;i++){
if(noPulseInterrupts)noInterrupts();
if(usePulseInLong)val=pulseInLong(INPUTPIN,HIGH,10000);
else
val=pulseIn(INPUTPIN,HIGH,10000);
interrupts();
Serial.print(i);Serial.print(" ");
Serial.println(val);
avg1+=val;
}
avg1/=10;
Serial.print("Avg high:");Serial.print(avg1);
Serial.println("\n-------");
avg2=0;
for(i=0;i<10;i++){
if(noPulseInterrupts)noInterrupts();
if(usePulseInLong)val=pulseInLong(INPUTPIN,LOW,10000);
else
val=pulseIn(INPUTPIN,LOW,10000);
interrupts();
Serial.print(i);Serial.print(" ");
Serial.println(val);
avg2+=val;
}
avg2/=10;
Serial.print("Avg low:");Serial.println(avg2);
Serial.println("\n-------");
f=1.0/((avg1+avg2)*1e-6);
Serial.print("Frequency: ");Serial.println(f);
}
stop=1;
if(Serial.read()=='1')stop=0;
}
Arduino PulseIn Sketch output
The following results are for pulseIn with interrupts on.
The above results are for using pulseIn when interrupts are running i.e.
inaccurate results occur.
The following set of results shows the difference between interrupts on and
off (for pulseIn) and results for pulseInLong (which requires interrupts
active). The input signal is 1013 to 1014 Hz measured on the PIC frequency
counter. It drifted up a little as the source is not high stability.
The most consistent results are for the assembler version (pulsein)
with
interrupts off. When interrupts are on pulseIn results vary a lot.
Interestingly, pulseInLong results are consistently stable and compare
very well
to pulseIn (ints off).
If you need to have interrupts active then pulseInLong will give acceptable
results.
The function pulseIn() uses empirical measurement; the longer the pulse the more error accumulates so use it for short pulses.
Use pulseInLong() for longer pulses (the opposite argument to the statement above). This is why the function uses the word 'Long' - it is because the function is more accurate for longer duration pulses.
To make pulseIn more accurate turn off interrupts when using it, but remember the millis and micros timers will also be stopped!
How to get accurate DHT22 digital humidity sensor readings with an Arduino. Did you know it also measures temperature as Well? Find out why, in this page...
A PIR sensor lets your Arduino sense movement without contact. This tutorial covers PIR sensor basics, connecting one to an Arduino board and coding a motion detector.
Arduino Hall Effect Sensor: Add magnetic sensing superpowers to your Arduino projects with an easy-to-use hall effect sensor. With full code and layout...
Comments
Have your say about what you just read! Leave me a comment in the box below.
Don’t see the comments box? Log in to your Facebook account, give Facebook consent, then return to this page and refresh it.