Pulse width measurement

Hi Guys,

Is Arduino suitable to measure pulse width in microseconds accurately (pulse widths > 20 microseconds).

If so, what the basic code would be to get me started.

Thanks in advance.

pulseIn()

int pin = 7;
unsigned long duration;
unsigned int data;
float buf = 0;
float output=0;
int bit_value;
int i=0;
void setup()
{
  pinMode(pin, INPUT);
  Serial.begin(9600);
}

void loop()
{
  
  duration = pulseIn(pin, HIGH);
  if(duration > 4000 && duration <4700)  
  for(i=0;i<=15;i++){
  data = pulseIn(pin, LOW);
  if(data>30 & data<43)
  bit_value = 0;
    
  else if(data>70 & data<85)
  bit_value = 1;

buf = buf + (bit_value*pow(2,i)*0.138);

  }
  if(i=11)
  output = 14.5*(output + buf);

  if(i=15)
  buf = 0;
    Serial.println(output);
  i = 0;
  duration =0;
}

I have written this code to decode the pwm signal (please see attached image), i am not getting any meaningful result. Any suggestions please. Thanks.

If you want the least overhead in terms of timing error, I would suggest using two interrupts. Set one for RISING, the other for FALLING, and attach your pulse-in line to both. When the leading edge triggers the first interrupt, record micros() and activate the second interrupt. The trailing (FALLING) edge of the pulse activates that interrupt, so read micros() again, and take the time difference. I have no idea how much jitter would be in these readings, but they'd probably be as accurate as you could hope for.

Actually, I've never tried it, but maybe you can declare two interrupts one the same pin (2 or 3), as long as they are not triggered by the same criterion.

Have you tried the pulseIn() function?

regards

Allan

allanhurst:
Have you tried the pulseIn() function?
regards
Allan

I suggested that in reply #1, technophile showed his code trying to use it in reply #2.

@technophile, that's not a PWM signal, it's a 16-bit data stream. What format is it in? (There might be an existing library.)
Do you have a link to the pressure sensor datasheet?

Thank your guys for your response and please excuse my lack of knowledge (@oldsteve). Unfortunately

I don’t have access to full datasheet, but I have attached another document, please have a look. It says

the output data format is 16 bit PWM. Any suggestions please. Thanks.

PWM_datasheet.PNG

technophile:
Thank your guys for your response and please excuse my lack of knowledge (@oldsteve). Unfortunately

I don’t have access to full datasheet, but I have attached another document, please have a look. It says

the output data format is 16 bit PWM. Any suggestions please. Thanks.

Well I guess it could be called PWM, but not in the usual sense. 16-bit PWM is PWM with a varying duty-cycle and 16-bit resolution, not a 16-bit serial data stream.

Do you have a part number and manufacturer for the sensor? A website? Where did you get the sensor from?
Even if the data format doesn’t have a name, chances are that there’s an existing library for the sensor.

There are some serious problems with your code.
These:-

if(data > 30 & data < 43)
.
.
else if(data > 70 & data < 85)

should look like this:-

if(data > 30 && data < 43)
.
.
else if(data > 70 && data < 85)

And these:-

if(i = 11)
.
.
if(i = 15)

should look like this:-

if(i == 11)
.
.
if(i == 15)

Edit: I think that you’ll be missing the LSB.
You’re using ‘pulseIn()’ to measure the period that the pin stays high, before starting to measure the pulse widths of the actual data. That ‘idle’ period measurement ends when the pin goes low. Then, when you call ‘pulseIn()’ to measure the width of the first data bit, the pin will already be low. ‘pulseIn(pin, LOW)’ waits for a transition from high to low, but that first transition will have already happened.

The delay between readings can be as short as 2.3ms too, so waiting for a high between 4ms and 4.7ms in length might not be such a good idea.

Edit2: Just thinking, one way around the problem that would still allow you to use the ‘pulseIn()’ method would be to use ‘micros()’-based timing in conjunction with digitalRead() to find the high period between each valid data transmission. If the pin remains high for 2000us, call ‘pulseIn(pin, LOW)’ to start receiving the first data bit. When waiting for the first data bit with ‘pulseIn()’, set a timeout in excess of the longest time remaining after 2mS has elapsed. ie 2700us timeout.

Many thanks for your suggestions @oldsteve. I am trying to find out the full datasheet and will post it as

soon as I find it. I’ve amended my code as you suggested (i.e. && , ==), but still no gains.

I am gonna try Digital.read() and micros(), see what happens. Fingers crossed. Thanks.

Id go for this approach: PSUEDO CODE

Some volatiles (code ones…not chemical ones…)

unsigned int data=0;
volatile boolean streaming=false;
volatile unsigned long time=0;

The have a falling interrupt that will set the “streaming” boolean to 1 so the main loop knows shit is happening.
It also sets the volatile timer to the current micros().

streaming = true;
time = micros();

The main loop then basically initially waits for the “false” streaming boolean to flip to true.
Once it sees that the boolean streaming variable is now true…it will wait for a rise in the signal by constantly reading the pin…Once that happens, it calculates the time for that period between the fall and rise using micros()…

while (!streaming){ // Do nothing!}
while (!in_pin_state){state = digitalRead(in_pin)} // wait for the pin to go high again
//Now that it has gone back high...calculate the time between the fall and rise.
period = micros() - time;
//Now either add a 1 to the data int and shift it to the left or just shift left (0).
if (period > 50uS) {
data +=1;
data=data<<1;
}
else{
data = data << 1;}

You will need to add like timeout function (so after say 10ms it will reset the data and streaming variable to 0).

I have done this off the top of my head…so no idea if it is a good start or pants.

Johnny010:
Id go for this approach: PSUEDO CODE

Some volatiles (code ones…not chemical ones…)

unsigned int data=0;

volatile boolean streaming=false;
volatile unsigned long time=0;




The have a falling interrupt that will set the "streaming" boolean to 1 so the main loop knows shit is happening.
It also sets the volatile timer to the current micros().



streaming = true;
time = micros();




The main loop then basically initially waits for the "false" streaming boolean to flip to true.
Once it sees that the boolean streaming variable is now true...it will wait for a rise in the signal by constantly reading the pin...Once that happens, it calculates the time for that period between the fall and rise using micros()...



while (!streaming){ // Do nothing!}
while (!in_pin_state){state = digitalRead(in_pin)} // wait for the pin to go high again
//Now that it has gone back high…calculate the time between the fall and rise.
period = micros() - time;
//Now either add a 1 to the data int and shift it to the left or just shift left (0).
if (period > 50uS) {
data +=1;
data=data<<1;
}
else{
data = data << 1;}





You will need to add like timeout function (so after say 10ms it will reset the data and streaming variable to 0).

I have done this off the top of my head...so no idea if it is a good start or pants.

The length of the high (idle) period also needs to be measured. Your method, as it stands, could detect a change from high to low in the middle of a transmission, instead of right at the beginning.
The pin needs to have been high for at least 2300us before looking for the first data bit.

OldSteve:
The length of the high (idle) period also needs to be measured. Your method, as it stands, could detect a change from high to low in the middle of a transmission, instead of right at the beginning.
The pin needs to have been high for at least 2300us before looking for the first data bit.

Note why I said there needs to be a form of timeout function.

They can add a first delay as well...the thing seems to be a a pressure sensor...

If the device has an enable/interupt pin itself...then this is an easy thing to fix.

Johnny010:
Note why I said there needs to be a form of timeout function.

They can add a first delay as well...the thing seems to be a a pressure sensor...

If the device has an enable/interupt pin itself...then this is an easy thing to fix.

I think it's better to check for a valid start, (long enough idle period), first, then start getting data. Then there's no reason why 'pulseIn()' shouldn't work. Simplifies coding, and 'pulseIn()' has a built-in timeout.

Yeah just had a look at the PulseIn().

Seems like a nice easy implementation!

Only uses 20clock cycles (the version in 1.0) and can detect from 16 cycles after initialisation to an edge...

May have to have a play myself...never came across it.

Johnny010:
Yeah just had a look at the PulseIn().

Seems like a nice easy implementation!

Only uses 20clock cycles (the version in 1.0) and can detect from 16 cycles after initialisation to an edge...

May have to have a play myself...never came across it.

Of course, the drawback to both methods is that they're blocking code. It would be nice if it was 100% non-blocking, but if that's all that technophile wants, it's pretty straightforward.

Then, once all 16 bits have arrived, any value larger than 4095 can be discarded, since bits 12, 13 and 14 are always supposed to be 0, and bit 15 is the error bit, set on errors.
From there, the result only needs to be multiplied by 2 to get the true PSI reading, since the resolution is 2PSI, (1LSB = 2PSI, according to the chart).

Still feel a sticky on the science meaning behind the terms "Accuracy" and "Precision" should be a sticky :P.

I know colloquially they are the same thing...but I still feel it should be pushed more that in science/engineering they mean very different things.

I’ve followed up on the ideas presented so far in this thread, and here is a sketch (untested) to decode the data.

int pin = 7;
unsigned long duration;
unsigned int data;
unsigned long buf = 0;//to hold 16 bits
int output = 0;
byte bit_value;
byte i = 0;

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

void loop()
{
  duration = 0;
  duration = pulseIn(pin, HIGH);

  if (duration > 2300) //idle pulse detected
  {
    buf = 0;
    for (i = 0; i <= 15; i++) {
      data = 0;
      data = pulseIn(pin, LOW);
      if (data > 30 && data < 43)
        bit_value = 0;

      else if (data > 70 && data < 85)
        bit_value = 1;
        
      //combine bit_values into 16 bit number
      buf = buf << 1; //buf = buf*2 
      buf += bit_value;

      if (i == 11) //12 bits of pressure data received
        output = buf / 2;

      if (i == 15)//end of data stream check for error
      {
        if (bit_value == 1) //error condition
        {
          Serial.println("error");
          output = 0;
        }
        else
        {
          Serial.println(output);
        }
      }
    }
  }
}

cattledog:
I’ve followed up on the ideas presented so far in this thread, and here is a sketch (untested) to decode the data.

There’s still a problem with using ‘pulseIn()’ like that to test the length of the idle period. It means that the first bit of data will be missed.

At the end of the idle period, the pin will go low, ending the idle time ‘pulseIn()’ measurement. Then when ‘pulseIn()’ is called for the first data bit, the pin will already be low, so ‘pulseIn()’ won’t start measurement until the second bit, since ‘pulseIn()’ measurement starts on the transition from high to low.

I had a brainwave, though, and realised that if ‘pulseIn()’ is used with a timeout for a minimum valid idle period, the timeout will expire before the pin actually goes low, so then when ‘pulseIn()’ is called to measure the first bit, it will succeed.

The other thing I see is this:-output = buf / 2;to convert the reading to actual PSI. Shouldn’t it be:-output = buf * 2;The spec shows that 1LSB = 2PSI, so unless I’m misunderstanding it, that means that the reading has to be doubled for the true PSI. The way I see it, if the returned value is 1, the actual PSI is 2. Am I just seeing it wrongly?

Anyway, I wrote similar code to yours, but used a timeout for the idle period timing. (And I multiplied the result by 2 - I hope that bit’s right.)
My error-checking is a little different, too, and the code throws an error if the error bit is set or if any of the unused bits are set, (they should be zeros). Valid raw results have to be from 0 to 4095.
The code:-

// ReadPressureSensor.ino

// Pin allocations:-
const byte pressureSensorPin = 7;

// Constants:-
const int validIdlePeriod = 2000;       // 2000us minimum valid idle period.

// Global variables:-
unsigned int data;
long pulseLength;
bool currentBit;

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

void loop()
{
    pulseLength = pulseIn(pressureSensorPin, HIGH, validIdlePeriod);
    if(pulseLength == 0)                    // Valid Idle period detected, ('pulseIn()' timed out).
    {
        data = 0;
        for(int i = 0; i < 15; i++)
        {
            pulseLength = pulseIn(pressureSensorPin, LOW, 2700);    // 2700us timeout.
            if(pulseLength > 60)
                currentBit = 1;
            else
                currentBit = 0;
            data |= currentBit << i;
        }
        if(data > 4095)
            Serial.println("Error reading pressure sensor!");
        else
        {
            data *= 2;                      // 1LSB = 2PSI, so convert to PSI.
            Serial.print("Pressure = ");
            Serial.print(data);
            Serial.println(" PSI.");
        }
        delay(500);                         // Delay between readings for serial print.
    }
}

I also wrote a small program to simulate the pressure sensor, for testing the above.
This is it:-

// PressureSensorSimulator.ino

const byte dataPin = 2;

void setup()
{
    pinMode(dataPin, OUTPUT);
    delay(5);
}

void loop()
{
    uint16_t data = 100 / 2;            // 100PSI, so send 50. (1LSB = 2PSI)
    
    delayMicroseconds(3500);            // Idle period between data transmissions.
    for(int i = 0; i < 15; i++)
    {
        digitalWrite(dataPin, LOW);
        if((data >> i) & 1)
            delayMicroseconds(80);
        else
            delayMicroseconds(40);
        digitalWrite(dataPin, HIGH);
        delayMicroseconds(40);
    }
}

I got reliable results in the serial monitor for a range of pressure values using the above two programs. Mind you, if I’m wrong about the final conversion, that part will need to be changed from multiplying by two to dividing by two.

Nice work OldSteve. :slight_smile:

I'd messed up several things in my sketch beside the multiply by 2. I got the msb-lsb of the assembled 16 bit number backwards, and obviously misunderstood the behavior of pulseIn. Your use of the time out was very good.

cattledog:
Nice work OldSteve. :slight_smile:

I'd messed up several things in my sketch beside the multiply by 2. I got the msb-lsb of the assembled 16 bit number backwards, and obviously misunderstood the behavior of pulseIn. Your use of the time out was very good.

First up, before I thought of the timeout thing, I messed around and wrote a previous version using 'micros()' to measure the idle period. It worked, but was much clunkier. This version works well, (at least with my crude pressure sensor simulator program), and uses a lot less flash memory.

It's still blocking code though. :frowning: