Become a subscriber (Free)

Join 29,000 other subscribers to receive subscriber sale discounts and other free resources.
:
:
Don't worry -- youre-mail address is totally secure. I promise to use it only to send you MicroZine.

Arduino PulseIn

The Arduino 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 following examples show correct use for pulseIn and pulseInLong (requires interrupts on).

To measure a high pulse

noInterrupts();
pulseIn( <pin>, HIGH);
interrupts();
or
pulseInLong( <pin>, HIGH);
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 this function to measure frequency (two measurements are needed one to measure the low period and one to measure the high period - unless you know the mark to space ration is even, in which case you only need one measurement). This project uses pulseIn to measure the TCS320 frequency outputs.

pulseIn 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.

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 for the last parameter:

noInterrupts();
pulseIn( <pin>, HIGH, 10000000UL);  // 10 second timeout.
interrupts();

Arduino Pulse Width Measurement Example

The following code expects a signal on pin 7 and uses the pulseIn function to measure it. It is then reported 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

const byte INPIN = 7;

void setup() {
   Serial.begin(9600);
   pinMode(INPIN, INPUT);
}

void loop() {
unsigned long res;

  Serial.print("Time: ");
  Serial.println(millis());
  Serial.println("Measuring...");
  Serial.flush(); // Needed since serial requires interrupts to operate.

  noInterrupts();
  res = pulseIn(INPIN, HIGH, 3000000UL);
  interrupts();

  if (res) {
     Serial.print("Time high (us): ");
     Serial.println(res);
  } else
     Serial.println("No signal present");

}

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.

This shows you the principle reason that you may want to use pulseInLong - millis will work unless interrupts are active. Change the above code to use pulseInLong to check (not forgetting to comment out the interrupt control functions).

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

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.

Conclusion - it's easy to forget that pulseIn is not allowing interrupts - 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.

How does Arduino PulseIn work

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 pulse operation showing signal input and algorithm operation


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:

C:/Program Files/Arduino/hardware/arduino/avr/cores/arduino/wiring_pulse.c

For the assembler version you can find the code in

C:/Program Files/Arduino/hardware/arduino/avr/cores/arduino/wiring_pulse.S

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.

  1. While valid wait for invalid pulse polarity - Wait for end of previous pulse.
  2. While invalid wait for valid pulse polarity - Wait for pulse to start.
  3. While valid polarity increment width pulse - Wait for pulse to stop.

The operation is shown in the diagram below

arduino pulse operation showing signal input and algorithm operation

The function generated by the code is:

countPulseASM

From the C part of the code we have the following prototype (or function definition):

unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout)

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.

unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout)
{
// 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_t bit = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
uint8_t stateMask = (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
unsigned long maxloops = microsecondsToClockCycles(timeout)/16;

unsigned long width = countPulseASM(portInputRegister(port), bit, stateMask, maxloops);

// prevent clockCyclesToMicroseconds to return bogus values if countPulseASM timed out
if (width)
return clockCyclesToMicroseconds(width * 16 + 16);
else
return 0;
} 

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.

Note: PulseInLong will return a longer time period than pulseIn as it will be interrupted by running interrupts.
unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout)
{
// 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_t bit = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
uint8_t stateMask = (state ? bit : 0);

unsigned long startMicros = micros();

// wait for any previous pulse to end
while ((*portInputRegister(port) & bit) == stateMask) {
if (micros() - startMicros > timeout)
return 0;
}

// wait for the pulse to start
while ((*portInputRegister(port) & bit) != stateMask) {
if (micros() - startMicros > timeout)
return 0;
}

unsigned long start = micros();
// wait for the pulse to stop
while ((*portInputRegister(port) & bit) == stateMask) {
if (micros() - startMicros > timeout)
return 0;
}
return micros() - 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. Also remember that if your Arduino uses a resonator it won't be that accurate anyway.

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.

However remember that any interrupt can not be serviced while another interrupt is being serviced. If there are a lot of interrupts firing then the accuracy of pulseInLong will degrade.

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.
//
void setup() {
   Serial.begin(115200);
}

#define INPUTPIN 4

// Controls
#define noPulseInterrupts 1
#define usePulseInLong    0

// Must have interrupts for pulseInLong
#if usePulseInLong
  #define noPulseInterrupts 0
#endif

void loop() {
uint8_t i;
uint16_t avg1,avg2;
static unsigned int stop=0;
unsigned long val;
float f;

  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.

0 497
1 497
2 497
3 497
4 491
5 492
6 491
7 491
8 491
9 491
Avg high:493
-------
0 449
1 447
2 446
3 469
4 493
5 493
6 493
7 493
8 487
9 487
Avg  low:475

-------
Frequency: 1033.06

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.

pulseIn no ints
Frequency: 1012.15
Frequency: 1012.15
Frequency: 1013.17
Frequency: 1013.17
Frequency: 1012.15

pulseIn Ints
Frequency: 1037.34
Frequency: 1040.58
Frequency: 1028.81
Frequency: 1037.34
Frequency: 1030.93

pulseInLong
Frequency: 1015.23
Frequency: 1014.20
Frequency: 1018.33
Frequency: 1016.26
Frequency: 1016.26


New! Comments

Have your say about what you just read! Leave me a comment in the box below.




Privacy Policy | Contact | About Me

Site Map | Terms of Use



Visit our Facebook Page:
To Visit Click Here


Recent Articles

  1. How to use the ADS1115

    A tutorial on using the ADS1115 precision 16 bit ADC for low power use.

    Read more

  2. ESP8266 Webserver in Lua

    ESP8266 Webserver: This code shows you how to use lua to create a webserver using html button inputs to contrtol an LED on the ESP module.

    Read more

  3. ESP8266 NodeMCU Firmware: ESP8266 flash

    How to flash nodemcu firmware into a ESP8266 so you can use the LUA scripting language.

    Read more

  4. NodeMCU Examples

    Nodemcu examples starting off wioth simple ones and progressing to wifi coding. This is an excellent tutorial to start learning how to use nodeMCU with lua.

    Read more

  5. How to use ESPLorer to upload lua scripts

    Find out how to download the ESPLorer and use it to upload lua scripts

    Read more

  6. The TCS230 Color Sensing Chip: How it works and how to use it.

    How to use the TCS230 (/TCS3200) Color detector chip and easily add it to any of your projects.

    Read more

Readers Comments

"I wanted to thank
you so so so much
for all the information
you have provided in
your site it's

SUPERB and FANTASTIC."

- Ranish Pottath

"This site really is
the best and my favorite.
I find here many useful
projects and tips."

- Milan

bursach<at>gmail.com<

"Awesome site,
very, very easy and nice
to navigate!"


- Matt
matt_tr<at>
wolf359.cjb.net


Learn Microcontrollers

"Interested in
Microcontrollers?"

Sign up for The
Free 7 day guide:

FREE GUIDE : CLICK HERE


"I am a newbie to PIC
and I wanted to say
 how great your
site has been for me."


- Dave

de_scott<at>bellsouth.net

"Your site is a great
and perfect work.
congratulations."


- Suresh

integratredinfosys<at>
yahoo.com

"I couldn't find the correct
words to define
yourweb site.

Very useful, uncovered,
honest and clear.

Thanks so much for
your time and works.
Regards."


- Anon

Back to Top