Go Down

Topic: Expanding PWM (Read 7737 times) previous topic - next topic


Hi Everyone

I've seen forum posts about multiplexers/demultiplexers that can be used to expand the outputs of an Arduino board so as to, for example, control a greater number of LEDs.  The examples I've seen for LEDs, however, seem to be only for turning LEDs on and off.  I'm wondering if there's a way to control a large number LEDs such that they are dimmable (expanding the Arduino's PWM output somehow).



Mar 14, 2007, 02:35 am Last Edit: Mar 14, 2007, 02:35 am by jims Reason: 1
I have TLC5940NT chips on order, about $4 each from digikey. These are 16 channel LED drivers with 4096 shades of PWM gray on each. The arduino can drive them with just a couple digital lines and a clock. You can daisy chain them if you need more than one, so you won't take more arduino lines. There is nifty logic in there for calibrating your LEDs, but I won't have the voltage to program that in the circuit.

There are a number of similar products, I chose by what I could get in a DIP package.


Mar 14, 2007, 06:17 pm Last Edit: Mar 14, 2007, 06:29 pm by sdbrown Reason: 1
Have you thought about how to program the serial data transfer for that chip?  The GS packet format makes enough sense, but how fast can the clock change and how tolerant is the TLC5940 of clock variation?  I was interested in using that chip (very interested) but haven't used it because I have no idea how to program the ATMega168 to interface with it.

How I interpret having to code the ATMega168 (for simplicity, just turning all the bits on for a "full on" to all 16 outputs):

Code: [Select]

main loop {
 digitalWrite( toTLCClock, 0 );
 digitalWrite( toTLCIn, 0 );

 for ( x = 0; x <= 191; x++) {
   digitalWrite( toTLCIn, 1 );
   digitalWrite( toTLCClock, 1);
   delayMicroseconds( 3 );
   digitalWrite( toTLCClock, 0);

 digitalWrite ( toTLCLatch, 1 );
 delayMicroseconds( 3 );
 digitalWrite ( toTLCLatch, 0 );
 digitalWrite ( toTLCClock, 1 );
 delayMicroseconds( 3 );
 digitalWrite ( toTLCClock, 0 );

Does that vague pseudocode look right?  I have no idea if the ATMega chip is fast enough to blow out the ~10 nanosecond delays required by the TLC chip, so I put in some short microsecond delays.  Obviously I would base the inner for loop on some array with the output level data in it but this is just an idea for the serial format.


Thanks, I'd be keenly interested in seeing documentation if someone is able to get something like this working.  My personal goal in this would be to have computer controlled dimmable LEDs (by max-msp or similar) for a light/sound installation.


That code by sdbrown looks like what I am planning. I am not anticipating a need for delays, the arduino should be well under the maximum clock rates of the LED driver. I also don't anticipate any kind of jitter issue since the bit clocking is synchronous. I could be surprised, but that is what I expect now.


I'm trying to do the same thing, but was slightly off track for a while.  And now, since the led driver listed above isn't available on digikey anymore, I'm curious why I couldn't daisy chain a couple of ATMEGA168 chips together, utilizin their PWM outputs.  Is this crazy?  Is there any other way to do this without the led driver?


TI has a free sample service... hunt around their website. you usually have to be logged in to find it.

There are some really nice output expanders from Maxim-IC, like the MAX7213, but you have to deal with their teensy-tiny QFN or THIN packages.  



And now, since the led driver listed above isn't available on digikey anymore, I'm curious why I couldn't daisy chain a couple of ATMEGA168 chips together, utilizin their PWM outputs.  Is this crazy?

No, it's not crazy at all.  In fact, I just got done doing this very thing: I have one ATMega168 as the server and 8 ATMega168s as clients.  The server sends data out the serial TX pin which is received by the client chips and the client chips then make adjustments to their PWM outputs, which are connected to LEDs.  The difference is primarily one of price: the cost of additional outputs using the TLC chip is less than half the cost of using the ATMega168 chip.  On the other hand, by using the ATMega168 chips as clients you can add all sorts of extra functionality or just offload some processing from the server chip (which is what I've done).


sdbrown, I'm just curious what other  components you needed along with the additional ATMEGA168s.  Did you have to build a whole other Arduino board or were the chips standalone?




Sorry for the double post, but I've just found that the TLC5940NT is available again, so I'm going to give it a go.  Can anyone give a more complete coding example on how to control PWM on multiple LEDs with this.  I'm not real familiar with serial communication, etc. so even a generic example of how to send the GS info would be extremely helpful.




I should have parts in a couple days, as soon as digikey notices my backorder and fills it from the 153 they have in stock, I'll code up a library and post it here as soon as they come in.


I have placed an example of driving this part in the Playground, http://www.arduino.cc/playground/Learning/TLC5940. It doesn't rise to the level of library code, but it does get all the bits in and out.


Thanks to insights gained from jimS and Amp and others in the forum, I've developed this code for the TLC5490, just for fading across a row of LEDs smoothly.

To fully grok what's going on here, get the data sheet:

The biggest challenge was avoiding flicker, since the usual Arduino digitalWrite commands are a little too slow. Amp's "4MHz output from ports" post helped reveal to me the PORTB call, which is easy to use and much faster. Note I only needed to do this when pulsing the 5940's greyscale clock.

Have fun...

/*Ti 5940 16-port LED driver
= overlapped fade across 16 LEDs at a low background level
* Peter Mackey  June 2007  Pratt Digital Arts  pmackey@pratt.edu
* smooth flickerless fading (thanks to Amp on the Arduino forum)
* additional logic from David Cuartielles's & Marcus Hannerstig's LEDdriver demo

//using the pin codes on the TLC5940NT to name the Arduino ports
#define VPRG 2 //"chip pin 27 to Arduino pin 2"
#define SIN 3
#define SCLK 7
#define XLAT 4
#define BLANK 5
#define DCPRG 6
#define GSCLK 8 //note: but using PORTB method

#define FADEMIN 100 //lowest fade level LEDs will reach (min would be 0, max 4095)
#define FADEINCR 64   //determines how many steps it takes to run the desired range (lower=longer)

int fadeLevel[] = {
 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //stores a level for each of 16 ports
int faderNdx = 0;  //counter used in this fading sequence

int fadeState[] = {
 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //stores the direction of fading for each port 1,0,-1
//start with first port

int word[] = {
 0,0,0,0,0,0,0,0,0,0,0,0}; //temp storage for reversing bits in a word (for greyscale setting)

void setup() {
 pinMode(VPRG, OUTPUT);    
 pinMode(SIN, OUTPUT);    
 pinMode(SCLK, OUTPUT);
 pinMode(XLAT, OUTPUT);    
 pinMode(BLANK, OUTPUT);    
 pinMode(DCPRG, OUTPUT);    
 pinMode(GSCLK, OUTPUT);       //could also set DDRB directly

preset();    //input "Dot Correction" data

void loop () {

void incrementFades() {
 //uses two 16 item arrays
 //fadeState stores -1,0,or1 to specify direction of fade
 //fadeLevel stores current value of fade
 //too long to post, sorry...

//=======5940 control
void setGreys()  {
 //data for each port (12 bit word * 16 ports =192 bits in this loop)...
 //read the fadeLevel array
 for (int i=15; i>=0; i--) { // ports, count DOWN
   int datb = fadeLevel;    

   //load fade level bits into the temp array BACKWARDS
   for (int j=11; j>=0; j--) {
     word[j]=(datb & 1); //& bitwise AND      
     datb >>= 1;        //shift right and assign
     // (maybe there's a slicker way to do this!? but this works...)
   //send the data to the 5940
   for (int j=0; j<12; j++) {
 digitalWrite(XLAT, HIGH);
 digitalWrite(XLAT, LOW);

void feedPorts() {
 //The actual sequencing of the PWM data into the LEDs, must do constantly...
 digitalWrite(BLANK, HIGH);    
 digitalWrite(BLANK, LOW);      //=all outputs ON, start PWM cycle

 for (int i=0; i<4096; i++) {

////DOT CORRECTION...do once
void preset() {
 //Input "DotCorrex" Data
 //16 outputs, 64 posssible levels of adjustment, 6 bits/chan = 96 bits total
 //[use if any LEDs in array are physically too bright]

 digitalWrite(DCPRG, HIGH);      //leaving it H is my arbitrary choice (="write to register not EEPROM")  
 digitalWrite(VPRG, HIGH);     //=inputting data into dot correx register

 digitalWrite(BLANK, HIGH);      //=all outputs off, when this goes high it resets the greyscale counter
 digitalWrite(SIN, LOW);       //to start dot correction
 digitalWrite(XLAT, LOW);      

 //begin loading in the dot correx data, most significant bit first...
 //but here we are not correcting anything, so LSB is going first!
 for (int i=0; i<16; i++) { //16 ports
   for (int j=0; j<6; j++) { //6 bits of data for each port
     digitalWrite(SIN,      HIGH);      //for now, 111111 for everybody
     digitalWrite(SIN,      LOW);


 digitalWrite(XLAT, HIGH);   //latch the dot data into the dot correx register
 digitalWrite(XLAT, LOW);
 digitalWrite(VPRG, LOW);    //entering greyscale mode

 for (int i=0; i<16; i++) { //16 ports
   int datb = 4095;        //using same fade level for all ports this first time

   for (int j=0; j<12; j++) { //data for each port, all the same value to start
     digitalWrite(SIN,      datb & 01);      
 digitalWrite(XLAT, HIGH);  //latch the greyscale data
 digitalWrite(XLAT, LOW);
 pulseSCLK();               //193rd clock pulse only need to do the FIRST time after dot correx

 digitalWrite(BLANK, LOW);  //=all outputs ON, start PWM cycle... moved here

//SCLK used in dot correx and greyscale setting
void pulseSCLK() {
 digitalWrite(SCLK, HIGH);
 digitalWrite(SCLK, LOW);

void pulseGSCLK() {
 //ultra fast pulse trick, using digitalWrite caused flickering
 PORTB=0x01; //bring PORTB0 high (pin 8), other ports go low [0x01 does only pin 8, 0x21 also lifts pin 13]
 //16nanosecs is the min pulse width for the 5940, but no pause seems needed here
 PORTB=0x20;  //keep pin13 high for oard LED [0x00 would be all low]


I've used the 5940 for LED signs and it works great but the datasheet and features you have to deal with are bit daunting.

I wanted to point out the Arduino is fast enough to PWM all of it's pins smoothly, so depending on how many LED's you need dimming for this might be an alternative.  I didn't throw in the Analog pins but there's nothing that would prevent it. You need to do some bitshifting and bitmath though to maintain the speed.

See this approach.




How possible would it be to use 2 (or 3?) 5940s multiplexed to drive an 8x8 RGB LED matrix (like the one in sparkfun   http://www.sparkfun.com/commerce/product_info.php?products_id=683 ) ?

would it be possible to get a good refresh rate for a non-flicker image?


Go Up

Please enter a valid email to subscribe

Confirm your email address

We need to confirm your email address.
To complete the subscription, please click the link in the email we just sent you.

Thank you for subscribing!

via Egeo 16
Torino, 10131