Smooth low level fading of LED strip using PWM with 8-bit ATtiny85

Hi all,
I'm making a project to fade an LED strip up to maximum brightness then back down again over about 10 seconds using PWM on an ATtiny85.

I have written code to get the LED's to fade non linearly and it looks pretty good except that at low levels the steps in brightness are very visible. From my research it seems that this is due to there only being 255 levels on an 8-bit processor.

Is there a way to increase the resolution of the fading at low levels using an 8 bit controller or do I need to get a microcontroller with 16bit PWM?

My code follows:

const int transistorPin = 0;    // connected to the base of the transistor
int potPin = 1;                 // designate pin 1 as potentiometer
int val;
// The number of Steps between the output being on and off
const int pwmIntervals = 400;

// The R value in the graph equation
float R;

 void setup() {
   pinMode(transistorPin, OUTPUT);

    // Calculate the R variable (only needs to be done once at setup)
  R = (pwmIntervals * log10(2))/(log10(255));
 }
 


 void loop() {
    int brightness = 0;

{
val = analogRead(potPin);
val = map(val, 0, 1023, 1, 5); 
}
   for (int interval = 0; interval <= pwmIntervals; interval++) {
      // Calculate the required PWM value for this interval step
      brightness = pow (2, (interval / R)) - 1;
      // Set the LED output to the calculated brightness
      analogWrite(transistorPin, brightness);
      delay(7);                                   // delay (n) where n is time to full brightness in milliseconds
    }

    delay(1000);                                   //n is time at full brightness  

    for (int interval = 0; interval <= pwmIntervals; interval++ ) {
      // Calculate the required PWM value for this interval step
      brightness = pow (2, (-(interval-pwmIntervals)/ R)) - 1;
      // Set the LED output to the calculated brightness
      analogWrite(transistorPin, brightness);
     
     delay(7);                      //n is time to zero brightness
    }

     delay(80);             //n is time at zero brighness
 }

Best to just ignore the lowest ~16 PWM values with 8-bit dimming.
Leo..

tdeanos:
Is there a way to increase the resolution of the fading at low levels using an 8 bit controller or do I need to get a microcontroller with 16bit PWM?

What, fading only one channel?

Do it in software - what else are you going to use the Tiny85 for?

Your code looks convoluted to me. To implement an exponential fade, just multiply by a factor at each step using a shift-and-add (or shift-and subtract) routine which is going to be faster than a formal multiply. You could use a fixed step of 1.125 or 0.875. You can detect when the result is no longer changing.

Wawa:
Best to just ignore the lowest ~16 PWM values with 8-bit dimming.
Leo..

I really need it to fade down to black if possible

Paul__B:
Your code looks convoluted to me. To implement an exponential fade, just multiply by a factor at each step using a shift-and-add (or shift-and subtract) routine which is going to be faster than a formal multiply. You could use a fixed step of 1.125 or 0.875. You can detect when the result is no longer changing.

Thanks Paul B, that's a really good idea to fade it exponentially. This is the first time I've written code so this is cobbled together from a bunch of other example codes and I had a feeling it had become convoluted due to this. That sounds like a much better solution.

As for

Paul__B:
What, fading only one channel?

Do it in software - what else are you going to use the Tiny85 for?

I've spent quite a bit of time looking for a way to do this and haven't been able to figure out how I could do it for this application. How could I go about acheiving this?
Thank you very much for your help.

The fundamental principle of microcontroller programming is that the loop() continuously cycles without ever waiting for some event to complete - it only ever tests for events.

So in that loop, you make a number of decisions. You can check the time using millis() to see whether you need to do something such as increasing or decreasing the PWM value. You can check to see if buttons have been pressed. And then you increment your 16-bit PWM counter and see whether it has either zeroed - in which case you turn your LEDs off, or equalled the value at which the LEDs should be turned on.

You can try this out to see whether it is fast enough to dim without flicker. If it is not, you can knock off a bit at a time to re-test it and reduce the resolution down to perhaps 12 bits.

Another interpreting approach is to hybridise the 8-bit PWM with a similar sequence of 16 or so steps in which the LEDs are kept on for part of the sequence, PWMed for one step to give the finer resolution, and held off for the remainder.


In fact, you could use the basic 8-bit resolution until you get down to a value of about 32, then swap to this alternate code in which you only enable the PWM for one eighth of the time.

const int transistorPin = 9;  // change to Your pwm pin of choice
int x;   
void setup()
{
  pinMode(transistorPin, OUTPUT); // not strictly nec. w/analogWrite
}

void loop()
{
  x = 1;     // x = { 1,2,...15}
  float val = (exp(sin(millis()/1500.0*x*PI)) - 0.36787944)*108.0;  //
  val = 255 - val;      //  val 'inverted'
  analogWrite(transistorPin, val);
}
1 Like

Thanks Paul, I'm starting to build a greater understanding of the theories involved.

Paul__B:

In fact, you could use the basic 8-bit resolution until you get down to a value of about 32, then swap to this alternate code in which you only enable the PWM for one eighth of the time.

This sounds like it could be an option. Would you be able to give me a tip of how I might be able to write this code?

Try this:

const int transistorPin = 0;    // connected to the base of the transistor
int potPin = 1;                 // designate pin 1 as potentiometer
int val;
// The number of Steps between the output being on and off
const int pwmIntervals = 400;

// The R value in the graph equation
float R;

void setup() {
  pinMode(transistorPin, OUTPUT);

  // Calculate the R variable (only needs to be done once at setup)
  R = (pwmIntervals * log10(2)) / (log10(2047));
}



void loop() {
  int brightness = 0;

  val = analogRead(potPin);
  val = map(val, 0, 1023, 1, 5);

  for (int interval = 0; interval <= pwmIntervals; interval++) {
    // Calculate the required PWM value for this interval step
    brightness = pow (2, (interval / R)) - 1;
    // Set the LED output to the calculated brightness
    if (brightness > 255) {
      analogWrite(transistorPin, brightness >> 3);
      delay(8);                                   // delay (n) where n is time to full brightness in milliseconds
    }
    else {
      analogWrite(transistorPin, brightness);
      delay(1);                                   // delay (n) where n is time to full brightness in milliseconds
      analogWrite(transistorPin, 0);
      delay(7);                                   // delay (n) where n is time to full brightness in milliseconds
    }
  }

  delay(1000);                                   //n is time at full brightness

  for (int interval = 0; interval <= pwmIntervals; interval++ ) {
    // Calculate the required PWM value for this interval step
    brightness = pow (2, (-(interval - pwmIntervals) / R)) - 1;
    // Set the LED output to the calculated brightness
    if (brightness > 255) {
      analogWrite(transistorPin, brightness >> 3);
      delay(8);                                   // delay (n) where n is time to full brightness in milliseconds
    }
    else {
      analogWrite(transistorPin, brightness);
      delay(1);                                   // delay (n) where n is time to full brightness in milliseconds
      analogWrite(transistorPin, 0);
      delay(7);                                   // delay (n) where n is time to full brightness in milliseconds
    }
  }

  delay(80);             //n is time at zero brighness
}

Not tested but compiled.

Is this what you had in mind, Paul__B ? (excepting use of delay() and inefficient float calculations)

PaulRB:
Is this what you had in mind, Paul__B ? (excepting use of delay() and inefficient float calculations)

Seems to resemble it. Given those caveats.

Well, it sort of works, but it flickers very badly indeed at those low levels. Something to do with the way analogWrite() works, perhaps? Or maybe interaction between the PWM frequency and the ~8ms switching?

I set up a second led that runs on the 8-bit pwm for comparison. Its on the right in this vid:

const int transistorPin = 5;    // connected to the base of the transistor
int potPin = 1;                 // designate pin 1 as potentiometer
int val;
// The number of Steps between the output being on and off
const int pwmIntervals = 4000;

// The R value in the graph equation
float R;

void setup() {
  pinMode(transistorPin, OUTPUT);

  // Calculate the R variable (only needs to be done once at setup)
  R = (pwmIntervals * log10(2)) / (log10(2047));
}



void loop() {
  int brightness = 0;

  val = analogRead(potPin);
  val = map(val, 0, 1023, 1, 5);

  for (int interval = 0; interval <= pwmIntervals; interval++) {
    // Calculate the required PWM value for this interval step
    brightness = pow (2, (interval / R)) - 1;
    // Set the LED output to the calculated brightness
    analogWrite(3, brightness >> 3);
    if (brightness > 255) {
      analogWrite(transistorPin, brightness >> 3);
      delay(8);                                   // delay (n) where n is time to full brightness in milliseconds
    }
    else {
      analogWrite(transistorPin, brightness);
      delay(1);                                   // delay (n) where n is time to full brightness in milliseconds
      analogWrite(transistorPin, 0);
      delay(7);                                   // delay (n) where n is time to full brightness in milliseconds
    }
  }

  delay(1000);                                   //n is time at full brightness

  for (int interval = 0; interval <= pwmIntervals; interval++ ) {
    // Calculate the required PWM value for this interval step
    brightness = pow (2, (-(interval - pwmIntervals) / R)) - 1;
    // Set the LED output to the calculated brightness
    analogWrite(3, brightness >> 3);
    if (brightness > 255) {
      analogWrite(transistorPin, brightness >> 3);
      delay(8);                                   // delay (n) where n is time to full brightness in milliseconds
    }
    else {
      analogWrite(transistorPin, brightness);
      delay(1);                                   // delay (n) where n is time to full brightness in milliseconds
      analogWrite(transistorPin, 0);
      delay(7);                                   // delay (n) where n is time to full brightness in milliseconds
    }
  }

  delay(80);             //n is time at zero brighness
}

PaulRB:
Well, it sort of works, but it flickers very badly indeed at those low levels. Something to do with the way analogWrite() works, perhaps? Or maybe interaction between the PWM frequency and the ~8ms switching?

Predictable. One or both. Well, it was a thought anyway ...