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