Optimize PWM code for LEDs??

hello!

i’m working on a piece where there are two sets of leds that fade and brighten based on proximity. the code is basic and works - however it seems that i have a couple of issues:

  1. the ping readings are slightly erratic and cause the LEDs to flicker
  2. the fade in seems to be faster than the fade out (could be perception :wink:

following is my code. any suggestions on betters ways to do things to avoid either of the above issues or just improve performance would be hugely helpful!


const int led_01 = 10; // LED connected to PWM 10
const int led_02 = 9; // LED connected to PWM 9
const int led_03 = 11; // LED connected to PWN 11
const int led_04 = 5; // LED connected to PWN 5
const int led_05 = 3; // LED connected to PWN 3
const int led_06 = 6; // LED connected to PWN 6
const int pingPin = 2;
boolean state_01 = false;
boolean state_02 = false;
int i = 0;
int j = 0;

void setup() // run once, when the sketch starts
{
pinMode(led_01, OUTPUT);
pinMode(led_02, OUTPUT);
pinMode(led_03, OUTPUT);
pinMode(led_04, OUTPUT);
pinMode(led_05, OUTPUT);
pinMode(led_06, OUTPUT);
Serial.begin(9600);

}

void loop(){
//The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
// Give a short LOW pulse beforehand to ensure a clean HIGH pulse:

/Serial.print("state_01 is ");
Serial.print(state_01, DEC);
Serial.println();
/
long duration, inches, cm, avgInches;

pinMode(pingPin, OUTPUT); // sets the digital pin as output

digitalWrite(pingPin, LOW);
delayMicroseconds(2);
digitalWrite(pingPin, HIGH);
delayMicroseconds(5);
digitalWrite(pingPin, LOW);

// The same pin is used to read the signal from the PING))): a HIGH
// pulse whose duration is the time (in microseconds) from the sending
// of the ping to the reception of its echo off of an object.
pinMode(pingPin, INPUT);
duration = pulseIn(pingPin, HIGH);

// convert the time into a distance
inches = microsecondsToInches(duration);
delayMicroseconds(200);

if (inches < 15){
state_01 = true;
state_02 = false;
}
else if (inches >= 15 && inches < 48){
state_01 = false;
state_02 = true;
}
else{
state_01 = false;
state_02 = false;
}

if (state_01 == true){
if (i < 255){
i++;
analogWrite(led_01, i);
analogWrite(led_03, i);
analogWrite(led_05, i);
analogWrite(led_06, i);
}
}
else if (state_01 == false){
if (i >0){
i–;
analogWrite(led_01, i);
analogWrite(led_03, i);
analogWrite(led_05, i);
analogWrite(led_06, i);
}
}

if (state_02 == true){
if (j < 40){
j++;
analogWrite(led_02, j);
analogWrite(led_04, j);
}
}
else if (state_02 == false){
if (j >0){
j–;
analogWrite(led_02, j);
analogWrite(led_04, j);
}
}
}

long microsecondsToInches(long microseconds){
// According to Parallax’s datasheet for the PING))), there are
// 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
// second). This gives the distance travelled by the ping, outbound
// and return, so we divide by 2 to get the distance of the obstacle.
// See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
return microseconds / 74 / 2;
}

long microsecondsToCentimeters(long microseconds){
// The speed of sound is 340 m/s or 29 microseconds per centimeter.
// The ping travels out and back, so to find the distance of the
// object we take half of the distance travelled.
return microseconds / 29 / 2;
}

First thing you need to do is learn how to properly post code in the forums. When authoring your post you'll notice the array of buttons and options above the text field. To properly post code you'll want to highlight that code and then click on the button with the # symbol in it. This will add the appropriate tags to your code to display it right in the forum.

your code there...

Why did you designate the pinMode of the ping pin in the main loop and not the setup?

thanks. will do next time.

as for where the pinmode is set - i was following the Tom Igoe ping example on the arduino site:

http://arduino.cc/en/Tutorial/Ping?from=Tutorial.UltrasoundSensor

there isn't an explanation about that in particular - but it looks to me that it helps get a clean HIGH pulse, however i'm just guessing.

reposted code:

  /*
 * Interactive LED Portraits code with PING sensor
 *
 */

const int led_01 = 10;                // LED connected to PWM 10
const int led_02 = 9;                // LED connected to PWM 9
const int led_03 = 11;                // LED connected to PWN 11
const int led_04 = 5;                // LED connected to PWN 5
const int led_05 = 3;                // LED connected to PWN 3
const int led_06 = 6;                // LED connected to PWN 6
const int pingPin = 2;
boolean state_01 = false;
boolean state_02 = false;
int i = 0;
int j = 0;
   
void setup()                    // run once, when the sketch starts
{
  pinMode(led_01, OUTPUT);
  pinMode(led_02, OUTPUT);
  pinMode(led_03, OUTPUT);
  pinMode(led_04, OUTPUT);
  pinMode(led_05, OUTPUT);
  pinMode(led_06, OUTPUT);
  Serial.begin(9600);
  
}

void loop(){
  //The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
   // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:

  /*Serial.print("state_01 is ");
  Serial.print(state_01, DEC);
  Serial.println();*/
  long duration, inches, cm, avgInches;


  pinMode(pingPin, OUTPUT);      // sets the digital pin as output

  digitalWrite(pingPin, LOW);
  delayMicroseconds(2);
  digitalWrite(pingPin, HIGH);  
  delayMicroseconds(5);
  digitalWrite(pingPin, LOW);  

  // The same pin is used to read the signal from the PING))): a HIGH
  // pulse whose duration is the time (in microseconds) from the sending
  // of the ping to the reception of its echo off of an object.
   pinMode(pingPin, INPUT);
   duration = pulseIn(pingPin, HIGH);

   // convert the time into a distance
  inches = microsecondsToInches(duration);
  //cm = microsecondsToCentimeters(duration);
  delayMicroseconds(200);

  Serial.print("distance is ");
  Serial.print(inches);
  Serial.println();

  

  if (inches < 15){
    state_01 = true;
    state_02 = false;
  }
  else if (inches >= 15 && inches < 48){
    state_01 = false;
    state_02 = true;
  }
  else{
    state_01 = false;
    state_02 = false;
  }

  if (state_01 == true){
    if (i < 255){
      i++;
      analogWrite(led_01, i);
      analogWrite(led_03, i);
      analogWrite(led_05, i);
      analogWrite(led_06, i);
    }
  }
  else if (state_01 == false){
    if (i >0){
      i--;
      analogWrite(led_01, i);
      analogWrite(led_03, i);
      analogWrite(led_05, i);
      analogWrite(led_06, i);
    }
  }

  if (state_02 == true){    
    if (j < 40){
      j++;
      analogWrite(led_02, j);
      analogWrite(led_04, j);
    }
  }
  else if (state_02 == false){
    if (j >0){
      j--;
      analogWrite(led_02, j);
      analogWrite(led_04, j);
    }
  }
}

 long microsecondsToInches(long microseconds){
   // According to Parallax's datasheet for the PING))), there are
   // 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
   // second).  This gives the distance travelled by the ping, outbound
   // and return, so we divide by 2 to get the distance of the obstacle.
   // See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
   return microseconds / 74 / 2;
 }

 long microsecondsToCentimeters(long microseconds){
   // The speed of sound is 340 m/s or 29 microseconds per centimeter.
   // The ping travels out and back, so to find the distance of the
   // object we take half of the distance travelled.
   return microseconds / 29 / 2;
 }

The pinMode is set in the main loop because:

 // The same pin is used to read the signal from the PING))): a HIGH
  // pulse whose duration is the time (in microseconds) from the sending
  // of the ping to the reception of its echo off of an object.
   pinMode(pingPin, INPUT);
   duration = pulseIn(pingPin, HIGH);

So each time through the main loop the pin is set to an output to send the ping, then switched to an input to measure the response.

right makes sense - no thoughts my original post and how to optimize?

I don't have a ping but I love the idea of a function named microsecondsToInches(long microseconds).

Can you run the ping readings direct into the PC to look at them in bulk? I find it's always good to have a hard look at the data before trying to work with it. You may find the problem is solvable by averaging or dropping outliers.

Also, you can then fake up a representative sequence to drive the rest of your code and deal with the other issues.

I don’t see any reason that fade in vs fade out speed would be different, so that may be perception.

Regarding the flicker from erratic readings, there’s a couple of possibilities:

  1. You could filter (average) a few readings to help reduce brief glitches. A simple formula for that is:
// somewhere in your global variables
float ave_inches;
float alpha = 0.70;  // adjust to meet your needs

// in your main loop
ave_inches = alpha * ave_inches + (1-alpha) * inches;

// then use the ave_inches in your if( ) statements

In this formula, alpha can be anything from 0.0 to <1.0; Bigger alpha means slower response (more filtering) because it gives more weight to the previous readings. Alpha =1.0 would completely ignore new readings, so it needs to be less than 1.0. Alpha = 0, would be no filtering at all.

  1. Another technique is to put a bit of “deadband” between your state 1 and state 2. That is, instead of state_1 being <15, and state_2 being >15, you might have state_1 <15, and state_2 >16. That gives you a an area (between 15 and 16 inches) where the state does not get changed.

  2. you can combine 1) and 2).

Oh, something else:

Instead of having microsecondsToInches return a type of "long", you'd be better off with "float".

With a "long" you're dealing with integer math. There is no 15.5 inches; you get only 14, 15, 16, etc. So if the sensor is reading close to 15" plus or minus a little jitter, the math will bounce from 15 to 16, or 14 to 15, with nothing in between.

To fix: 1) declare "float inches;" in your loop() function 2) declare the microsecondsToInches as:

[glow]float [/glow]microsecondsToInches(long microseconds)

3) and change the return statement to force the compiler to using floating point math instead of integer math:

return microseconds / 74[glow].0[/glow] / 2[glow].0[/glow];

thanks SO much for all those suggestions - i tried them all but they don't seem to be making a noticeable difference sadly. i think my biggest issue is that once i get more than a couple feet from the sensor, the values get really erratic (they jump from like 24" to 120) - which causes the really bad flickering. i tried averaging the values as you suggested but it slowed the loop down too visibly (in the fade of the lights).

i think it might just be that the ping doesn't detect people well at a certain distance - but if you have any other thoughts, they would be very much appreciated.

thanks so much!

Ah, ok… I was thinking you were having trouble with the transition at 15". At further distances it’s probably more of a physical issue than a software one. The ultrasonic beam could be missing the target, or the target is not providing sufficient echo strength.

Solid, flat, larger objects should provide a stronger echo. Soft objects would tend to dampen the echo strength.

yeahh - that's what i figured... i was hoping there was a way i could smooth the interaction anyway. it seems that this is just the shortcoming of the ultrasonic sensor.

thanks for all the help.