Smooth Voltage Regulation for LED dimming on 8-bit microcontrollers

Smooth Voltage Regulation for LED dimming on 8-bit microcontrollers

Arduinos can be used as MQTT-enabled LED strip controllers. However, their limited PWM step resolution can pose a problem for the smooth LED lighting look we want to achieve. This post explains the reasons why this happens, explores a cheap and easy solution and provides plenty of reference material to help you come to terms with pulse-width modulation on the Arduino and achieving smooth, seamless, and futuristic LED dimming.

Background (Optional read)

But first… some biology. It may come as a surprise to you that the human eye’s perception of light is logarithmic, as opposed to linear. This means that as a light source increases in brightness it becomes more difficult for the eye to perceive minute differences. Another way to think about it is this: Say there are two light sources and the first is twice as powerful as the one next to it. Even though they energy it outputs is double, it only looks incrementally brighter to us because of the eye’s perception of light intensity.

We are more sensitive to small changes in luminance at the low end of the scale than the high end. Source

In addition to this, LED’s themselves are logarithmic in nature. This means a \Delta v at low voltages is a lot more noticeable as the same \Delta v at high voltages. (low and high voltage are terms relative to the rating of your LED strips.)

Why am I bringing this up? Well, in order to create smooth fading LED strips we must apply a logarithmic brightness scaling function such that the dimming action appears linear.

Dimming Modes

Dimming by Voltage Regulation

Using a varible resistor we can adjust the voltage supplied to the LED strip directly. This allows for some dimming capability in the high voltage range (50-100% of the rated voltage). At <50% rated voltage, the LED will just turn off because the voltage is not high enough to power it.

Most LEDs are driven by PWM (pulse-width modulation) and will simply not work at voltages lower than the rated voltage. The following method is more appropriate.

Dimming by PWM

Pulse-width modulation is a way to flash LEDs rapidly at full brightness. The LEDs are on for a fraction of the duty cycle, and off for the remainder of the cycle. The longer they remain on, the brighter the resulting light will be to the eye. At 50% on, the light will appear half as bright as it does at 100% duty cycle.

PWM Diagram

Source: What is Pulse-Width Modulation (PWM)?

Connecting this PWM signal voltage to a MOSFET and an external power source will produce a high voltage at the voltage required to run the LED strip (12V). The low voltage signal is stepped up to operate in the high voltage range of 0-12V, as the Arduino it self cannot produce a high voltage or output more than a few milliamps.

The Problem with 8-bit microcontrollers

The processors in 8-bit microcontrollers are capable of producing 255 different voltage levels via PWM on their output pins. This is not enough to create smooth dimming for low brightnesses, resulting in a visibly choppy dimming effect. This is very unattractive for LED lighting.

This problem created by a hardware limitation of 8-bit processors has been discussed in detail on the Arduino forums. Of course, we want smooth and attractive LED dimming at all brightness’s. Choppy animation and fading makes the setup look cheap. So what can we do to solve the problem?

I had a few approaches I wanted to try. They’re listed here for the record (and it might inspire you a bit). The one that worked was the low-pass filter and you can skip ahead to that section down below.

  • Experiment with different smoothing funcions (As I have done in the linked post)
  • keep dimming transition period short (more “Frames per second” and an overall shorter transition make the choppiness less noticeable. This is not solving the problem…rather it is placing a restriction on how the dimmer should be used. i.e. “You must use short transition times”)
  • Use different functions for the high and low voltage part of the brightness scale. Rather than using 256 values on the full scale, we could split the range in two and use one pin (256 values) on the top end and then switch over1 to a differnet pin and apply the same 256 values on the bottom half (where the high resolution is required.), effectively doubling our resoltion.
  • Summing two 8-bit outputs
  • Increasing the PWM frequency (NO IDEA if this has any effect, something to try if you are inclined.)
  • Use a low pass filter (This works!)

The first one (short transition periods) works as well because you are effectively increasing the frames per second. However, we want a solution that can provide butter-smooth, sexy, LED animations at all speeds, unaffected by the length of transition.

Here is a picture of the LED strip lighting I implemented behing my TV. Sexy LED lighting set up behind TV unit

Viable Solution: Low Pass Filter

You can build a low-pass filter using a capacitor and resistor and the following diagram:

Low-pass filter schemmatic

Note that the arrows labelled v_{in} and v_{out} simply indicate that the input and output voltage occurs across the two pins at the end of the arrow.

This is what mine looked like using a capactior and resistor I had at hand.

Quick and dirty low pass filter MOSFET Connection

How does it work?

The capacitor will charge up during the active part of the PWM cycle and it discharges during the inactive part of the cycle. This has the effect of smoothing the output voltage. Rather than a Square wave, our MOSFET will receive a signal that closer resembles a constant voltage.

How do we choose capacitor and resistor values? In short, use this online calculator, however, I strongly recommend you read the linked blog post. It includes a great introduction to PWM and (more importantly) a detailed explanation of how low pass filters work. Once you understand you can try this filter simulator written in Java.2

I used a 220\Omega resistor and a 1000\mu F capacitor. The cap is way too large as is evident by the slow LED response time. There is a trade off in lowpass filters parameters:

  • too small a capacitor and it will be fully charged before the end of the active period, meaning it is not capable of filtering out all the ripples.
  • too large a capacitor and the filter will become unresponsive because of the time is takes the capacitor to charge and discharge exceeds the frequency of voltage peaks.

You will need to determine a cut off frequency acceptable for your application. Try reducing the resistor value as well, this increases responsiveness.

Tip: I placed two 220 \Omega resistors in parallel for an effective $$110 \Omega%% resistance. Placing resistors in paralell reduces their overall resistance. Usefull if you don’t have any small resistors.

Building a cheap oscilloscope

If you have a spare Arduino lying around, you can build a simple oscilloscope using this Instructable.

The Instructable says that a single wire allows you to measure voltage, but in my eperience it does not work. You have to connect a wire to the ground pin on your “Oscilloscope Arduino” and then apply the two wires to the voltage you want to measure on the oscilloscope. (The way you would typically measure a voltage using a multimeter).

The image below shows the low pass smoothing in action. Where we would tpyically see square waves of varying frequencies, we see a smooth voltage change dictated by the discharge of the capacitor. This creates a stunningly smooth (analog) dimming effect. Low pass smoothing

Software Optimisation

Notice in the oscilloscope image, that the voltage changes are limited to the top half of the active region. The voltage dips below 50% only when the LEDs are switched off completely. This is because I programmed some optimisation into the LED firmware such that input values in the range 0 - 255 are converted to values between 150-255. The reason for this is that the LEDs are off for values smaller than 150.

I created a fork of Corban’s firmware to add my changes and opened a pull request on Corban’s codebase. This serves as a simple but robust firmware for all your LED strip controller needs. If you haven’t decided on what arduino to use, I would highly recommend the NodeMCU due to their low-cost (AUD$4.00), the built-in Wifi chip and small formfactor (shown below) .



I think this post has been quite productive. We discussed how Arduinos use pulse-width modulation and how to smoothen out this signal to remove it’s digital nature using a low pass filter. We understand the effect the resistor and capacitor play in a low pass filter and we have successfully built one and verified its workings using a DIY oscilloscope.

The next step for me is to draw up a circuit board that combines all these components (Arduino, MOSFET, low pass filter and power supply/LED connectors) into a single circuit that can be used for all my LED controller applications.

  1. How? Can we do this in software or is special hardware required? The way I see it work is using two PWM pins, both connected to the MOSFET driving your LED strip. On a brightness scale of 0-255, we might dedicate the first third to pin 1 and the upper 2/3 to pin 2.3 As the brightness fades from 255 we use Pin 2 to drive the MOSFET and dictate a voltage. As we cross 255/3=85, we use Pin 1 to provide 255 steps of brightness in the range 0-85. I have not tried this, but you can give it a try if you feel like experimenting. ↩︎

  2. Click launch and run the .jnlp file using javaws.exe in your C:/Program Filters/Java/bin directory. It’s great fun to play with the parameters and see the graph window update in real time like a real oscilloscope! (see section on building a cheap oscilloscope in this post if you missed it, I found it very useful.) ↩︎

  3. Uneven division because we want more resolution in the lower end. ↩︎