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 pulse in microseconds and can measure pulses from 2-3us to 3minutes. It provides a simple way to measure signal periods on any digital pin. There are in fact two versions of the function explored below. The functions themselves do not use any fancy internal hardware - they are wiritten in pure c code - although one of them is converted to assmbler code (see below) and both give good results.

Note: Function input controls allow you to measure the high or the low period of the signal.


How does Arduino Pulsein work

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 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 - this is auto generated assembler from the original C code shown above, 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 so that the next high 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;
} 

Arduino unsigned long Pulsein

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(). micros() 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;
} 

Arduino Accuracy of Pulsein

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 Pulsein 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;
 
}

Pulsin 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 interrputs active). The input singnal 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 assmbler version (pulsein) with interrupts off. When interrupts are on pulseIn results vary a lot. Interesingly, 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:

   Click Here



Recent Articles

  1. [Arduino Tutorial] : How to use the 74HC595 shift register with shiftOut().

    The Essential Guide to the 74HC595; What it is and how you can easily use one in any of your projects.

    Read more

  2. Pic Programmer Types

    Which pic programmer do you need? This page discusses PIC programmers and gives some essential information on choosing or building your own programmer.

    Read more

  3. Arduino Pulsein: an easy way to measure pulse periods in microseconds.

    learn how to use Arduino pulseIn and pulseInLong to get the most accurate pulse measurement on an Arduino.

    Read more

  4. Arduino millis

    How to use Arduino millis() for deylays but still make the processor do work - Stop using delay()

    Read more

  5. Multiple MCP23017 Interrupt tutorial for operating multiple interrupts from multiple MCP23017s.

    This MCP23017 Interrupt tutorial shows you how to connect interrupt outputs from several MCP23017s to a single microcontroller interrupt pin.

    Read more

  6. Arduino delay() How to use it (and why you should not use it!)

    Arduino Delay() is a useful function but not for large programs - Find Out Why Here...

    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