This note describes an LED driver I designed for a lighting project. I decided on a custom solution because I couldn’t find any suitable boards available to buy at a reasonable price. The intended application (which I will describe in another note) requires 12 high-power (~1W) LEDs to be driven individually a 3 by 3 grid measuring approximately 1 metre square, with each of the nine cell containing a red, green, blue and white LED.

I embarked on this project without any experience of designing PCBs, and with relatively little knowledge of electronics, so it was a steep but fantastic learning experience. I wanted to record some of the details of this project for others who may find them useful, as I did with some of the resources I’ve linked to at the end. All of the PCB design files and processor firmware are available on GitHub.

Design

The board is designed around the following main IC components:

Other features:

I designed the PCB using the excellent KiCAD and had it manufactured very cheaply by Seeed Studio in Shenzhen, China.

Here’s the schematic:

Parts list

Quantity Package Description
3 TO-263 CAT4101 LED drivers 5.5V
1 DIP-8 PIC12F1572 microcontroller
1 DIP-8 MAX485 serial interface
3 0805 Green LEDs
3 0805 Resistor 10K
3 0805 Resistor 1.4K
3 0805 Resistor 510
5 0805 Capacitor 0.1 uF
3 SOT-23 2N7002 N-channel MOSFET 300 mA
4 - 2 way 5mm pitch terminal blocks
1 - 3 way 5mm pitch terminal blocks
12 - 2.54mm pitch pin headers
3 - 2.54mm pin header jumper caps

The CAT4101 sense resistors are chosen to give a constant current of 300 mA.

For reference, I have assumed the following parameters of the LEDs I used (you should however check the datasheet for a particular LED):

Colour Typical forward voltage
(@ 350 mA)
Part Reference
Red 2.4 3W RGB module Datasheet (PDF)
Green 3.4 3W RGB module Datasheet (PDF)
Blue 3.5 3W RGB module Datasheet (PDF)
White 2.8-3.4 1W Ice White LED (Bridgelux 9000-15000k) Future Eden

Programming

The intention of the PIC microcontrollers is to react to a simple set of commands sent via the serial interface. In deployment, an array of these driver boards would be controlled by another processor broadcasting on the RS485 bus. I used a Raspberry Pi with an RS485 shield to do this.

At a minimum, the PICs need to set the intensity of each LED they control, which is the current operation of the firmware. They could however be triggered to perform more complex modulations. This would reduce the data transmission requirements on the RS485 bus, potentially improving the quality of animations produced by an array.

The PIC microcontrollers are programmed in C, which I did using Microchip’s XC compiler and MPLab IDE software. In order that each board can uniquely identify it’s control data, they are compiled with a unique ID. With a deployment of 12 boards, firmware updates are a little arduous, particularly since the two jumpers need to be removed as well.

I found that an efficient communication protocol between an array of boards and the main controller (Raspberry Pi) is a sequence of bytes with the first uniquely determining the header and the following 36 determining the intensity of each of the individual LEDs. Each board uses its ID to choose three values in the payload. At 115,200 bps, this in theory allows up to 1,107 commands to be sent per second. Note that the boards do not send an acknowledgement, since this significantly reduces the throughput. In Python a packet can be sent with (snippet from here):

def set_colour(payload):
    assert len(payload) == (3*12)
    usart.write(bytearray([chr(255)]+payload))
    usart.flush()

The PICs receive UART data and set the output PWMs thus (snippet from here):

...
// Reset if we see the start of packet marker.
if (RCREGbits.RCREG == START_PACKET) {
  uart_count = 0;
} else {
  // Packet payload.
  uart_data[uart_count++] = RCREGbits.RCREG;
  // When we've received the payload, update PWMs and setup for next packet.
  if (uart_count == PAYLOAD_SIZE) {
    // Set the duty cycles, scale an 8-bit range into 16 bits.
    PWM1DC = uart_data[DRIVER_OFFSET+0] * 256;
    PWM2DC = uart_data[DRIVER_OFFSET+1] * 256;
    PWM3DC = uart_data[DRIVER_OFFSET+2] * 256;
    // Reload the PWMs.
    PWM1LD = 1;
    PWM2LD = 1;
    PWM3LD = 1;
    uart_count = 0;
  }
}
...

Pictures

Improvements

References