Sensor that gives PWM reading in pulse train

Hi every1!

I'm having a problem, already posted in Italian section, but I'm posting here so maybe the solution can be helpful to people around there.
I have a sensor which makes 3 readings in a pulse train (inverted PWM signal). Here's an image that maybe explains better:

As you can see, it is inverted PWM output. That's why I need to take the time of the LOW signal states.
Then, I need to calculate the three different values with this simple formulas:

PWM1 = T1/T
PWM2 = T2/T
PWM3 = T3/T

the period of T should be 1.2 sec = 1200 milliseconds.

I would like to do this with pulseIn() or interrupt, but maybe pulseIn() is much more easier. I don't have any idea on how to do this. Any ideas? Thanks!

I would use an interrupt, with mode CHANGE so that you get an interrupt on both edges. In the interrupt service routine, get the current time by calling micros(). If the pin is low, then you are at the start of T1, T2 or T3, so just store the value you read (in an 'unsigned long' variable). If the pin is high, subtract the value you stored earlier, that gives you the T1, t2 or T3 value in microseconds. Use micros() rather than millis(), because millies() is less accurate and is subject to jitter - it sometimes advances by 2 instead of 1.

Something like this (warning: untested code!):

unsigned long lastFallingTime;
volatile unsigned long lettura[3];
byte index = 0;

void setup()
{
  attachInterrupt(2, isr, CHANGE);
  Serial.begin(19200);
}

void isr()
{
  unsigned long now = micros();
  if (digitalRead(2) == LOW)
  {
    // falling edge seen
    if (now - lastFallingTime > 2000000UL)
    {
       index = 0;   // we've seen the pause, so reset the counter
    }
    lastFallingTime = now;
  }
  else
  {
    // rising edge seen
    if (index < 3)
    {
      lettura[index] = now - lastFallingTime;
      ++index;
    }
  }
}

void loop()
{
  delay(5000);
  for (int i = 0; i < 3; ++i)
  {
    noInterrupts();
    unsigned long t = lettura[i];
    interrupts();
    Serial.print("Lettura ");
    Serial.print(i);
    Serial.print('=');
    Serial.println(t);
  }
}

[EDIT: corrected code so that it at least compiles without warnings]

Thanx dc42, that's a nice idea.
If I'm not doing something wrong... and if I understood correctly, 100% duty cycle (in this case LOW signal) should be 1.200 milliseconds.
If I have to do the following calculation I need:

PWM1 = T1/T
PWM2 = T2/T
PWM3 = T3/T

should be

PWM1 = lettura[0] / T (T, taking a look at the wave graph above, should be 1.200 milliseconds, is that correct?)
PWM2 = lettura[1]
PWM3 = lettura[2]

What do you think?

From the diagram in your original post, it looks like 20% of T is the minimum, and 80% is the maximum. It's been done this way so that you always see a pulse, otherwise it would be difficult to work out what is going on when some pulses were at 0% or 100%. So your calculation will give values in the range 20% to 80%.

Do you know how accurate the 1.2 seconds is supposed to be? If it is not very accurate, or if you are looking for accuracy better than 0.5% (the accuracy of the Arduino's ceramic resonator), then it would be best to measure the time between falling edges as well, and use that as the reference. Like this:

unsigned long lastFallingTime, lastRisingTime;
volatile unsigned int pwm[3];
byte index = 3;    // to avoid storing readings until we are synchronized

void setup()
{
  attachInterrupt(2, isr, CHANGE);
  Serial.begin(19200);
}

void isr()
{
  unsigned long now = micros();
  if (digitalRead(2) == LOW)
  {
    // falling edge seen
    unsigned long interval = now - lastFallingTime;
    if (interval > 2000000UL)
    {
       index = 0;   // we've seen the pause, so reset the counter
    }
    else if (index < 3 && interval >= 0x1000000UL && interval <= 0x1400000UL)   // if between 1000 and 1400ms
    {
      pwm[index] = (unsigned int)(((lastRisingTime - lastFallingTime) * 100)/interval);   // store the PWM value as a percentage
      ++index;
    }
    lastFallingTime = now;
  }
  else
  {
    // rising edge seen
    lastRisingTime = now;
  }
}

void loop()
{
  delay(5000);
  for (int i = 0; i < 3; ++i)
  {
    noInterrupts();
    unsigned int t = pwm[i];
    interrupts();
    Serial.print("PWM ");
    Serial.print(i);
    Serial.print('=');
    Serial.println(t);
  }
}

dc42, thanks for the reply.

Using that code i get these results from the serial monitor:

PWM 0=0
PWM 1=0
PWM 2=0

what's wrong?

Looks like no input is being received. The code I suggested assumes that you are running on an Arduino Uno and you feed the input to digital pin 2 (because that is the pin that triggers interrupt 0). Is that what you have done? Do you have a common ground between the sensor and the Arduino?

dc42:
Looks like no input is being received. The code I suggested assumes that you are running on an Arduino Uno and you feed the input to digital pin 2 (because that is the pin that triggers interrupt 0). Is that what you have done? Do you have a common ground between the sensor and the Arduino?

Sure yes! Output from the sensor goes to Arduino digital pin 2 and there is ground in common. Maybe there's something with timing in the code? I don't think...

Are you sure the sensor is generating the pulses? If you do a digitalRead of pin 2 in a loop, do you read both highs and lows?

Also, are you certain that T is 1200ms? It doesn't say that in the diagram that you posted.

If you take a look on the first line just below "PAUSA" in the diagram, you will see that it says

1 sec + 200 msec

oh wait - 1 sec + 200 msec maybe it's not 1.2 secs?

It's not clear what the 1 sec + 200 msec is, because it doesn't indicate where that time starts from. I think it is the length of the pause. This might not be the same as the time T.

Try this code:

void loop()
{
  while (digitalRead(2) == HIGH) {}
  unsigned long fall =micros();
  while (digitalRead(2) == LOW) {}
  unsigned long rise = micros();
  Serial.print("Low="); Serial.print(rise - fall);
  while (digitalRead(2) == HIGH) {}
  fall =micros();
  Serial.print(" High="); Serial.println(fall - rise);
}

That's what I get:

Low=44724 High=71020
Low=43992 High=71156
Low=22616 High=1249748
Low=44004 High=71020
Low=43992 High=71156
Low=22628 High=1249764
Low=44000 High=71020
Low=44000 High=71164
Low=22624 High=1249824
Low=44004 High=71012
Low=43992 High=71156
Low=22620 High=1249804
Low=44004 High=71024
Low=43992 High=71156
Low=22776 High=1249636
Low=44004 High=71020
Low=44136 High=71016
Low=22624 High=1249764
Low=44004 High=71024
Low=43996 High=71156
Low=22624 High=1249780
Low=43996 High=71016
Low=43992 High=71156
Low=22640 High=1249764
Low=44000 High=71020
Low=44124 High=71020
Low=22636 High=1249740
Low=43996 High=71016
Low=43984 High=71160
Low=22640 High=1249744
Low=44004 High=71012
Low=43992 High=71156
Low=22624 High=1249784
Low=44004 High=71016
Low=43992 High=71160
Low=22784 High=1249652
Low=43996 High=71020
Low=43992 High=71156
Low=22796

OK, so T looks like 1.15sec for the first 2 pulses, and 1.27sec for the one that includes the pause. Try the code I suggested in my first reply, but change the constant 2000000UL to 1200000UL.

Done, but i get this:

Lettura 0=0
Lettura 1=0
Lettura 2=0
Lettura 0=0
Lettura 1=0
Lettura 2=0
Lettura 0=0
Lettura 1=0
Lettura 2=0
Lettura 0=0
Lettura 1=0
Lettura 2=0
Lettura 0=0
Lettura 1=0
Lettura 2=0
Lettura 0=0
Lettura 1=0
Lettura 2=0
Lettura 0=0
Lettura 1=0
Lettura 2=0
Lettura 0=0
Lettura 1=0
Lettura 2=0
Lettura 0=0
Lettura 1=0
Lettura 2=0
Lettura 0=0
Lettura 1=0
Lettura 2=0
Lettura 0=0
Lettura 1=0
Lettura 2=0

Sorry, I got the attachInterrupt call wrong. It should be attachInterrupt(0, isr, CHANGE).

Nice, this is what I get:

Lettura 0=45196
Lettura 1=44724
Lettura 2=23212
Lettura 0=45196
Lettura 1=44584
Lettura 2=23232
Lettura 0=45196
Lettura 1=44748
Lettura 2=23208
Lettura 0=45200
Lettura 1=44584
Lettura 2=23212
Lettura 0=45200
Lettura 1=44724
Lettura 2=23232

Good! We get results. Now I need to calculate PWM1, PWM2 and PWM3. That is simply done by this:

PWM1 = T1 / T
PWM2 = T2 /T
PWM3 = T3/T

That way we designed PWM signal definition. Then, T1 is the total time subtracted the time that signal stays LOW. Am I correct? I think this is already done in the code. Right?

Try this, which is based on the second code suggestion I made. I had to modify it to allow for the fact that we don't know directly the value of T on the third pulse.

unsigned long lastFallingTime, lastRisingTime;
unsigned long intervalT[2];
byte index = 3;    // to avoid storing readings until we are synchronized
volatile unsigned int pwm[3];

void setup()
{
  attachInterrupt(0, isr, CHANGE);
  Serial.begin(19200);
}

void isr()
{
  unsigned long now = micros();
  if (digitalRead(2) == LOW)  // if we've just had a falling edge
  {
    unsigned long interval = now - lastFallingTime;
    if (interval > 1200000UL)   // if we've just had the pause
    {
      if (index == 2 && intervalT[0] != 0)
      {
        pwm[2] = (unsigned int)(((lastRisingTime - lastFallingTime) * 200)/(intervalT[0] + intervalT[1]));
      }
      index = 0;   // we've seen the pause, so reset the counter
    }
    else if (index < 2 && interval >= 0x1100000UL)
    {
      intervalT[index] = interval;  // save this for later
      pwm[index] = (unsigned int)(((lastRisingTime - lastFallingTime) * 100)/interval);   // store the PWM value as a percentage
      ++index;
    }
    lastFallingTime = now;
  }
  else
  {
    // rising edge seen
    lastRisingTime = now;
  }
}

void loop()
{
  delay(5000);
  for (int i = 0; i < 3; ++i)
  {
    noInterrupts();
    unsigned int t = pwm[i];
    interrupts();
    Serial.print("PWM ");
    Serial.print(i);
    Serial.print('=');
    Serial.println(t);
  }
}

...and I get 0 again:

PWM 0=0
PWM 1=0
PWM 2=0
PWM 0=0
PWM 1=0
PWM 2=0
PWM 0=0
PWM 1=0
PWM 2=0
PWM 0=0
PWM 1=0
PWM 2=0
PWM 0=0
PWM 1=0
PWM 2=0
PWM 0=0
PWM 1=0
PWM 2=0
PWM 0=0
PWM 1=0
PWM 2=0
PWM 0=0
PWM 1=0
PWM 2=0
PWM 0=0
PWM 1=0
PWM 2=0
PWM 0=0
PWM 1=0
PWM 2=0

0x1100000UL should have been just 1100000UL.

Ah, it went away to me too!
Corrected, but I still get the same:

PWM 0=0
PWM 1=0
PWM 2=0

Looks like I can't add up. The value that was 0x1100000UL needs to be much lower, because T is only about 115ms. Try 100000UL. Also change the 1200000UL to 1000000UL.