Go Down

### Topic: Smooth low level fading of LED strip using PWM with 8-bit ATtiny85 (Read 3069 times)previous topic - next topic

#### tdeanos ##### Feb 28, 2016, 07:53 am
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:

Code: [Select]
`const int transistorPin = 0;    // connected to the base of the transistorint potPin = 1;                 // designate pin 1 as potentiometerint val;// The number of Steps between the output being on and offconst int pwmIntervals = 400;// The R value in the graph equationfloat 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 }`

#### Wawa #1
##### Feb 28, 2016, 08:18 am
Best to just ignore the lowest ~16 PWM values with 8-bit dimming.
Leo..

#### Paul__B #2
##### Feb 28, 2016, 08:40 am
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?

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.

#### tdeanos #3
##### Feb 28, 2016, 09:28 am
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

#### tdeanos #4
##### Feb 28, 2016, 09:35 am
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

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.

#### Paul__B #5
##### Feb 28, 2016, 10:14 amLast Edit: Feb 28, 2016, 10:19 am by Paul__B Reason: Sigh! Always more.
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.

#### runaway_pancake #6
##### Feb 28, 2016, 03:10 pm
Code: [Select]
`const int transistorPin = 9;  // change to Your pwm pin of choiceint 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);}`

"Who is like unto the beast? who is able to make war with him?"
When all else fails, check your wiring!

#### tdeanos #7
##### Feb 29, 2016, 09:49 am
Thanks Paul, I'm starting to build a greater understanding of the theories involved.

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?

#### PaulRB #8
##### Feb 29, 2016, 11:43 amLast Edit: Feb 29, 2016, 11:44 am by PaulRB
Try this:
Code: [Select]
`const int transistorPin = 0;    // connected to the base of the transistorint potPin = 1;                 // designate pin 1 as potentiometerint val;// The number of Steps between the output being on and offconst int pwmIntervals = 400;// The R value in the graph equationfloat 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)

#### Paul__B #9
##### Feb 29, 2016, 12:20 pm
Is this what you had in mind, Paul__B ? (excepting use of delay()  and inefficient float calculations)
Seems to resemble it.  Given those caveats.

#### PaulRB #10
##### Feb 29, 2016, 01:08 pmLast Edit: Feb 29, 2016, 01:38 pm by 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?

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

https://youtu.be/OKPN7TuqD2Y

Code: [Select]
`const int transistorPin = 5;    // connected to the base of the transistorint potPin = 1;                 // designate pin 1 as potentiometerint val;// The number of Steps between the output being on and offconst int pwmIntervals = 4000;// The R value in the graph equationfloat 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}`

#### Paul__B #11
##### Feb 29, 2016, 10:40 pm
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 ...

Go Up