How to fade a LED logarithmically

I noticed when I tried to use the fade sketch on a large number of LED’s that the fade was far from “even” to the human eye and wanted a better way to make the fade. My brother helped me with a table of levels to better imitate a smooth fade. I made a simple code for uploading the table to the EEPROM and then just read it. The fade table is used for the UNO and other similar Arduinos with only 8 PWM bits. A larger table can be made for PWM outputs with a higher resolution.

#include <EEPROM.h>

int addr = 0;
int LED = 13;

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

}

void loop()
{
  digitalWrite(LED, HIGH);
  delay(1000);
  digitalWrite(LED, LOW);
  delay(1000);
  digitalWrite(LED, HIGH);
  delay(1000);
  digitalWrite(LED, LOW);
  delay(1000);
  
  EEPROM.write(0,	1);
  EEPROM.write(1,	1);
  EEPROM.write(2,	1);
  EEPROM.write(3,	1);
  EEPROM.write(4,	2);
  EEPROM.write(5,	2);
  EEPROM.write(6,	2);
  EEPROM.write(7,	2);
  EEPROM.write(8,	2);
  EEPROM.write(9,	3);
  EEPROM.write(10,	3);
  EEPROM.write(11,	3);
  EEPROM.write(12,	3);
  EEPROM.write(13,	4);
  EEPROM.write(14,	4);
  EEPROM.write(15,	5);
  EEPROM.write(16,	5);
  EEPROM.write(17,	6);
  EEPROM.write(18,	6);
  EEPROM.write(19,	7);
  EEPROM.write(20,	7);
  EEPROM.write(21,	8);
  EEPROM.write(22,	9);
  EEPROM.write(23,	10);
  EEPROM.write(24,	11);
  EEPROM.write(25,	12);
  EEPROM.write(26,	13);
  EEPROM.write(27,	14);
  EEPROM.write(28,	16);
  EEPROM.write(29,	17);
  EEPROM.write(30,	19);
  EEPROM.write(31,	21);
  EEPROM.write(32,	23);
  EEPROM.write(33,	26);
  EEPROM.write(34,	28);
  EEPROM.write(35,	31);
  EEPROM.write(36,	34);
  EEPROM.write(37,	37);
  EEPROM.write(38,	41);
  EEPROM.write(39,	45);
  EEPROM.write(40,	50);
  EEPROM.write(41,	55);
  EEPROM.write(42,	60);
  EEPROM.write(43,	66);
  EEPROM.write(44,	73);
  EEPROM.write(45,	80);
  EEPROM.write(46,	88);
  EEPROM.write(47,	97);
  EEPROM.write(48,	107);
  EEPROM.write(49,	117);
  EEPROM.write(50,	129);
  EEPROM.write(51,	142);
  EEPROM.write(52,	156);
  EEPROM.write(53,	172);
  EEPROM.write(54,	189);
  EEPROM.write(55,	208);
  EEPROM.write(56,	229);
  EEPROM.write(57,	255);
  
  digitalWrite(LED, HIGH);
  delay(10000);
  
}

The LED blink and delays are for not writing the EEPROM unnecessarily. There probably is a better way to do this but I have only just begun programming and it worked for me.

write_fade_array_to_eeprom.ino (1.66 KB)

That seems a very long-winded way of wearing out your EEPROM.

Here is the code for reading the table and it also has the ability to display the values on the serial monitor, which can be very useful.

#include <EEPROM.h>

int addr = 0;
byte value;
int led = 5;
int brightness = 0;
void setup() {
  Serial.begin(9600); 
 pinMode(led, OUTPUT); 
}


void loop() {
  value = EEPROM.read(addr);
  //Serial.print(addr);
  //Serial.print("\t");
  //Serial.print(value, DEC);
  //Serial.println();
  analogWrite(led, addr);
  addr = addr + 1;
  Serial.print(value);
  Serial.println();
  
   if (addr == 58)  {
   addr = 0;
}
  delay (30);
}

AWOL:
That seems a very long-winded way of wearing out your EEPROM.

I only ran the program once. Hence the long delays that will give the user time to shut down and upload the "real" code. This is ONLY for writing the table into the EEPROM. Perhaps there is a better way to do this, but I couldn't find any.

Perhaps there is a better way to do this, but I couldn't find any.

Put the code in "setup ()" which runs only once.

Thank you for the tip, but that would "steal" almost 2k of flash memory. This way, you can free up that space and just read from the EEPROM once the table has been stored.

Thank you for the tip, but that would "steal" almost 2k of flash memory

No, I'm sorry, I don't understand what you're saying.
I don't see where 2k of anything comes into the discussion.

If you put the EEPROM writing code into "setup ()" it will run only once, and you don't run the risk of wearing out your EEPROM if you accidentally leave the board powered on.

Why store a simple arithmetic progression in a table? Why hammer hardware when you don't need to? Are you planning to create some kind of storage and reading system for lighting effects? Why not just calculate this on the fly?

jasmine2501:
Why store a simple arithmetic progression in a table? Why hammer hardware when you don't need to? Are you planning to create some kind of storage and reading system for lighting effects? Why not just calculate this on the fly?

From this old solder sucker holding hacker, don't worry about hammering the hardware, it can handle it. :wink:

retrolefty:

jasmine2501:
Why store a simple arithmetic progression in a table? Why hammer hardware when you don't need to? Are you planning to create some kind of storage and reading system for lighting effects? Why not just calculate this on the fly?

From this old solder sucker holding hacker, don't worry about hammering the hardware, it can handle it. :wink:

Yeah but it seems unnecessary. I make pretty good money fixing things like that - calling the database for simple arithmetic, that kind of thing. Sure, the DB can handle it, but it's unnecessarily complex, slow, and uses resources that don't need to be used. I'm just wondering - it seems like it would be simple to calculate this instead of reading the animation from a table.

jasmine2501:

retrolefty:

jasmine2501:
Why store a simple arithmetic progression in a table? Why hammer hardware when you don't need to? Are you planning to create some kind of storage and reading system for lighting effects? Why not just calculate this on the fly?

From this old solder sucker holding hacker, don't worry about hammering the hardware, it can handle it. :wink:

Yeah but it seems unnecessary. I make pretty good money fixing things like that - calling the database for simple arithmetic, that kind of thing. Sure, the DB can handle it, but it's unnecessarily complex, slow, and uses resources that don't need to be used. I'm just wondering - it seems like it would be simple to calculate this instead of reading the animation from a table.

That pretty much is the classic design trade-off of deciding if code size or speed of the task is more important for a specific application/project. Table look up is a classic speed improvement method if one has the available memory space to hold the table. Recall that floating point calculations can be a huge speed bump on a 8 bit processor. If working with ints then cleaver algorithms and formulas might very well be fast enough with real memory saving.

Yeah I always forget about that floating point thing. This could be done with integers though I think. I think the way to do it is to fade linearly, but adjust the time between steps. Something like this…

for (int brightness = 255; i > 0; i--) {
  setLED(brightness);
  delay(255 - brightness); 
}

I wouldn’t use it that way, I would re-work it to work with millis() but that’s the general idea - just go faster with each step (or slower).

Try this:

unsigned int brightness=0xffff;
while (brightness > 255) {
   setLED(brightness>>8);
  brightness -= brightness>>2;
  delay(10);
}

fungus:
Try this:

unsigned int brightness=0xffff;

while (brightness > 255) {
  setLED(brightness>>8);
  brightness -= brightness>>2;
  delay(10);
}

There’s only gonna be 4 steps in that though, right?

fungus:
Try this:

unsigned int brightness=0xffff;

while (brightness > 255) {
   setLED(brightness>>8);
  brightness -= brightness>>2;
  delay(10);
}

That's a step multiply by 0.75. The table originally cited uses a step multiply of about 0.9. That would be more like

brightness -= brightness>>4;

This will fail - and the original code will also perform poorly - at low PWM values. 16 bit PWM would be better, and the calculation - which is still deadly fast - should be performed as 32 bit integers.

Paul__B:
That's a step multiply by 0.75. The table originally cited uses a step multiply of about 0.9.

It makes no difference, the curve is logarithmic and fades nicely. If you want it to fade faster/slower you do it more/less often.

If you really want to you can increase/decrease the amount:

brightness -= brightness>>3;
// or
brightness -= brightness>>5;
brightness -= brightness>>4;

This will fail - and the original code will also perform poorly - at low PWM values.
[/quote]

Try it and see...I do this sort of thing all the time and it works beautifully. I gave up using tables a long time ago.

I assumed 8 bits, but the same technique works with more.

jasmine2501:

unsigned int brightness=0xffff;

while (brightness > 255) {
   setLED(brightness>>8);
  brightness -= brightness>>2;
  delay(10);
}



There's only gonna be 4 steps in that though, right?

No, because it compounds. You are subtracting a quarter of the current value, the second time you do it, you do not reduce it to half, but to 0.5625 (or whatever approximates that as a 16 bit integer), the third time to 0.422, the fourth to 0.311, thence 0.237, 0.177, 0.133 and so on.

My previous comment got a little muddled. The algorithm will work quite well in 16-bit arithmetic, it is the 8-bit PWM that becomes too coarse at the lowest levels. You do of course have to limit the shift-and-subtract algorithm once it approaches a value where it no longer reduces at all.

The 25% per step reduction is however, a little coarse for smooth dimming where the steps are slow enough to be seen. This can be demonstrated using the PWM values on a MAX7219.

/* Fading demonstration
Code for max 7219 from maxim, reduced & optimised for using multiple 7219 cascaded.
______________________________________

General notes: 

- if using only one max7219, then use maxSingle function to control
the module --- maxSingle(register (1-8), column (0-255))

- if using more then one max7219, and all should work the same, use maxAll
function --- maxAll(register (1-8), collum (0-255))

- if using more than one max7219 and want to change something on one module only,
then use maxOne function 
--- maxOne(module you want to control [1=first], register [1-8], column [0-255])

 During initiation, be sure to send every part to every max7219 and then upload it.
For example, if you have five max7219's, you have to send the scanLimit 5 times
before you load it, otherwise not every max7219 will get the data. the (fixed)
variable maxInUse keeps track of this, just tell it how many max7219 you are using.
*/

#include <Wire.h> 

int dataIn = 2;            // "DIN" on module
int load = 3;              // "CS" on module
int clock = 4;             // "CLK" on module
const int ledPin =  13;    // LED pin number

int maxInUse = 1;          // set how many MAX7219's used
int ledState = LOW;        // initialise the LED

int e = 0;                 // just a varialble

// define max7219 registers
byte max7219_reg_noop        = 0x00;
byte max7219_reg_digit0      = 0x01;
byte max7219_reg_digit1      = 0x02;
byte max7219_reg_digit2      = 0x03;
byte max7219_reg_digit3      = 0x04;
byte max7219_reg_digit4      = 0x05;
byte max7219_reg_digit5      = 0x06;
byte max7219_reg_digit6      = 0x07;
byte max7219_reg_digit7      = 0x08;
byte max7219_reg_decodeMode  = 0x09;
byte max7219_reg_intensity   = 0x0a;
byte max7219_reg_scanLimit   = 0x0b;
byte max7219_reg_shutdown    = 0x0c;
byte max7219_reg_displayTest = 0x0f;


void putByte(byte data) {
  byte i = 8;
  byte mask;
  while(i > 0) {
    mask = 0x01 << (i - 1);      // get bitmask
    digitalWrite( clock, LOW);   // tick
    if (data & mask) {           // choose bit
      digitalWrite(dataIn, HIGH);// send 1
    } else {
      digitalWrite(dataIn, LOW); // send 0
    }
    digitalWrite(clock, HIGH);   // tock
    --i;                         // move to lesser bit
  }
}

// maxSingle is the "easy" function to use for a single max7219
void maxSingle( byte reg, byte col) {    
  digitalWrite(load, LOW);  // begin     
  putByte(reg);             // specify register
  putByte(col);             //((data & 0x01) * 256) + data >> 1); // put data   
  digitalWrite(load,HIGH); 
}

// initialize all MAX7219's
void maxAll( byte reg, byte col) {
  int c = 0;
  digitalWrite(load, LOW);
  for ( c =1; c<= maxInUse; c++) {
  putByte(reg);             // specify register
  putByte(col);             //((data & 0x01) * 256) + data >> 1); // put data
    }
  digitalWrite(load,HIGH);
}

// for adressing different MAX7219's while cascaded
void maxOne(byte maxNr, byte reg, byte col) {    
  int c = 0;
  digitalWrite(load, LOW);  // begin     
  for ( c = maxInUse; c > maxNr; c--) {
    putByte(0);             // no operation
    putByte(0);             // no operation
  }

  putByte(reg);             // specify register
  putByte(col);             //((data & 0x01) * 256) + data >> 1); // put data 

  for ( c = maxNr-1; c >= 1; c--) {
    putByte(0);             // no operation
    putByte(0);             // no operation
  }
  digitalWrite(load,HIGH); 
}

void putCol( byte colno, byte coldat) {
// Interprets colno as (zero ref) index in combined array
    byte t;
    t = colno >> 3;
    byte u;
    u = colno & 0x07;
    maxOne(t+1, u+1, coldat);
}


void dispon() {
 maxAll(max7219_reg_shutdown, 0x01);               // Display on
}  

void dispoff() {
 maxAll(max7219_reg_shutdown, 0x00);              // Display off
}  

byte pattern;           // bit mask
byte dimstate;          // state pointer

void worker () {
  switch (dimstate) {
    case 0:
      maxAll(max7219_reg_intensity, 0x0f & 0x0f);  // middle argument is intensity value
      dimstate++;
      break;
      
    case 1:
      maxAll(max7219_reg_intensity, 0x0b & 0x0f);
      dimstate++;
      break;
      
    case 2:
      maxAll(max7219_reg_intensity, 0x08 & 0x0f);
      dimstate++;
      break;
      
    case 3:
      maxAll(max7219_reg_intensity, 0x06 & 0x0f);
      dimstate++;
      break;
      
    case 4:
      maxAll(max7219_reg_intensity, 0x05 & 0x0f);
      dimstate++;
      break;
      
    case 5:
      maxAll(max7219_reg_intensity, 0x04 & 0x0f);
      dimstate++;
      break;
      
    case 6:
      maxAll(max7219_reg_intensity, 0x05 & 0x0f);
      dimstate++;
      break;
      
    case 7:
      maxAll(max7219_reg_intensity, 0x06 & 0x0f);
      dimstate++;
      break;
      
    case 8:
      maxAll(max7219_reg_intensity, 0x08 & 0x0f);
      dimstate++;
      break;
      
    case 9:
      maxAll(max7219_reg_intensity, 0x0b & 0x0f);
      dimstate = 0;
      break;
      
    default:
      maxAll(max7219_reg_intensity, 0x0f & 0x0f);
      dimstate = 0;
      break;
      
    
    }
  }
  
// the follow variable is a long because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long interval = 200;           // interval at which to step (milliseconds)
long previousMillis = 0;      // will store last time LED was updated

void setup () {

  pinMode(dataIn, OUTPUT);
  pinMode(clock,  OUTPUT);
  pinMode(load,   OUTPUT);
  pinMode(ledPin, OUTPUT);      

  //Serial begin(9600);
  digitalWrite(13, HIGH);  

//initiation of the max 7219
  maxAll(max7219_reg_displayTest, 0x00); // no display test
  maxAll(max7219_reg_scanLimit, 0x07);   // all columns in use   
  maxAll(max7219_reg_decodeMode, 0x00);  // using a LED matrix (not digits)
  maxAll(max7219_reg_shutdown, 0x01);    // not in shutdown mode
  putCol(0,0);                           // blank.
  pattern = 0x7e;                        // 6 middle bits
  for ( int j = 1; j < 7; j++ ) {        // 6 middle rows
    putCol(j,pattern); }                 // Show the column.
  putCol(7,0);                           // blank.
  maxAll(max7219_reg_intensity, 0x08 & 0x0f);  // middle argument is intensity value
                                               // range: 0x00 to 0x0f
  dimstate = 0;
}  

void loop () {

  unsigned long currentMillis = millis();
 
// Active waiting for next event
  if(currentMillis - previousMillis > interval) {
    // save the last time you blinked the LED 
    previousMillis = currentMillis;   

    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW) { ledState = HIGH;  } else { ledState = LOW; }
    // Timed process:
    
    worker();

    // set the LED according to ledState:
    digitalWrite(ledPin, ledState);
    }

}

I do think that the 6.25% per step reduction (shift right by 4 and subtract) would be preferable.

Sounds like we need two non-blocking general purpose pwm functions to be designed:

log_fadeUp(byte desired_pwm_pin, int desired_millis_duration); // fade from 0 to 255 in log progression

log_fadeDown((byte desired_pwm_pin, int desired_millis_duration); // fade from 255 to 0 in log progression.

and maybe a:
byte fade_status_complete(byte desired_pwn_pin); //return true if any prior fading is complete
How it works behind the curtains is important I'm sure, but simple API is the key to utilization for the majority of arduino uses I'm sure.

So what can someone come up with?

Paul__B:
You do of course have to limit the shift-and-subtract algorithm once it approaches a value where it no longer reduces at all.

That's why I cut it off when it reached 255.

It also works in reverse for fade in of an LED. You start with 255 then add some fraction of the number to itself.

Wouldn't PROGMEM be a good solution for this?