Digging Deeper

You might want a slightly different gamma table. Here’s the code that generated our sample. This is not an Arduino sketch…it’s written in Processing (free download)…chosen because it installs easily on Windows, Mac or Linux. Run this program, then copy-and-paste the output into an Arduino sketch, replacing the gamma[] table.

Download: file
// Generate an LED gamma-correction table for Arduino sketches.
// Written in Processing (www.processing.org), NOT for Arduino!
// Copy-and-paste the program's output into an Arduino sketch.

float gamma   = 2.8; // Correction factor
int   max_in  = 255, // Top end of INPUT range
      max_out = 255; // Top end of OUTPUT range

void setup() {
  print("const uint8_t PROGMEM gamma[] = {");
  for(int i=0; i<=max_in; i++) {
    if(i > 0) print(',');
    if((i & 15) == 0) print("\n  ");
    System.out.format("%3d",
      (int)(pow((float)i / (float)max_in, gamma) * max_out + 0.5));
  }
  println(" };");
  exit();
}

This first line sets the exponent for the correction curve:

Download: file
float gamma   = 2.8; // Correction factor

Higher values here will result in dimmer midrange colors, lower values will be brighter. 1.0 = no correction. The default of 2.8 isn’t super-scientific, just tested a few numbers and this seemed to produce a sufficiently uniform brightness ramp along an LED strip; maybe you’ll refine this further.

max_in and max_out set the input and output ranges of the table. The defaults here are for the NeoPixel brightness range of 0–255…if you’re working with LPD8806 strips (which have a 7-bit brightness), max_out can be changed to 127 (might want to leave max_in at 255, since a lot of existing code assumes 8-bit colors).

If you’re really persnickety, you can make separate tables for red, green and blue to achieve a more neutral white balance, adjusting max_out for each.

Perks and Caveats

Aside from aesthetics, an unexpected benefit of gamma correction is that battery-operated projects tend to run longer, because of the lower intermediate brightnesses.

On the downside, look at the first few lines of the gamma-correction table:

Download: file
const uint8_t PROGMEM gamma[] = {
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,
    1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2,
    2,  3,  3,  3,  3,  3,  3,  3,  4,  4,  4,  4,  4,  5,  5,  5,
...

Notice the first 28 elements are all 0, the next 12 are 1, next 7 are 2, and so forth. Input values from 1–27 all result in an “off” LED. This is the unfortunate reality of quantization. The LED driver only handles 256 distinct PWM settings, period. When we move values up or down, they still must fall in one of those same 256 fixed bins…we don’t get new ones…the result being much fewer distinct output values (163 in this case). It’s most pronounced at the low end, progressively less toward the top.

“Luxury” LED drivers such as the PCA9685 and TLC5947 use 12-bit PWM (4096 output levels) to minimize the effects of quantization. More advanced drivers like FadeCandy use dithering to 'fake' a wider dynamic range

This guide was first published on Aug 29, 2014. It was last updated on Aug 29, 2014. This page (Digging Deeper) was last updated on Aug 25, 2019.