Skip to content

LED Strip Dimmer, the software

Managing the luminosity

For a first try, I used the out of the box standard PWM provided by Arduino libraries through the analogWrite() function.

Unfortunately, this is not satisfactory:

  • The analogWrite() function accept an input range of 0..255. This is not enough to provide a smooth change in luminosity. When incrementing the duty cycle, the steps are visible to the eyes, especially for the low values, we can clearly see each increment of the luminosity.
  • The output frequency is around 500 Hz, which was too low for my electronic, and yield to an audible unpleasant noise (again, at list with my components, this may not be true with something else).

So I changed the code to use 16 bits timer for the PWM.

Full 16 bits was not required though, so the range is actually limited to 0..4095, which is enough to provide a smooth change between light steps. It has also the advantage of enabling a higher output frequency (if 16 bits used and no pre-divider, 16 MHz / 65 536 equals 244 Hz, too low! … 16 MHz / 4 096 is approx 4 900 Hz).

With 0..4095 range to control luminosity level is now perfect. Having that, it is obvious that we cannot use just 0..100 steps for ramp up / ramp down: we would go back to the same problem!

After some trials, the good range is using 0..1023 for stepping from 0 to 100 % light (or 100 % to 0).

However, the next test with this new setup was not very good too. The problem now is that a ramp up from 0 to full light is not perceived as increasing linearly. We have the impression that the full luminosity is obtained very quickly and then the luminosity doesn’t seem to increase.

This is a known problem due to the fact that the human eye doesn’t respond linearly to the luminance, but follows more on logarithmic law.

After googling, a little bit, I found an interesting post that gives an algorithm to compute the response curve.

I used this algorithm to generate a 1024 entries lookup table where each index is the linear luminosity level we want to achieve, and the content for each index is the actual PWM value we will send to LEDs (0..4095).

And with that, the result is perfect: the ramp up / ramp down are really smooth without any glitch.

Managing the ramps

The next difficulty was then to generate the ramp up and ramp down values for dimming up and down.

This problem is much like drawing a line on a 2D area: X is the time (0..1023) and Y the luminosity.

The dimmer accepts command telling “go to LLL % luminosity in SSS.S seconds”. So when receiving such commands, the program have to determine what would be each Y for a simulated progression of X representing the requested duration.

Going to the actual implementation, this is not a series of (X, Y) points that are prepared for output, but more a state machine that is updating the Y value every 50 ms during the ramp process. This way of doing allows to implement other tasks in parallel, such as reading serial line to get command from host and taking care of the bi-stable Relay.

For the ramp, there is no floating point computing using multiplication and division.

Instead, for each X increment, the fractional part of Y is held in a ‘quotient’ variable. All parameters to know what to do every 50 ms are prepared at the beginning of the ramp (for instance, slope sign, how much should we add / subtract at each loop, how many loops will the ramp last, etc…). See the comments in the code that explain that process in detail.

At each time the Y value is computed, the function that set the output PWM is called.

Exact code is not detailed in this article, if you are interested, have a look in the code which has a lot of comments.

Host Interface

As said in introduction, the dimmer is driven by the host HTPC by sending command over serial line.

The interpreter is very basic and accepts two types of commands, one for dimming and one for the Relay:

  • ++Dc-vvv-ttt.t D stands for ‘Dim’, c is the channel, vvv the dim value and ttt.t the time (accepting tenth of seconds)
  • ++Rc-v R stands for ‘Relay’, c is the channel and v the realy state (0 / 1 for On / off)
  • ‘++’ is a re-sync pattern (as used in the old days by modems).
  • Currently, only one channel is implemented, and c must be equal to 0.


The code that must be managed with strict timings is synchronized by a flag that is true every 50 ms. This encompass the ramp management and the relay coils activation (using one loop, the coil is activated for 50 ms).

In the main loop, outside this 50 ms paced code, we have the management of serial line data reception and command decoding If a dimming command is received, then all the parameters used to generate the luminosity ramp are prepared at that time.


Bellow is the full code (also available as a zip file):


Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *