3

I am trying to make a simple breathing led circuit in which the intensity of the led varies between 0 and 255. I followed the Fade tutorial on the Arduino site, which gives me what I want, but I notice that the change in brightness is much more noticeable when the intensity is at 0.

In other words, the brightness seems to be bouncing back from 0, rather than slowly gliding back up.

I tried mapping the value of the delay to brightness: del = map(brightness, 0, 255, 10, 1); but that did not do the job.

The mapping seems to work better when I use 30 instead of 10 as longest delay time: del = map(brightness, 0, 255, 30, 1); but I think what I really need is to implement some sort of sinusoidal function to make transitions smoother, but I am really struggling with the math... Any suggestions?

Here is the code:

int led = 9;           // the PWM pin the LED is attached to
int brightness = 0;    // how bright the LED is
int fadeAmount = 1;    // how many points to fade the LED by
int del;

// the setup routine runs once when you press reset:
void setup() {
  // declare pin 9 to be an output:
  pinMode(led, OUTPUT);
}

// the loop routine runs over and over again forever:
void loop() {
  // set the brightness of pin 9:
  analogWrite(led, brightness);

  // change the brightness for next time through the loop:
  brightness = brightness + fadeAmount;

  // reverse the direction of the fading at the ends of the fade:
  if (brightness <= 0 || brightness >= 255) {
    fadeAmount = -fadeAmount;
  }

  // here I map the delay to brightness
  del = map(brightness, 0, 255, 10, 1);


  delay(del);
}
4
  • Maybe use a sinewave to set the brightness. Or possibly you want a logarithmic scale?
    – Majenko
    Commented Mar 9, 2017 at 16:30
  • Sinewave would be good, and a logarithmic scale too.. just really not sure about how to implement those...
    – samzmann
    Commented Mar 9, 2017 at 16:32
  • I think you should look into gamma correction. Our eyesight is logarithmic, so for our eye to see something as twice as bright, it has to be around 4 times as bright. For example, using the standard fade sketch, the leds starts to increase in brightness fairly rapidly, but then seems to slow down.
    – Gerben
    Commented Mar 9, 2017 at 19:36
  • These days there are so called "Digital Potentiometers", these can work with a board, to actually change the voltage into the led, instead of simulating it with a pwm
    – Coder9390
    Commented Jan 8, 2022 at 10:48

6 Answers 6

4

There's two ways you could algorithmically do what you want, both with different results.

The first is with a sinewave:

for (int i = 0; i < 360; i++) {
    analogWrite(5, (sin(i * 0.0174533) + 1) * 127));
    delay(3);
}

Basically you work your way 360 degrees around a circle and take one axis (e.g., the vertical one), offset it so that the values are all positive (sin gives a value between -1 and +1), then multiply it to fit the full brightness.

This is the smoothest way of fading an LED, but it doesn't respect the different sensitivities your eye has to the different brightnesses (you see more brightness variation when it's dim compared to when it's bright).

The other method is much cruder but does respect those differences in sensitivities. This specific method only gives you 8 brightnesses as opposed to 256, but they are on a log2 curve:

for (int i = 0; i < 9; i++) {
    int v = (1 << i) - 1;
    analogWrite(5, v);
    delay(100);
}
for (int i = 7; i > 0; i--) {
    int v = (1 << i) - 1;
    analogWrite(5, v);
    delay(100);
}

The theory of this one is it uses bit-shifting to gradually fill a byte with 1 from the least significant bit upwards.

When i is 0, this formula:

int v = (i << i) - 1;

gives:

1 << 0 == 0b00000001
0b00000001 - 1 = 0b00000000

If i is 4, you get:

1 << 4 == 0b00010000
0b00010000 - 1 = 0b00001111

So you effectively end up with a set of values:

0: 0
1: 1
2: 3
3: 7
4: 15
5: 31
6: 63
7: 127
8: 255
2
  • That code gives -1 3 15 47 127 319 767 1791 4095 1791 767 319 127 47 15 3.
    – Mark Smith
    Commented Mar 9, 2017 at 17:38
  • Seems there's a typo there - i instead of 1 - easily done. I've also fixed another bug.
    – Majenko
    Commented Mar 9, 2017 at 17:54
2

The three 255 values for RGB colour space (255^3) is 16,581,375 colours. These include for brightness level. The reason you're experiencing brightness issues is not to due with your arduino board but, with the RGB color space. And by breathing, and i'm guessing here, that you are trying to transition thru RGB:

void colorLoop() {  

    static int Col1;
    static int Col2;
    static int Col3;

    static int C1;
    static int C2;
    static int C3;

  if ((((Col1 == 0) || (Col1 == 250)) && (((Col2 == 0) || (Col2 == 250)) && ((Col3 == 0) || (Col3 == 250))))) {

  C1 = random(1, 3);
  C2 = random(1, 3);
  C3 = random(1, 3);
  }

  if (((C1 == 1) && (Col1 != 0))) {
      Col1 = (Col1 - 10);
  }
  if (((C2 == 1) && (Col2 != 0))) {
      Col2 = (Col2 - 10);
  }
  if (((C3 == 1) && (Col3 != 0))) {
      Col3 = (Col3 - 10);
  }

  if (((C1 == 2) && (Col1 != 250))) {
      Col1 = (Col1 + 10);
  }
  if (((C2 == 2) && (Col2 != 250))) {
      Col2 = (Col2 + 10);
  }
  if (((C3 == 2) && (Col3 != 250))) {
      Col3 = (Col3 + 10);
  }

if (((Col1 <= 90) && ((Col2 <= 90) && (Col3 <= 90)))) {  //avoid black
   C1 = 2;
   C2 = 2;
   C3 = 2;
 }

    for (uint8_t i = 0; i < LED_COUNT; i++) {
     leds[i] = CRGB(Col1, Col2, Col3);
    }
    delay(100);
    LEDS.show();
  }

You may have more success or find it easier to use a HSL or HSV colour space. HSV uses Hue, Saturation and Value instead of RGB. FastLED has optimized it's implementation of HSV to use a 0-255 addressing for Hue (instead of 360), keeping it fast for animations run on microcontrollers:

https://github.com/FastLED/FastLED/wiki/FastLED-HSV-Colors

here is a similar effect using the HSV color spectrum:

void rainbow_fade() {                        

 int ihue = 0; //some microcontrollers use HSV from 0-255 vs the normal 0-360               
    ihue++;
    if (ihue > 255) {ihue = 0;}
    for(int i = 0 ; i < LED_COUNT; i++ ) {
     leds[i] = CHSV(ihue, 255, 255);
    }
      LEDS.show();    
      delay(100);
  }
2

I thought about making a regular fade, with a logarithmic delay: for an example:

val=30*(1-((sqrt(brightness))/(sqrt(255))));
delay(val);
1

I'm not sure that it will work with a normal led. Some 20mA leds are still bright with only 1mA. The programmable RGB 5050 leds (for example the NeoPixels) are easier for a smooth visual brightness change.

The human eye is logarithmic for light brightness. Therefor a 10-based exponent or a less steep natural-based exponent can be used.

This will give a smooth brightness change:

y = a * exp (-b * x * x);
// x is incrementing in time.

If you really need a very smooth brightness change for a normal led, you might need an extra output with a higher resistor value to the same led, for a higher resolution at low brightness.

1
  1. Use a timer interrupt and in thee Isr change a pwm generator duty cycle. Or

  2. Use a timer and set up two compare channels with a fixed offset. In the compare Isr, advance the math points forward and then flip an output pin. You will see the brightness goes up and down, whose speed depends on the offset.

No need for gamma correction.

edit: to demonstrate how the 2nd approach might work, here is a quick example.

It runs off of TIMER0, Output Compare Ch A and Ch B.

TIMER0 is set with a 256x prescaler, free running. Ch A is set to interrupt every LED_PR ticks, and Ch B is set to interrupt every LED_PR + LED_OFFSET ticks. they run the same user interrupt handler -> to flip a pin attached on LED_PORT / LED pin.

tmr0_init(TMR0_PS256x);                 //initialize tmr0 with a prescaler
tmr0a_setpr(LED_PR);                    //set up output compare ch a period
tmr0a_act(led_flp);                     //install user handler for ch a
tmr0b_setpr(LED_PR + LED_OFFSET);       //set up output compare ch b period, with offset
tmr0b_act(led_flp);                     //install user handler for ch b
ei();

and here is how it works in simulation:

enter image description here

the DC will go up then down, then up and then down, ....

0

Just recently I published a blog post about this very topic at https://thingpulse.com/breathing-leds-cracking-the-algorithm-behind-our-breathing-pattern/

Turns out you get very pleasing results if you use f(x) = e^sin(x). If you bring this down to 0 and have an amplitude of 255 the formula becomes f(x) = (e^sin(x) - 1/e) * (255/(e - 1/e)). Finally, you stretch x so you get a frequency of 12-20 breaths per minute: x * PI/2.

#include <math.h>

void setup() {
  pinMode(9, OUTPUT);
}

void loop() {
  float val = (exp(sin(millis()/2000.0 * PI)) - 0.368) * 108.0;
  analogWrite(9, val);
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.