Four-channel RGB driver board using a Teensy 3.1
(Last modified 24 Apr 2015)

I ran into a true Renaissance man last year at an art festival in Bellevue, WA.  His name is Sam Bates and he does (among MANY other things) staggeringly beautiful hand-carved sheet-glass sculptures (check this out!).  I spent an hour or so talking with Sam about his artwork.  Many of his displayed pieces used side-lighting with RGB LEDs.  The effect is gorgeous, but Sam is a true perfectionist and mentioned a couple of issues with the off-the-shelf DMX-512 controllers he was using.

The main problem was the step resolution of the color changes.  Often, when setting up the colors he wanted, Sam found that a one-step change in red, for instance, gave him too much red or not enough; he couldn't hit the exact shade he wanted.  This step-resolution issue also caused problems in fades to black.  At the end of the fade, there would be a small time where only one color would be visible; the other colors had switched off because there was no smaller step interval.  This showed as a jarring color blink just before black.

Another problem Sam faced dealt with color brightness at lower levels.  Basically, setting the color values in the software to half-scale did not produce half brightness; the brightness was only slightly reduced from full-on.  But the mid- to lower-brightness levels are where the subtle color play should happen, and the controller Sam was using simply wasn't giving him the control he wanted.

After hearing what he really wanted in the perfect RGB driver, I told him writing the proper firmware would be trivial.  He got really excited, we spent some time discussing design issues, and I headed back to my lab with a new project.

I needed to design a MOSFET-based RGB driver board capable of driving up to four strips of RGB LEDs.  The driver firmware needed to support:

I decided to go with a Teensy 3.1 as the micro.  The T31 is about $20, provides a bunch of memory, has a dozen hardware-based PWM channels, and I already have a lot of support code written for some of the subsystems I'll be using.

Note that the final project is not yet done; some of the user interface features in the list above still need to be added.  But the electronics and supporting firmware for many of the basic features are done and the project is ready to hook up to a glass piece.


Overview of the RGB LED driver board

Here you see the board with all 12 power MOSFETs installed.  The Teensy 3.1 board is the green daughter board in the lower left.  The blue and yellow wires from the white 2-pin connector next to the Teensy carry two PWM signals from solder pads on the underside of the Teensy board.


Closeup showing power supply mod

Here is the added Schottky diode, mounted next to the USB connector on the Teensy board.  The lower lead of the diode fits in an unpopulated via labeled VUSB on the underside of the Teensy board.  Make sure you install the diode with the cathode (banded) lead away from the board.


Teensy 3.1 PWM
The Teensy 3.1 board supports, with a bit of extra soldering, 12 hardware-generated PWM channels.  Ten of these channels appear on the standard 28 pins along the Teensy's two longer edges.  The last two PWM outputs appear on solder pads on the underside of the Teensy's PWB.  Here is a list of the available PWM outputs, taken from comments in my driver program:

Signal   FTM      Dev pin        Teensy pin
------   ---      -------        ----------
RED.0    0:0        PTC1            22
GRN.0    0:1        PTC2            23
BLU.0    0:2        PTC3             9

RED.1    0:3        PTC4            10
GRN.1    0:4        PTD4             6
BLU.1    0:5        PTD5            20

RED.2    0:6        PTD6            21

GRN.2    0:7        PTD7             5
BLU.2    1:0        PTA12            3

RED.3    1:1        PTA13            4

GRN.3    2:0        PTB18           32
BLU.3    2:1        PTB19           25

The Teensy 3.1 uses a Freescale MK20DX256 device, which contains three Flexible Timer Modules (FTMs) for hardware-generated PWM, among other timing capabilities.  The column above labeled FTM defines the FTM module and channel used to drive a color.  For example, the red drive for the first RGB strip (RED.0) comes from channel 0 of FTM module 0.  The entry in the Dev pin column shows this signal appears on the MK20 MCU as port C, bit 1, and on the T31 board as pin 22.

Note that the last two channels, for GRN.3 and BLU.3, appear on the T31 as pins 32 and 25.  These are actually solder pads on the underside of the T31 board.


The schematic
Here is the schematic for the first draft of this project (PDF).

I was not happy with the results I got when driving the MOSFETs directly from the T31's PWM outputs.  I am using a PWM frequency of over 4 kHz;.at that frequency, the gate capacitance of the MOSFETs caused issues with the drive signal quality.  I opted to run the PWM outputs into some dual-channel 4427 MOSFET drivers, then let the 4427s drive the MOSFETs.

This decision offers a side benefit.  If you will be driving less than 750 mA of LEDs per channel, you can replace each MOSFET with a ware between gate and source.  This effectively lets the 4427s drive the LEDs directly.  Unfortunately, I chose the 4427 because I had a bunch of them laying around.  The 4426 is a better choice, since it includes a built-in inversion between the drive signal and the output.  If the board is populated with 4427s and you want to drive the LEDs directly, you need to set a flag in the code to invert the PWM drive signal.  If the board is populated with 4426s, which have the same pinout, you should not need to invert the drive signal in the code.  Note that I haven't tested this yet...

The schematic includes several connectors not needed to meet Sam's requirements.  I added access to the I2C bus, an SPI channel, RTC battery, and some GPIO and A/D pins because they might come in handy for other projects.  All I need to support Sam's current requirements are SL4 (serial port), 12 VDC input, and the 4427s and MOSFETs.


Modify the Teensy 3.1 for external power
Note that you must modify the T31 board before you can drive it from external power.  The PJRC website includes details for modifying other Teensy boards; see here for example.  The mod I did to my T31 board is very similar.  You will need one 1N5817 Schottky diode with axial leads (not SMD).

First, isolate the USB 5 VDC from the rest of the circuit by cutting a trace on the underside of the board, between two large pads near the VUSB pin; see back of the Teensy 3.1 reference card for details.

Next, insert the anode lead (end WITHOUT the stripe) of the Schottky diode into the hole marked VUSB.  Note that the body of the diode should be on the top side of the board; refer to my photos.  Solder this lead in place, then trim the excess lead.

Finally, bend the cathode lead (end WITH the stripe) over so it makes good physical contact with the header pin sticking up through the hole marked Vin.  Trim this lead, then solder this lead to the pin.

This mod lets you run the RGB driver board from an external 12 VDC supply, but still connect to USB so you can push down new firmware.

Depending on the regulator you install, you may not need an extra Schottky diode between the power source and the 12 VDC input to the board.  I used a 7805-style regulator I pulled from a defunct board, and I noticed my RGB board was feeding 5 VDC back into my bench supply if I had the bench supply off and the T31 powered by USB.


Setting up the timers
There really isn't much to this code.  The hardest part is initializing the FTM timers to provide selected PWM pulses.  Here is the my initialization code::

/*
 *  PWMInit      configure PWM channels
 *
 *  This routine sets up the PWM channels used for RGB control.  The
 *  PWM channels are all configured for legacy mode (none of the second
 *  set of FTM registers [starting with FTMn_MODE] are used).
 *
 *  FTMn_CNT sets the PWM initial value; this must be 0 for edge-aligned
 *  PWM mode.  This value will be the same for all PWM channels.
 *
 *  FTMn_MOD sets the PWM modulus (overflow) value.  This valuw will
 *  be the same for all PWM channels.
 *
 *  Each channel has its own output-compare match value, in register
 *  FTMn_CxV.  The value written to this register must be
 *  FTMn_CNT >= value < FTMn_MOD.  The value written to this register
 *  sets the duty cycle of the PWM waveform.
 */
static void  PWMInit(void)
{
    SIM_SCGC6 |= SIM_SCGC6_FTM0_MASK;        // enable the FTM0 subsystem clock
    SIM_SCGC6 |= SIM_SCGC6_FTM1_MASK;        // enable the FTM1 subsystem clock
    SIM_SCGC3 |= SIM_SCGC3_FTM2_MASK;        // enable the FTM2 subsystem clock

    FTM0_SC = FTM_SC_CLKS(1) | FTM_SC_PS(2);    // use system clock, select divider
    FTM1_SC = FTM_SC_CLKS(1) | FTM_SC_PS(2);    // use system clock, select divider
    FTM2_SC = FTM_SC_CLKS(1) | FTM_SC_PS(2);    // use system clock, select divider

/*
 *  Configure each PWM driver line for active-low operation.
 *  Use edge-aligned PWM; clear output when initial value is loaded, set output
 *  on match.
 *
 *  Configure each port line for FTM, high drive strength, totem-pole output.
 */
    PORTC_PCR1 = PORT_PCR_MUX(0x4);        // red.0 is on PTC1 (Teensy pin 22); FTM0 ch 0 (alt = 4)
    FTM0_C0SC = FTM_CnSC_MSB_MASK + EDGE_MASK;    // Edge-aligned PWM, assign output on match

    PORTC_PCR2 = PORT_PCR_MUX(0x4);            // green.0 is on PTC2 (Teensy pin 23); FTM0 ch 1 (alt = 4)
    FTM0_C1SC = FTM_CnSC_MSB_MASK + EDGE_MASK;    // Edge-aligned PWM, assign output on match

    PORTC_PCR3 = PORT_PCR_MUX(0x4);            // blue.0 is on PTC3 (Teensy pin 9); FTM0 ch 2 (alt = 4)
    FTM0_C2SC = FTM_CnSC_MSB_MASK + EDGE_MASK;    // Edge-aligned PWM, assign output on match

    PORTC_PCR4 = PORT_PCR_MUX(0x4);            // red.1 is on PTC4 (Teensy pin 10); FTM0 ch 3 (alt = 4)
    FTM0_C3SC = FTM_CnSC_MSB_MASK + EDGE_MASK;    // Edge-aligned PWM, assign output on match

    PORTD_PCR4 = PORT_PCR_MUX(0x4);            // green.1 is on PTD4 (Teensy pin 6); FTM0 ch 4 (alt = 4)
    FTM0_C4SC = FTM_CnSC_MSB_MASK + EDGE_MASK;    // Edge-aligned PWM, assign output on match

    PORTD_PCR5 = PORT_PCR_MUX(0x4);            // blue.1 is on PTD5 (Teensy pin 20); FTM0 ch 5 (alt = 4)
    FTM0_C5SC = FTM_CnSC_MSB_MASK + EDGE_MASK;    // Edge-aligned PWM, assign output on match

    PORTD_PCR6 = PORT_PCR_MUX(0x4);            // red.2 is on PTD6 (Teensy pin 21); FTM0 ch 6 (alt = 4)
    FTM0_C6SC = FTM_CnSC_MSB_MASK + EDGE_MASK;    // Edge-aligned PWM, assign output on match

    PORTD_PCR7 = PORT_PCR_MUX(0x4);            // green.2 is on PTD7 (Teensy pin 5); FTM0 ch 4 (alt = 4)
    FTM0_C7SC = FTM_CnSC_MSB_MASK + EDGE_MASK;    // Edge-aligned PWM, assign output on match

    PORTA_PCR12 = PORT_PCR_MUX(0x3);        // blue.2 is on PTA12 (Teensy pin 3); FTM1 ch 0 (alt = 3)
    FTM1_C0SC = FTM_CnSC_MSB_MASK + EDGE_MASK;    // Edge-aligned PWM, assign output on match

    PORTA_PCR13 = PORT_PCR_MUX(0x3);            // red.3 is on PTA13 (Teensy pin 4); FTM1 ch 1 (alt = 3)
    FTM1_C1SC = FTM_CnSC_MSB_MASK + EDGE_MASK;    // Edge-aligned PWM, assign output on match

    PORTB_PCR18 = PORT_PCR_MUX(0x3);            // green.3 is on PTB18 (Teensy pin 32); FTM2 ch 0 (alt = 3)
    FTM2_C0SC = FTM_CnSC_MSB_MASK + EDGE_MASK;    // Edge-aligned PWM, assign output on match

    PORTB_PCR19 = PORT_PCR_MUX(0x3);        // blue.3 is on PTB19 (Teensy pin 25); FTM2 ch 1 (alt = 3)
    FTM2_C1SC = FTM_CnSC_MSB_MASK + EDGE_MASK;    // Edge-aligned PWM, assign output on match

    FTM0_CNTIN = 0;                            // initial value of PWM counter range is always 0
    FTM0_CNT = 0;                            // any write to CNT copies CNTIN to the counter;
                                            // always init CNT before initing MOD!
    FTM0_MOD = MAX_PWM_COUNT;                // upper limit of PWM counter range is always max resolution

    FTM1_CNTIN = 0;                            // initial value of PWM counter range is always 0
    FTM1_CNT = 0;                            // any write to CNT copies CNTIN to the counter;
                                            // always init CNT before initing MOD!
    FTM1_MOD = MAX_PWM_COUNT;                // upper limit of PWM counter range is always max resolution

    FTM2_CNTIN = 0;                            // initial value of PWM counter range is always 0
    FTM2_CNT = 0;                            // any write to CNT copies CNTIN to the counter;
                                            // always init CNT before initing MOD!
    FTM2_MOD = MAX_PWM_COUNT;                // upper limit of PWM counter range is always max resolution
}


The code is straightforward.  MAX_PWM_COUNT is the upper limit to the FTM channel and corresponds to 100% on.  This value is defned elsewhere in my system and is currently 4000.

EDGE_MASK is a named literal that defines the value written to a FTM CnSC register, and is an OR-mask of two bits ELSnB:ELSnA.  Because I've configured the FTM channels for edge-aligned PWM, I can use either of two values for EDGE_MASK.  A binary value of 0b10 defines the channel as active-high; pulses start out high at timer value 0, then go low when the timer value matches the count value.  A binary value of 0b01 defines the channel as active-low; pulses start out low at timer value 0, then go high when the timer value matches the count value.

To change the duty cycle of a PWM channel, and hence the brightness of the associated LEDs, simply write a new value to the FTM channel's CnV register.  For example, the red LEDs in the second RGB strip are controlled by FTM0_C3V.  Writing a value between 0 and MAX_PWM_COUNT to FTM0_C3V will immediately change the duty cycle of that set of LEDs.

This is an admittedly light overview of the FTM subsystem.  Download a copy of the Freescale K20 Reference Manual and dig through the section on the Flex Timer Module, using my code as a reference.  Hopefully, that will clear up how to use the FTM for PWM control.


The software
I've included the C source for this program in this zip file.  Note that you will NOT be able to rebuild this code and try it out, because it requires header files and object modules that are common to my other programs.  However, you can load the included binary file to your T31 and play around with changing the colors of the LEDs.  You will need to provide a serial connection (115,200 baud, 8N1) to your PC from the UART connector at SL4.  Since these signals are logic-level, you will need to provide suitable level-shifting if you want to use a true RS-232 connection.

The interface is very basic.  It uses ANSI control sequences to clear the screen and move a large highlight block around.  The highlight block selects one RGB strip for each of 20 possible color scenes.  Within the highlighted colors, you can press keys to change the color intensity:

q    increments red value by 1
Q    increments red value by 20
a    decrements red value by 1
A    decrements red value by 20

w, W, s, and S perform the same function for the green value

e, E, d, and D perform the same function for the blue value

Use the keyboard's arrow keys to move the highlight block around on the screen.  For this interface to work, you need to use a VT-100 or equivalent ANSI terminal emulator, such as TeraTerm.

Note that there is no way (yet) to save any of the changed colors.  There is support for a kind of psuedo-EEPROM on the MK20 device, but I have not added that code yet.

I have added code to try and correct for your eyes' non-linearity versus PWM duty cycle.  The adjustment is a simple one, but I like the results.  If you ask for a PWM value of 2000, which should be 50% brightness, the LEDs appear to be half of full-brightness.  Check the code in the ScaleBrightness() function for details.


Making a board
This is the first project I have taken to a PWB in a long time.  I use the free Eagle board package from CADSoft USA and the difficulty of converting the Eagle .brd file into a full set of Gerbers for use by a board house has been more trouble than it was worth.  But Sam needed a board to play with and I needed a board for further code development, so I had no choice.

I decided to give OSH Park a try and was very impressed with the results, pricing, and response.  This was my first design to include soldermask and silk-screen and I love the results.  I got three copies of the board (3.5 x 4 inches) for about $60 with free shipping.  The feature I like best about OSH Park, however, is their ability to accept an Eagle .brd file as input.  No need to create Gerbers, just load the .brd file onto their website, visually check the resulting layers, and place the order.  Wonderful!  I will definitely be using OSH Park for my future projects.


Summary
I am placing the schematic and the code in the above zip file into the public domain.  I want others to take this project and build on it.  This could easily be turned into a marketable product for large-scale RGB LED lighting control.

Sam is ready to do some commercial pieces using this board, but my version of the board is far from a commercial product.  I write firmware, I'm not an EE.  I haven't addressed any of the many concerns or requirements that a commercial product must meet before shipping.  I have done no work on flammability, current limits, EMI, vibration, power supply quality, or thermal.  I have no experience designing a board for manufacturability or testing a product for regulatory approval.  I will leave that to others more qualified than I.

You are welcome to use this project as a starting point, and you do so at your own risk and with full responsibility for the results.  The design you see here is at best a proof of concept and far from ready for commercial use, but it's got a lot of potential.



Home