PWM drive

The LEDs in the module receive varying current to adjust their brightness via the hardware PWM features of the PIC16LF1933 in conjunction with Timer 2. The module has 4 capture/compare peripherals, which is perfect for the 4 LEDs in the module.

Each PWM output controls a simple current limited 2-transister drive circuit. This circuit is superior to a simple single transistor switch, as the CE junction of one transistor is used as a fixed voltage drop controlling current through a resistor of known size. This is particularly nice given that different LED modules have differnt voltage drops, and voltage drop is also a function of current and temperature. A fixed current driver removes these variables.

Mixing color and brightness

An interesting challenge is to manipulate the output of the discrete color LEDs both for the purpose of emitting a specific color and for controlling the brightness. The solution is to treat the pre-computed PWM output value as a fraction of the maximum value (255). In the same fashion, the color range and the brightness are also treated as a fraction of their respective maximums. Therefore, the rgb drive output is essentially the product of the color and brightness values. Simple, and it seems to work out pretty well.

rgb = 255 * (color/color_max * bright/bright_max)

Of course, for this processor the preference is to do this computation in integer math. The actual implementation looks more like this:

color: 0..63
bright: 0..BRIGHT_MAX
rgb = (color << 4) * bright / 4 / BRIGHT_MAX

LEDs

An interesting effect was uncovered during testing. A certain delta change in drive string of a LED at a relatively low average power level is readily perceptible vsually, while the same delta is hard to detect at higher power levels. Some research shows this to be a nature of the human eye -- it perceives changes at lower intensities more readily than at higher percentages (1). To get a perceived linear output, the PWM value must be adjusted according to CIE Lightness.

L* = 116(Y/Yn)^1/3 - 16 , Y/Yn > 0.008856
L* = 903.3(Y/Yn), Y/Yn <= 0.008856

Using these formulas, a look-up table can be created to provide the compensation. It works pretty well. The rgb.c and rgb.h files in the repository implement this feature. Note that there are two different look-up tables available, depending upon the resolution desired.