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:
- Much finer resolution than the 256 steps Sam was currently using
- Better brightness linearity
- Multiple scenes (a scene is a group of RGB settings for each of
the four strips)
- Fades and switches between scenes
- Simple user interface for adjusting any single color and for
setting up scenes
- Relatively cheap
- Built around easy to get parts
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.
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.
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