Introduction: Practical Guide to LEDs 3 - Switching & Dimming

About: Send me an encrypted message on or visit me on for the latest updates!

You've heard about LEDs. Chances are you've already tinkered with them. But there are so much details you probably don't know about. Sadly the resources available are often incomplete or just unpractical. This guide takes you all the way from a beginner level to adept skills!

This is chapter 3 of a short series. Use the table of contents below to browse the content I've already published.

Your LEDs are working, so now it's time to make them do exactly what you want! This chapter covers everything from adding a simple switch to advanced dimming, suitable for almost all LEDs. Have fun and share the projects you come up with!


  1. Pick your LED!
  2. Essential Circuits
  3. Switching & Dimming
    1. On & Off
    2. Switching - NPN Transistor
    3. Switching - N-Channel Mosfet
    4. Analog Dimming
    5. Digital Dimming
    6. Pulse Width Modulation (PWM)
  4. Matrix & Multiplexing (new!)
  5. High Power & Lighting

Step 1: On & Off

Turning LEDs on and off manually is as simple as it can get: Just put a switch in series with the LEDs you want to control. Most buttons and small switches will handle 100mA or so. More rugged switches, such as those used in power supplies, will handle a few amps.

In many projects you want the LED do something, such as blinking, without you pushing a button constantly. While most basic chips and circuits are able do drive a standard LED at 20mA, they are likely to be damaged at higher loads. For example the ATMEGA328P, the chip of the ARDUINO, can only deliver around 20mA (40mA, if you stress the chip to the limit).

To provide a higher current the signal needs to be amplified. Fortunately this is a fairly easy!

For this task usually one of the following components is chosen:

  1. (Bipolor) Transistor
  2. (Mos-)FET

These parts come in two types, NPN & PNP or N-Channel & P-Channel, respectively. While both types can be used to switch LEDs, NPN transistors and N-channel FETs require less circuitry to work. For now we'll stick to the simple solution.

NPN transistors and N-channel FETs will be used as a "low side switch", meaning they cut the connection between the LEDs and gound. Although it seems counter-intuitive at first it does not matter whether a load (such as an LED) is switched. Current flow requires a conducting path from the plus to the minus terminal. As soon as this loop is broken, it doesn't matter where, the current will stop flowing.

Step 2: Switching - NPN Transistor

Bipolar transistors are best suited for light to medium loads. They are cheap, widely available and often come in THT packages.

The example project: Ambient light activated lamp

The operating conditions:

  • 3 white LEDs are connected in series with a resistor
    • ILED = 30mA (total current to switch)
  • The logic signal voltage is
    • UIO = 5V
  • The transistor chosen is the common BC817/BC337, but almost any will work

The circuit:
The circuit consits of two parts, the LEDs in series with R1 and the transistor with R2. The external circuitry provides a 5V signal if the LEDs should be turned on.

LEDs & R
The first thing you need to do is to calculate the value of R1 as described in chapter 2 - LEDs in Series.

Transistor & R
A bipolar transistor is turned on by a current flow from its base (B) to its emitter (E). The larger this current is, the more current can flow from its collector (C) to its emitter. Those currents are called IBE and ICE, respectively. Their ratio is called GAIN.


The gain varies a LOT in between parts, across temperature and current. For reliable results it is crucial to pick the lowest possible value from the datasheet for these calculations (see picture above). With the LED current and the the gain you can calculate the required switching current:


Now it is important to know that the connection from B to E is a diode, hence the little diode-like arrow in the schematic. This this BE-diode has a forward voltage of about 0.7V. To set the current through it a resistor is required. Fortunatly the calculations are identical to those of a single LED.

R2 = (UIO - UBE) / IBE = (UIO - UBE) * GAIN / ILED

With our values this results in:

R2 = (5V - 0.7V) * 100 / 0.03A = 14.3kΩ

R2 may be sligtly smaller than calculated to ensure IBE is large enough to drive the LEDs. Therefore

R2≈ 12kΩ

Transistor power dissipation:

Lastly we need to check if the transistor can handle the load without overheating. The maximum power disapation can be found in the datasheet:

Ptot = 250mW

The power dissipated is calculated as:


PBE is so small that we'll ignore it. UCE depends on ICE as Fig 8. from the datasheet shows. To illustrate how to read the diagram I've uploaded an annotated screenshot. The "@" below refers to a specific operating condition, in this case UCE is depended on ICE.

P = PCE = UCE@ICE * ICE = UCE@30mA * 30mA = 20mV * 30mA = 0.6mW

Since P is way smaller than Ptot the transistor is suitable for this application.

Step 3: Switching - N-Channel Mosfet

Mosfets are slightly more expensive but can drive huge currents effortlessly. Unfortunately they often come in SMD packages, which can be complicated to work with.

The example project: Ambient light activated lamp - 5050 LED strip Edition

The operating conditions:

  • The power supply voltage is
    • USYS = 12V typical.
  • The LEDs are a 2m long 5050 LED strip with 120LEDs
    • ILED = 0.8A
  • The logic signal voltage is
    • UIO = 5V
  • The mosfet choosen is the common AO3400, it has a low on-resistance

The circuit:
The circuit consits of two parts, the LED strip and the mosfet with R2. The external circuitry provides a 5V signal if the LEDs should be turned on.

Mosfet & R2:
Unlike a transistor a mosfet is turned on by a voltage between its gate (G) and its source (S). This voltage is called VGS. As soon as VGS exceeds a certain threshold the connection from the drain (D) to the source will turn into a resistor with a very low value, RDS(ON).

There is no current flowing into the gate of a mosfet, and thus no resistor to limit the current is required. Instead R2 switches the mosfet off when no signal is applied. Its value is not critical and can be anything from 1kΩ to 100kΩ.

Mosfet turn-on voltage:

Some mosfets can require a quite high voltage (VGS) to turn on completely. If a part contains information like

RDS(ON) (at VGS = 4.5V) < XX mΩ

in the datasheet, it means it can be controlled voltages larger 4.5V. This information can also be hidden in the parameter tables, so check them carefully!

Mosfet power dissipation:
Again we check if the part is suitable for the load. The maximum power dissipation is

Ptot = 1.4W

The power dissipated is calculated as:

P = RDS(ON) * IDS²

This resuslts in

P = 33mΩ * (0.8A)² = 21.12mW

Under the identical conditions a BC817 transistor would generate

P = UCE * ICE = 300mV * 0.8A = 240mW

in heat, which pushing it to its limits. If the ambient temperature is higher than 25°C the transistor would operate beyond it's rating and fail soon. I strongly recommend you to select better parts than required as it allows for some errors in your calculation without failing.

Step 4: Analog Dimming

Over the last few years LEDs got constantly brighter and more powerful.

However there are situations where exactly that isn't wanted:
To bright status lights can disturb your sleep or drain the battery of a portable device too fast.

The simplest solution is to increase the value of the resistor. Doubling the resistor value results in about half the current through the LED. This is common practice in the industry for simple applications, but has three major disadvantages:

  1. The brightness is fixed
  2. LEDs are non-linear, making it hard to set the brightness precisely
  3. At lower currents some LEDs change their color slightly

There are several circuits to to solve the first issue:

  1. Widely used are potentiometers, which are nothing but variable resistors. They come with either a knob or a slider to change the resistance from 0% to 100%. Add a normal resistor with the minimum resistance required in series to ensure the LED always stays within operating condition, even when the potentiometer is set to 0% (so basically no resistor at all).

  2. Another option is to use switches to select a certain resistor. You can a also use a simple on/off switch to 'switch in' a resistor parallel to the 'main' resistor to decrease the total value. Just make sure the total value is still higher than the minimum resistance. This method is especially useful if you only need a few modes of operation, e.g. bright, medium and dim for a flashlight.

  3. There are a variety of circuits which do not require user control. Instead they use analog parts such as transistors or operational amplifiers to set various currents.

As far as I know there are no current regulating circuits which can fix issue number 2 & 3. This does not render analog dimming useless, though. For many devices it is still good enough, and it's far superior in terms of price and simplicity.

Step 5: Digital Dimming

While analog dimming requires some engineering, digital dimming is as simple as pushing a switch. Literally.

Human eyes are slow. For a film around 24 frames per second (fps) are enough that out brain merges it into a moving image. The same thing can be done with LEDs. Turn them on and off fast enough and we won't see a blinking LED, but an LED with lower brightness.

To see continuous light the switching frequency needs to be higher than for a film, 100Hz at minimum and up to a few kHz for moving objects. The unit hertz - Hz for short - is the frequency and can be interpreted as cycles per second. In each cycle the LED will be turned on for some time and off till the next cycle starts. The emmited light is proportional to the on time compared to the duration of a single period. The diagram above illustrates the typical driving signal.

Do note however that the brightness perceived by humans is not equal to the amount of emitted light. This allows us to distinguish light and dark colors at very bright and dim light conditions. Correcting for that is possible, but requires more advanced code or circuitry. As such it won't be covered here, but might be included in future chapters.

The benefits over analog dimming are:

  • great control & color accuracy
  • simple & small circuitry
  • lower power consumption

Obviously no human can push a button that fast. But electronics can.

There is a variety of circuits without microcontrollers which can generate PWM signals. Commonly used is the astable multivibrator based on two transistors or the IC NE555. While the basic design is quite simple it can be hard to tweak the values of all components. For advanced discrete (i.e. without a microcontroller) LED circuits I highly recommend to check out simpletronic's instructables. His projects go far beyond usual maker's circuits by using nothing but standard components.

For the remaining chapter we'll generate PWM waveforms with a micorcontroller. It can be programmed as you like, has many outputs (for many LEDs), and is widely available. In this guide we'll be using the ATMEGA328P, the chip used in many ARDUINOs, but other controllers will work as well.

illustration source:

Step 6: Pulse Width Modulation (PWM)

Pulse Width Modulation - PWM for short - is the correct technical term for digital dimming. It can be also used for many other applications beside controlling LEDs, therefore most microcontrollers have specialized hardware build-in.

To get started connect either a single LED directly or a group of LEDs through a transistor/mosfet to the PWM output OC0B of the ATMEGA328P/ ARDUINO. Each of the six build in PWM channels can be controlled individually while running your normal application code! If more channels are desired you need to switch the IOs within your application code, which is called Soft-PWM.

This step is all about code required to set up and run a PWM channel. While this is not an introduction to programming, basic knowledge will be enough to follow along. To get in touch with the inner workings of the chip, all registers (the internal settings storage) will be accessed directly in C and not through some fancy library. It may take a bit longer to learn, but performance is worth it!

The example project: Brightness adjustable 5050 LED strip lamp

The circuit:
The circuit is taken from "Chapter Switching - N-Channel Mosfet", the external circuitry is replaced with the microcontroller. The schematic shows the minimum of external components required for the ATMEGA328P, they are all included on every ARDUINO board.

The goal:

Usually there is some sort of input, such as a button, a knob or a sensor, to determine the output brightness. To keep things as simple as possible we'll program in a fixed value. Feel free to expand the program to suit your needs!


The official datasheet is always the #1 resource for any question you have about a particular component. If you haven't done so, download and open the complete datasheet of the ATMEGA328P. On mobile this can be quite a hassle, therefore I've included the relevant parts as screenshots above. (All screenshots of the datasheet are property of Atmel and are not covered by the licence of this instructable)

All code is written in AtmelStudio (which is available for free here) and uploaded with an AVR Dragon ISP Programmer. It should work just as well in the ARDUINO programming environment, although I have to admit I've never tried it. Please let me know if there are any issues with the code.

Every new project contains:

#include <avr/io.h>

int main(void)
    /* Replace with your application code */
    while (1)
        /* main loop */

The #include <avr/io.h> defines all registers with short names instead of just numbers. After power on the microcontroller strats executing code beginning from int main(void). After a few initializations the main loop while (1) is started, which continues until power is turned off.

The Timer/Counter0:

The most basic timer is Timer0 (p. 93-110). On power on it is disabled like all other peripherals to prevents unintentional behaviour. The timer has multiple modes of operation with additional settings. For LEDs the Fast PWM Mode (p. 99) is generally best suited.

How it works:

In Fast PWM Mode the timer is counting from 0 to 255 and begins then from 0 again. The change from 255 to 0 is called overflow. The current timer value is stored in the register TCNT0 (p. 108), which can be read ad modified in software. The timer module features two Output Compare Registers (OCR0A & OCR0B). One setting enables a continues comparison between these two values:

  • When TCNT0 < OCR0B ⇒ output on ⇒ LED on
  • When TCNT0 ≥ OCR0B ⇒ output off ⇒ LED off

To get ¼ of the total brightness OCR0B must be set to 63, which is ¼ of the maximum 255 steps. The figure 15-6 from the datasheet illustrates this.

Configuration - Mode Setting:

To load this setting the Control Register A (TCCR0A) (p. 104) needs to be written:

TCCR0A = (1<<COM0B1)|(0<<COM0B0)          // Turn output off when TCNT0 >= OCR0B
        |(1<<WGM01)|(1<<WGM00);           // Select Fast PWM Mode

To write to a register type its name TCCT0A the value after a = . The expression (1<<COM0B1) turns on the bit at the position of COM0B1. A | combines all individual bits to a single byte. All other bytes are set to 0. Before flashing AtmelSudio turns the well readable code into the byte 0b00100011, resulting in a very fast execution on the chip.

Configuration - Frequency Setting:

One cycle takes 256 steps to complete. Depending on the setting and external parts the ATMEGA328P runs at 1MHz (default factory setting), 8MHz (max frequency of internal RC oscillator) or 16MHz (with external crystal, default on all ARDUINO boards). The PWM frequency can be calculated with:

fPWM = fclock / steps

With values above this resuslts in:

fPWM = 3906Hz @ fclock = 1MHz
fPWM = 31250Hz @ fclock = 8MHz
fPWM = 62500Hz @ fclock = 16MHz

To avoid unforeseen issues (high switching losses, crosstalk, EMI) it is the best practice to keep all frequency as low as possible. To archive this the Timer0 features a prescaler which can divide the clock by 1, 8, 64, 256 or 1024. Setting a prescaler value also turns turns the timer on.

Assuming the clock is running at either 8MHz or 16MHz a prescaler of 256 results in the lowest frequency above 100hz.

fPWM = 31250Hz/256 ≈ 122Hz @ fclock = 8MHz
fPWM = 62500Hz/256 ≈ 244Hz @ fclock = 16MHz

It is set by writing to the Control Register B (TCCR0B) (p. 107):

TCCR0B	= (0<<CS02)|(1<<CS01)|(0<<CS00);   // Start timer with prescaler of /8

Configuration - Enable Output:
By default all IOs are configured as inputs to prevent damage to external circuitry. This setting also applies to the timer. The corresponding output to the PWM channel OC0B is PD5. This is done with:
DDRD	|= (1<<DDD5);                      // Enable the output for the IO PD5
A normal = forces all other bits to zero which is highly impractical for IO ports. Instead |= is used to set the individual bit. To turn of any amount of bits (examble for PD5 & PD6) use:
DDRD	&= ~((1<<PD5)|(1<<PD6));

Configuration - Brightness Setting:
Currently the LED is running at the default brightness value of 0. To change this write the desired brightness level to the register OCR0B (p. 108):
OCR0B	= 63;                               // Set brightness to 25%

You can edit this value at any time. To prevent glitches the value will be stored temporally and loaded to OCR0B when the cycle is completed (an overflow occurs).

Get playing!:

I get it, a static brightness is boring as hell. To help you to get started I've included an advanced example below. The code uses both PWM channels to drive tow colors of an RGB LED, it gracefully combines both colors with a smooth and slow transition. It uses an interrupt triggered by Timer0 overflow. If enabled, an interrupt pauses the main code (in the main loop), executes a few lines of code, and the returns to the main code. With interrupts there is no need for delays!

#include <avr/io.h>                             // Defines names most functions
#include <avr/interrupt.h>                      // Provides access to the assmbler commands sei() and cli()

volatile uint8_t brightness_LED1    = 0;        // uint8_t generates a byte-sized variable,
                                                // volatile is required for access within an ISR
volatile uint8_t brightness_rising  = 1;        // 0 = false, all other values = true 

int main(void)
    TCCR0A  = (1<<COM0A1)|(0<<COM0A0)           // Turn output off when TCNT0 >= OCR0A
             |(1<<COM0B1)|(0<<COM0B0)           // Turn output off when TCNT0 >= OCR0B
             |(1<<WGM01)|(1<<WGM00);            // Select Fast PWM Mode
    TCCR0B  = (1<<CS02)|(0<<CS01)|(0<<CS00);    // Set the prescaler to /256 and start the timer
    DDRD    |= (1<<DDD5)|(1<<DDD6);             // Enable the output for the IO PD5, PD6

    TIMSK0  = (1<<TOIE0);                       // Enable interrupts @ overflow
    sei();                                      // Enable interrupts in general

    while (1) 
        /* main loop */

ISR(TIMER0_OVF_vect)                            // This code will be executed every overflow!
    // Load current values
    OCR0A   = brightness_LED1;                  
    OCR0B   = 255-brightness_LED1;
    // Prepare next values
    if(brightness_LED1 == 0)                    // Check if lowest value is reached
        brightness_rising = 1;                  // Set direction to count up
    else if (brightness_LED1 == 255)            // Check if highest value is reached
        brightness_rising = 0;                  // Set direction to count down
    if (brightness_rising)                      // Check counting direction
        brightness_LED1++;                      //increase brightness by 1
        brightness_LED1--;                      //decrease brightness by 1
LED Contest

Participated in the
LED Contest