Take Control

20A 4-Channel Lighting Controller

Rob Bell and Mike Hansell

Issue 15, September 2018

This article includes additional downloadable resources.
Please log in to access.

Log in

A powerful DC PWM lighting controller for cars, caravans and boats.


Take control of your 12V lighting, adjusting the brightness to meet your needs.


You have gone and installed some amazing new 12V LED strip lights, and they look awesome, right? Absolutely. You might need sunglasses to eat dinner at night though so you don’t feel like you’re looking into the sun, but fear not! This 20A lighting controller project has come to save you from drowning in excess lumens!

OK - so perhaps that’s a little melodramatic, but who doesn’t love a good laugh. But it’s true. Sometimes we simply don’t want our lights running at full brightness. We’ll need bright light if we’re reading, but dim ambient light to softly illuminate a room if we’re watching TV, for example.


This is essentially a multi-channel PWM (Pulse Width Modulation) controller, with four independent outputs supporting up to 5A of current on each channel, for a combined 20A power handling. Each channel is independently powered and protected. This helps avoid having to accommodate over-sized PCB tracks and massive binding posts to handle the current.

We must point out that no voltage adjustments are made on the board between source and output on each channel - it only provides PWM output on the source voltage, as well as the short circuit / reverse polarity protection. However as long as you match the input voltage on each channel to the input voltage required by your lighting, you can basically do whatever you want. We have optimised the hardware selections for 12V since that’s expected to be most common.

The main board power is regulated from 12V, using 12V as well as 5V thanks to the on-board 7805 regulator. The 7805 provides power to the microcontroller and matrix display, while 12V is retained for the ULN2803 IC (Darlington transistor array) to ensure the MOSFETs are switched appropriately since they can struggle at 5V. They can be switched to 5V using jumper P2, in case you’re using different MOSFETs.

The Control Stage

For the full build, we’ve removed the UNO, and are using a programmed ATmega328p on its own. The microcontroller does all the heavy lifting for the user interface, which is comprised of just three switches and a LED matrix display.

You may be wondering looking at the pictures, why didn’t we include the display on the PCB? We’re using an 8x8 LED display to provide feedback on current output levels. While the 8x8 LED matrix requires substantial IO to control directly, our module (as many of these modules do) has a MAX7219 driver chip on board. This chip allows us to use just three IO pins and control everything using SPI.

This SPI compatibility means that you could use this PCB with any SPI-enabled display you like, which includes many LCD and OLED panels that are available to makers.

We selected an 8x8 LED matrix mainly because it provided a great way to display PWM increments with a sensible resolution, as well as handling multiple channels. In our configuration, the 8x8 display is broken up into four columns.

It can be easily adapted to break up into two columns if you’re only using two outputs, or 8 if you wanted to apply this theory to a larger output stage. Each “column” of LEDs provide 12.5% increments, between 0% duty cycle (off), and a 100% duty cycle (full power).

matrix breakup

Why don’t we have 1% resolution? It really comes down to practicality and ease of use. The controller absolutely has the capability to do so, and you can adjust the source code to have whatever resolution you like (you may even like less resolution, so there are 0% / 50% / 100% options, for instance). We decided that eight resolution settings was a good middle-ground between flexibility and having to push the up or down buttons 100-times to get from full power to off. 8-steps of brightness is more than enough to adjust for comfort in any conditions, without taking too many button presses to make it happen.

The Output Stage

Each PWM signal generated from the ATmega328p goes through its own output stage. It effectively consists of an IRF540N MOSFET to handle the heavy lifting (i.e. high current), and the rest is for protection and feedback.

The power output stages are fairly isolated, even though they share the same board. They can be considered akin to a PWM amplifier. They could just as easily be a module on their own on separate PCBs. You only need to provide a PWM signal to amplify, since it has onboard power and protection circuitry. But for the sake of convenience, we decided to keep it on the same board.

Each individual output stage, therefore, has a 5A fuse for short circuit/overload protection, as well as a beefy 6A flyback diode. Flyback diodes are really more useful with inductive loads, such as motors, to protect the circuit from damage. Even though this project has been designed with the intention of being a lighting controller, it can also suit other applications such as a motor speed or fan control if you have the need.

Power is brought in through a large terminal block and fed directly to the output terminal block via the MOSFET (and protection circuitry). If you only need two channels, you could easily omit the other two. It also means that if one channel suffers from overload or short circuit, it will protect itself (by blowing the fuse) while the other three channels continue running unaffected. This isolation can be very handy. Of course, each output stage features an LED indicator which can highlight power issues too, useful for debugging.

wired up

Build 1:

The Prototype

Parts Required:JaycarAltronicsCore Electronics
1 × Arduino UNO or EquivalentXC4410Z6280A000066
3 × 100Ω 1/4W Resistor*RR0548R7534-
3 × 10k 1/4W Resistor*RR0596R7582PRT-14491
3 × 0.1µF Ceramic Capacitors*RC5360R2865CE05188
3 × SPST Momentary Action SwitchesSP0603S1122-
1 × 8 x 8 LED Matrix DisplayXC4499Z6362-

Parts Required:

* Quantity shown, may be sold in packs. You’ll also need a breadboard and prototyping hardware.

Before we get into the full build, we want to understand the role that the buttons and display play in setting output levels. If you’d prefer to just skip ahead to the full project build, you’re welcome to do so. We’re mainly covering the functionality of a three-button sequential controller, in case that theory is useful for this or other projects. It’s not essential knowledge for project completion.

It’s also not practical to try and push 20A through a breadboard, so we’re not trying to tackle a full build at a prototype level. However, what is useful is seeing how an UNO drives our 8x8 LED matrix and configures each channel of output. What you do with these outputs can vary for all sorts of projects, but handling the control we’re after can take a little more effort than you might think.

Testing the 3-Button Control

We could have easily accommodated for four potentiometers but we wanted to consolidate the control hardware. We have drawn this down to simply three buttons, and let the code handle the rest.

Load up LightingController.ino (it’s the same sketch as the main build, so there is no need to change it). Assuming everything is connected correctly, you should see an initialisation sequence from the display before it springs to life.

The default initialisation mode has been set to 25%, 50%, 75%, 100% duty cycle from channel 1 to 4. These levels were selected to give a noticable display, but can be changed in the code to whatever you prefer.

The LED matrix gives you the necessary feedback, while the buttons provide minimum hardware input to get the job done. There’s effectively a channel selector with up and down buttons. The channel selector, as you may assume, selects which of the four channels you are currently modifying. The column is then set to flash so you can visually understand which output you’re controlling.

There’s actually a reasonable amount going on here to make this happen. The Arduino must retain the memory of which channel it should be adjusting, ensure that channel is flashing on the display so the user knows, and listen for adjustments up and down. This isn’t overly difficult when considering it at a high level but quickly becomes somewhat complex trying to handle everything in a suitable time.

full controller

Build 2:

The Full Controller

Parts Required:JaycarAltronicsCore Electronics
1 × 7805 Voltage RegulatorZV1505Z0505ADA2164
1 × ATmega328P MCU with 16MHz CrystalZZ8727Z5126 + V1289A000-ATMEGA328+UNO+CAPS
1 × ULN2803 Darlington Transistor Array IC-Z3010COM-00312
4 × IRF540 MOSFET TransistorsZT2466Z1537COM-10213
9 × 5mm Red LEDsZD0150Z0800COM-09590
5 × IN4004 DiodesZR1004Z0109CE05272
4 × 6A DiodesZR1024Z0121-
1 × 15V 5W Zener Diode – 1N5352ZR1450Z0415-
6 × 10Ω 1/4W Resistor*RR0524R7510-
3 × 100Ω 1/4W Resistor*RR0548R7534-
4 × 330Ω 1/4W Resistor*RR0560R7546PRT-14490
4 × 1k 1/4W Resistor*RR0572R7558PRT-14492
5 × 4.7k ¼W Resistor*RR0588R7574-
7 × 10k 1/4W Resistor*RR0596R7582PRT-14491
1 × 1MΩ 1/4W Resistor*RR0644R7630PRT-14494
2 x 22pF Ceramic Capacitors*RC5316R2814Included in MCU Pack
5 × 0.1µF Ceramic Capacitors*RC5360R2865CE05188
2 × 10µF/35V Electrolytic Capacitors*RE6075R5065CE05274
1 × 18 Pin IC SocketPI6458P0536-
2 × 14 Pin IC SocketsPI6454P0532-
4 × SPST Momentary Action SwitchesSP0603S1122-
1 × 8 x 8 LED Matrix DisplayXC4499Z6362-
8 × Fuse Clips*SZ2018S5983COM-09773
4 × 5A FusesSF2166S5744-
9 × 2 Way Screw TerminalsHM3172P2032B003-PCBT2
1 × 40 Pin Header StripHM3230P5390SSSTR108B3B
1 × TO-220 HeatsinkHH8502H0630PRT-00121

Parts Required:

* Quantity shown, may be sold in packs.


Our original prototype made with our Bantam Mini Desktop PCB Mill.

As with all our PCB-based projects, it is not essential to use the PCB; however, it makes construction easier (and safer when you plan to draw higher current). We prototyped this PCB using our Bantam Tools Desktop PCB Milling Machine, which just caters for this size PCB (the blanks are just a few millimetres larger than our PCB). Due to the complex nature of the PCB and the current requirements, we decided to run a professional prototype also. The professional prototype has solder masks and silkscreens. If you order a PCB through a manufacturer or purchase a kit from a retailer, you’ll likely have these features too.

The PCB itself is a double-sided design. There are only a handful of tracks on the top side, but they do avoid the need for some links to keep things cleaner.

Check your PCB for broken tracks, corrosion, or other manufacturing defects. Give it a good clean with some isopropyl alcohol or circuit board cleaner - a professional PCB will usually be fairly clean, but it can’t hurt. This will help reduce the incidence of dry joints and other problems. It may also avoid hours of debugging later!

Use a low temperature setting on the soldering iron, consistent with the type of solder you are using, to help avoid overheating the PCB and components.

As with all PCB builds, we follow a hardware, passives, semiconductors type construction pattern. This means you should install the two IC sockets (you should at minimum use a socket on the ATmega328p since no ISP access is provided), the fuse clips, pin headers, and screw-terminals first. Do not insert the fuses just yet - we’ll do that near the end.

You can also install S4 which is the reset switch for the ATmega328p, however, switches S1-S3 can be case-mounted with the display depending on your usage of this project. You may prefer to install pin headers into the pair of terminals provided next to the tactile switch pads. The pads for S1-3 are deliberately designed this way, to accommodate various use cases. If you’re using tactile switches, there’s no need for the headers either.

Next, install the resistors and capacitors. Take care with the orientation of the two electrolytic capacitors; their negative pin is on the outer edge of the PCB.

Moving on to the semiconductors, start with the diodes, taking care with the orientation as shown on the overlay. The large 6A diodes require some force to bend their thick pins appropriately - you may need a pair of pliers. Then install the LEDs. We have mounted the LEDs flush to the board, however, if you want them to protrude through your case, mount them at a suitable height above the PCB.

Keep in mind that we have included the LEDs mostly for debugging and fault finding purposes. When the system is running, the LED matrix provides all feedback, so we don’t see a huge need for them through the case.

Install the 16MHz crystal for the ATmega328p, then mount the 7805 voltage regulator, being mindful of its orientation. While the current requirements on the 5V side of the board may not seem significant, the power drop from 12V is substantial enough to generate a reasonable amount of heat.

It’s also providing power to the 8x8 matrix, which can require reasonable current when all 64 LEDs are illuminated. For this reason, we highly recommend installing it with a heatsink as shown in the images. This will ensure it doesn’t overheat under any normal operation.

You also have a choice to use heatsinks with the IRF540N MOSFETs, which drive the main output to your lights on each channel. These exceptionally useful MOSFETs are rated at 33A, and we’re capping them at 5A (for the sake of the PCB and back-EMF protections etc).

It’s safe to say we’re using them nowhere near their documented limits but we’ve still provided room for heatsink mounting, should you wish to do so. We tested this project with a full 20A load for a few days straight, with no heatsinks, and found no notable heat on the MOSFETs, however, the choice is yours if you prefer to add heatsinks.

With everything except the ICs and fuses installed into the PCB, it’s time to load the code onto your ATmega328P.

bottom layer


The cool thing about an ATmega328p is the ability to programme straight in the UNO then just transpose it into your circuit - though you may have to burn the bootloader if it’s a raw chip. Most vendors now provide an ATmega328p with the bootloader already loaded, and the required 16MHz crystal. Of course, if you have a dedicated programmer you can use that too!

Load the controller sketch LightingController.ino onto your chip. To confirm the code has loaded, you should open Serial Monitor and check for the default message:

LightingController.ino running...

Once loaded and confirmed, you can remove it from your programmer or UNO, and move on to the next steps to complete the build.


Before inserting the fuses and the IC / ATmega328p, we want to check we have power as expected on the IC sockets. It’s a very simple but powerful test to ensure we have power where we expect to. It helps rule out critical shorts and other hard faults with construction.

Apply 12V power to the P1 terminal block, which will provide power to the board, excluding the output stages. Test for 5V on the ATmega328p socket as shown. This is pin 20 and 22 for 5V and GND respectively.


Then check for approximately 12V on pin 9 and 10 of the ULN2803 as shown.


If the voltage readings were correct, you should be good to proceed! Switch off the power and insert the ATmega328p and ULN2803 into their sockets. Take care of their orientation!

lights on

Reapply power to the PCB and you should now see LEDs 6-9 illuminate at various levels. These are driven from the PWM outputs directly, so if they’re not illuminated, it may indicate an issue with the loading of the code onto the ATmega328p.

If you have connected the LED matrix already, it should also illuminate showing each channel at the default levels. If not, you can connect the LED matrix now (remove power then reapply once connected). You should notice markings on the PCB that relate to the ATmega pins.


Above is the breakdown of the SPI connection to the LED matrix. It also holds true for any other SPI-driven display you might like to attach.


Usually, with a large project of this kind, we like to give you a well-formed, fully finished case. However, this project is a little different. Since the PCB and display are independent, it’s entirely possible that the controller and the interface will be mounted separately. The tactile switches shown in the prototype can be changed out for standard momentary pushbutton switches and mounted into a case panel for example.

Some applications may require this project to be mounted behind a dashboard, inside a compartment or somewhere else where a case is totally pointless. For this reason, it was difficult to determine a “best use” case. However, to give you a springboard, we have indeed created a mounting case for the main PCB, which has a cut-out for cable entry, and mounting posts for the PCB itself. There's a generic version, and one for the 8x8 matrix. We suggest that this case design is expanded on, but is provided for you as a base.



Here are a few additional functions you could implement into this project without PCB-level modifications.

SINGLE-TOUCH ON / OFF: The current design doesn’t provide a method for switching the light immediately on or off. Since this can be easily done at a wire-level, we thought it wasn’t really necessary. You could enable a soft-touch on/off switch for each channel by connecting switches to the additional GPIO outputs provided on the 10-way header next to the ATmega828p. Some simple code additions could be made to include this functionality without too much hassle.

EEPROM STORAGE: When the primary power to the microcontroller is reset, so are the output settings. It wouldn’t take too much work to store the current values in the ATtiny’s EEPROM each time an output setting is updated. When the microcontroller boots, it checks its internal EEPROM for these values. If they’re found, it can use them. If not, the default boot sequence could be used.

LED MATRIX DIMMING: You could add additional functionality to dim the LED matrix. An easy way to do this is in software. Buttons could be used to change the value of x in lc.setIntensity(0, x).

Don’t forget that the additional GPIO pins are made available via a 10-way header, so you can expand things at a hardware level rather easily too.