Introduction: Creative Diorama Lighting With the Arduino and TLC5940
I became interested in model railroading a few years ago. It's a hobby that requires skills in precision painting, model building, scenery design, wood working, electrical engineering and about a dozen other skills that I’m forgetting right now. It is an excellent creative outlet.
Realism can be a part of the hobby, like laying out a train line complete with all the scenery with historical accuracy. You can also choose to just build a fantasy land complete with cities, realistic grass, unicorns and Godzilla-like monsters fighting Transformers for rule over the land. Regardless, you will be assembling and painting a variety of buildings to enhance the world you are creating.
I’m writing about adding some realism to the town: lighting. You can light a building by just plugging in a bunch of little model light bulbs and light everything in a uniform way. Towns don’t work like that though. People move from room to room, lights go on and off, street lights flicker, restaurants change the lighting through the night.
We’re going to look at how to use an Arduino, a few TLC5940's and diorama skills to script the LEDs of a part of a town to create realistic lighting.
Step 1: Tools
This looks like a long and expensive list of tools. You may have many of these tools already or those that can do the job just as easily.
Tools you'll need:
- A hammar
- A small nail
- A multimeter
- Needle nose pliers
- Calipers like Neiko 01407A Stanless Steel 6-Inch Digital Caliper with Extra-Large LCD Screen and Instant SAE-Metric Conversion or be really good with a ruler.
- A model file set, such as Tamiya Basic File Set or a tool to make very fine notches. Wire cutters work but easier to make a mistake. Modelers probably already have something like this.
- Wire strippers. I use these: Irwin Industrial Tools 2078300 8-Inch Self-Adjusting Wire Stripper with ProTouch Grips Note: The description says it is good to 24 AWG, but it works well down to 28 AWG stranded.
- Wire cutters similar to Xuron 170-II Micro-Shear Flush Cutter as you will want something with a small nose.
- Soldering iron with temperature control
- Ventilation
- Table top vices like these:
- PanaVise Model 201 "Junior" Miniature Vise
- PanaVise 301 Standard PanaVise
- PanaVise 209 Vacuum Base Pv Jr. Note: I regret this and would rather have 2 of the model 201's. The vacuum base is not great on wood and it doesn't have the weight to keep it steady.
Optional:
- A decade box when you need that one odd resistor that you don't have on hand: Elenco 1% 1 Watt Resistor Substitution Box
- An oscilloscope.
- A circuit board holder such as: PanaVise 315 Circuit Board Holder You can remove the vice in the 301 and replace it with the board holder. You need a vice to crimp a few ribbon cables.
Helping hands or something similar.
Bamboo skewers.
Step 2: Parts
Some of these parts are optional and you have alternatives. I used three retailers: Amazon, Mouser and Jameco. All three delivered within 2 days thanks to Amazon Prime, Priority Mail and really fast customer fulfillment.
The list is ordered by who fullfilled my order:
Amazon:
- microtivity IL336 4.8mm Wide Angle White Straw Hat LED w/ Resistors (Pack of 100)
- microtivity IL614 5mm Diffused RGB Controllable LED, Common Anode (Kit, Pack of 30)
- microtivity IM414 Double-sided Prototyping Board (4x6cm, Pack of 5)
- POW3U PowerBoard-3U with Power Rails, 1 Sided PCB, 3.94 x 6.30 in (100 x 160 mm)
- A lot of male to male, male to female and female to female jumper wires. The Dupont connectors were the best since they are square and can mash up against each other.
A note on Microtivity: Every part worked fine. The straw hats have a very wide angle and are great for building lighting. The RGB and white both draw 20mA according to their data sheets and that made it much easier to share the TLC5940's with both types. My regret was that I went with the prototyping board instead of a solder breadboard or strip board because of the time spent soldering bridges and buses. You will improve your soldering skills though.
Mouser:
- 2 or more TLC5940NT
- 10 10uF 25v radial caps cut tape
- 10 10K ohm 5% 1/4w resistor PN 660-CF1/4CT52R103J cut tape
- 10 2k OHM 5% 1/4w resistor PN 660-CFS1/4CT52r2027 cut tape
Note: The 2K resistor is based on a formula in the TLC5940NT's data sheet
Jameco:
- 10 .1uF 25v cap 20% PN 151116
- Several 16 pin female IDC sockets PN 119467.
- Several 16 pin male header w/shroud straight PN 68180 mates with the above 16 pin female connector.
1 Cable Ribbon 16 Conductor Rainbow 28AWG 10 Feet Flat PN 28RC16-10VP
A large solderless breadboard such as PN WBU-208-R
A couple small breadboards such as PN WBU-301-R with power bus
Several Connector Unshrouded Header 40 Position 2.54mm Solder Straight Thru-Hole PN 7000-1X40SG-R
Part 7 is just a 40 pin single row snappable header. You will need 64 pins per node board, which I'll describe in future step. You'll need two 40 pin headers per node board. Also, it is easy to miscount the number of pins you need when snapping them off so get a couple of extra headers for mistakes.
Note: Some of the parts above say "have 10 of this or that." Some vendors have minimum orders or parts like resistors and capacitors.
Other stuff:
- Thick white paper or cardboard that can hold a little weight.
- Common white glue
- Evergreen Scale Models: Channel 5/16" (7.9mm) PN 268 or something similar.
- Various other parts to make your diorama look realistic.
- Model buildings or similar structures, like lego.
- Foam sheets, see picture. You can find them in most kid's craft stores.
- An Arduino Uno R3
Step 3: Overview of Lighting the Buildings
The above ground setup will be just two buildings to demonstrate building lighting.
Below ground is the controller board. A bridge will connect the controller board to each node via one or more ribbon cables. The nodes connect to the LEDs in the buildings.
The LEDs are strung together on the channels to create a LED strip. Make a strip by measuring the windows you want to light. Draw lines on the strip to show room dividers and LED locations. Use the hammer and nail to punch holes for the LED leads. Create notches in the strip for the leads to bend into (see picture) using a file or something similar. Having LEDs off center from a window gives the effect of normal lighting.
Test that the strip works before proceeding.
Each LED on the strip is covered with a separate paper box. Use thick white paper or cardboard to make the box. Use the foam to cover the box to make it mostly light tight. Make sure to connect the LEDs to a Node before glueing the box to the windows. Test again that the LEDs work.
Note: Light bleed can be a challenge. Your wrapper on the LEDs should be as light tight as you can make them so they only light the window. Also, the light can bleed through other windows too.
Model makers use different manufacturing methods that impact lighting. First, some walls are very thin and the LED will bleed through the wall itself. Paint around the windows or better still, glue some white or black foam around the windows. Do not use other colored foam as it will change the color of the light.
Some manufacturers make the models with walls that are not square. Foam and sandpaper works really well in these corners.
I use a bright flashlight to see all the places where light bleeds from my corners and other joints. It is easy to patch those locations using the foam or other materials.
Step 4: Overview of the Controller Code
The code manages LED clusters of either white or RGB. The bar or nightclub demonstrates a mix of both.
The RGB LEDs follow a sine wave. The code originally calculated the value for the "lead" LED and then iterated it down to the other RGBs. The calculations were amazingly slow. Improving the speed required pre-calculating the entire sine wave and statically storing it in an array. The code runs significantly faster as a result and allows us to whip through all the LEDs.
The LEDs are clustered in the code. The RGBs are one cluster. One set of windows are another and so on. Each LED has start and end time fields that indicate how many milliseconds to wait before performing an action such as changing the color, turn on or turn off.
The loop() function will iterate through each LED cluster and you may notice slight delays as the code has to iterate through all the LEDs and then perform an update to set their state. A small delay is one of those bugs that's a feature. It adds some variability to the duration of the LEDs, making the lighting a little more realistic.
Note that all times are hard-coded. You could change this so the lights run for a random period.
Please be careful with the white LEDs and the brightness settings you choose. The TLC5940 has steps that go from 0 (off) to 4096 (leaves spots in your eyes). I have lowered the default LED brightness to something very low to see the LED work after foolishly setting the LEDs to their maximum and having a hard time seeing anything but spots afterwards. You will have to set the LEDs to a higher value once installed in the model because windows and other items may diffuse or block the light.
Step 5: Write the Code
First, you need the TLC5940 Aduino Library. The installation directions are out of date. Download the library and unzip into a temporary directory. Then open the Arduino editor and go to Sketch ->Import Library and add the library. The editor will do the rest.
Edit the tlc_config.h file to modify the number of TLC5940's you are using. This project runs thee TLCs so the line reads
#define NUM_TLCS 3
The code is pretty straightforward from here and is commented. I have some comments at the end too.
LEDController.ino
#include "Arduino.h" #include <Tlc5940.h> #include <tlc_config.h> #include "LedController.h" // Root List for all LEDs List *lightList = NULL; // various values for iterating through the RGBs int rgbIndex = 0; int rgbLastStart = 0; boolean firstRGBIndex; int maxRGB = 252; // Handy for steping through all the LEDs // Each LED will flash, which helps find missing or incorrect connections void diag() { for(uint8_t x= 0; x < 40; x++) { if ( x > 0) { Tlc.set(x-1,0); } Tlc.set(x,1000); while(Tlc.update()); delay(500); } } /* Pause for 5 seconds so that you can pull up any diagnostic you may need. Initialize the TLC chain. Create the complete light list */ void setup() { delay(5000); Tlc.init(); Tlc.clear(); lightList = createList(); addNode(lightList, createRGBCluster1()); addNode(lightList, createLEDCluster1a()); addNode(lightList, createLEDCluster2()); } /* Increment the RGB value so it slowly moves through the list of color transitions. Keep the current time so you know when to turn on and off lights. Run through the list. */ void loop() { //diag(); rgbIndex = rgbLastStart + 1; firstRGBIndex = true; long time = millis(); iterateList(time,lightList); /* The update finishes asynchronously on the TLC's. You must wait till everything updates or your lights will display incorrect values. */ while(Tlc.update()); } /* Single link list. Most of the following functions create a node of some type and attach it to the end of a list. This system creates lists of lists for each LED array. This exposed some memory challenges as the list is using some statically allocated values and shares some of those values. Pointers would have been a wiser choice. */ List* createList(void) { List *list = (List*)malloc(sizeof(List)); list->head = NULL; list->tail = NULL; return list; } List* addNode(List* list, Node* node) { if (list != NULL && node != NULL) { if (list->head == NULL) { list->head = node; list->tail = list->head; } else { list->tail->next = node; list->tail = node; } } } Node* createNode(NodeType type, uint8_t pin) { Node *result = (Node*)malloc(sizeof(Node)); result->next = NULL; result->type = type; switch(type) { case LIST: result->value = createList(); break; case LED_RGB: result->value = createRGB(pin); break; case LED_NORMAL: result->value = createLED(pin); break; } return result; } Node* createRGBNode(List* list, uint8_t pin, Runtimes runtimes, RGB *useSettings) { Node *results = createNode(LED_RGB, pin); RGB* rgb = (RGB*)results->value; configureRGB(rgb, runtimes, useSettings); addNode(list, results); return results; } void configureRGB(RGB* rgb, Runtimes runtimes, RGB *useSettings) { if ( useSettings != NULL) { rgb->useSettings = useSettings; rgb->color = useSettings->color; rgb->runtimes = useSettings->runtimes; } else { rgb->runtimes = runtimes; } } RGB* createRGB(uint8_t pin) { RGB *result = (RGB*)malloc(sizeof(RGB)); result->pin = pin; result->color.r = 0; result->color.g = 0; result->color.b = 0; result->useSettings = NULL; return result; } Node* createLEDNode(List* list, uint8_t pin, Runtimes runtimes, uint8_t level) { Node *results = createNode(LED_NORMAL, pin); LED* led = (LED*)results->value; led->level = level; led->runtimes = runtimes; addNode(list, results); return results; } LED* createLED(uint8_t pin) { LED *result = (LED*)malloc(sizeof(LED)); result->pin = pin; result->level=0; result->runtimes.startTime=0; result->runtimes.runTime=0; result->runtimes.on=false; return result; } void setRGBLed(RGB *led) { Tlc.set(led->pin,led->color.r); Tlc.set(led->pin+1,led->color.g); Tlc.set(led->pin+2,led->color.b); } /* iterate through the list and determine the correct way to execute each node. */ void iterateList(long time, List* list) { if(list != NULL) { Node* node = list->head; while(node != NULL) { executeNode(time, node); node = node->next; } } } void executeNode(long time, Node* node) { if(node != NULL) { switch(node->type) { case LIST: iterateList(time, (List*)node->value); break; case LED_RGB: setRGB(time,(RGB*)node->value); break; case LED_NORMAL: setLED(time,(LED*)node->value); break; default: Tlc.set(1,200); Tlc.update(); break; } } else { } } /* Horrible cheating going on here. There is only one RGB list so we're going to keep some extra state and apply it to just this rgb list. blech. However, this array runs much faster by iterating through precalculated values than trying to calculate and display those values. */ void setRGB(long time, RGB* rgb) { if (rgb != NULL) { boolean cycle = time > rgb->runtimes.startTime + rgb->runtimes.wait; if ( cycle ) { if ( rgb->useSettings != NULL ) { rgb->color = rgb->useSettings->color; rgb->runtimes = rgb->useSettings->runtimes; } else { if ( firstRGBIndex ) { firstRGBIndex = false; rgbLastStart++; if ( rgbLastStart > maxRGB) { rgbLastStart = 0; } rgbIndex = rgbLastStart; } rgb->color = rgbPattern[rgbIndex++]; if ( rgbIndex > maxRGB) { rgbIndex = 0; } } rgb->runtimes.startTime = time; } setRGBLed(rgb); } } void setLED(long time, LED* led) { if (led != NULL) { long execWindow = led->runtimes.startTime + led->runtimes.runTime; if(led->runtimes.runTime == -1 || (time > led->runtimes.startTime && time < execWindow)) { led->runtimes.on = true; Tlc.set(led->pin, led->level); } else if ( time > execWindow && led->runtimes.on == true ) { led->runtimes.startTime = time + led->runtimes.wait; led->runtimes.on = false; Tlc.set(led->pin, 0); } } } /* The various LED array factory methods */ Node* createRGBCluster1(void) { int i = 0; int wait = 50; Runtimes rt = (Runtimes) { 0, 0, wait, false }; Node* rgbList = createNode(LIST, 0); List* rgbCluster1 = (List*)rgbList->value; Node* a = createRGBNode( rgbCluster1, i, rt, NULL); Node* b = createRGBNode( rgbCluster1, i+=3, rt, NULL); Node* c = createRGBNode( rgbCluster1, i+=3, rt, NULL); Node* d = createRGBNode( rgbCluster1, i+=3, rt, NULL); Node* e = createRGBNode( rgbCluster1, i+=3, rt, NULL); // Let them share values which creates a kind of cool looking // fountain effect. createRGBNode( rgbCluster1, i+=3, rt, (RGB*)e->value); createRGBNode( rgbCluster1, i+=3, rt, (RGB*)d->value); createRGBNode( rgbCluster1, i+=3, rt, (RGB*)c->value); createRGBNode( rgbCluster1, i+=3, rt, (RGB*)b->value); createRGBNode( rgbCluster1, i+=3, rt, (RGB*)a->value); return rgbList; } Node* createLEDCluster1a(void) { int i = 30; Node* ledList = createNode(LIST, 0); List* ledCluster = (List*)ledList->value; createLEDNode( ledCluster, i++, (Runtimes) { -1, -1, -1, true }, 300); createLEDNode( ledCluster, i++, (Runtimes) { -1, -1, -1, true }, 300); createLEDNode( ledCluster, i++, (Runtimes) { -1, -1, -1, true }, 300); createLEDNode( ledCluster, i++, (Runtimes) { -1, -1, -1, true }, 300); createLEDNode( ledCluster, i++, (Runtimes) { -1, -1, -1, true }, 300); createLEDNode( ledCluster, i++, (Runtimes) { -1, -1, -1, true }, 300); return ledList; } Node* createLEDCluster2(void) { int i = 36; Node* ledList = createNode(LIST, 0); List* ledCluster = (List*)ledList->value; createLEDNode( ledCluster, i++, (Runtimes) { 10000, 30000, 25000, true }, 3000); createLEDNode( ledCluster, i++, (Runtimes) { 10000, 30000, 25000, true }, 3000); createLEDNode( ledCluster, i++, (Runtimes) { 3000, 90000, 45000, true }, 3000); createLEDNode( ledCluster, i++, (Runtimes) { 3000, 90000, 45000, true }, 3000); return ledList; } /* This is some old code that did the sine wave calculation. It works but is very slow. I wrote some code to capture the values and write them out the serial console instead and then copied the values into the header void setRGBFreq(RGB *led, uint8_t i, uint8_t max) { float frequency = .3; led->r = sin(frequency*(i) + 0) * 127 + 128; led->g = sin(frequency*(i) + 2) * 127 + 128; led->b = sin(frequency*(i) + 4) * 127 + 128; uint8_t total = led->r + led->g + led->b; if ( total > max ) { led->r -= led->r/total * 100.0; led->g -= led->g/total * 100.0; led->b -= led->b/total * 100.0; } //printList(led); }*/
LEDController.h
<code> #ifndef LEDController #define LEDController #define RGB_LEDS 10 typedef enum { LIST, LED_RGB, LED_NORMAL } NodeType; typedef struct { long startTime; long runTime; long wait; boolean on; } Runtimes; typedef struct { uint8_t r; uint8_t g; uint8_t b; } Colors; typedef struct rgb { Colors color; uint8_t pin; Runtimes runtimes; struct rgb *useSettings; } RGB; typedef struct { uint8_t pin; uint8_t level; Runtimes runtimes; } LED; typedef struct node { NodeType type; void* value; struct node* next; } Node; typedef struct { Node* head; Node* tail; } List; static Colors rgbPattern[] = { {128, 243, 31},{165, 222, 11},{199, 193, 1},{227, 158, 3}, {246, 120, 15},{254, 83, 38},{251, 50, 68},{237, 24, 104}, {213, 7, 142},{182, 1, 179},{145, 6, 211},{107, 22, 236}, {71, 47, 250},{40, 80, 254},{17, 117, 247},{3, 155, 229}, {1, 190, 202},{10, 220, 168},{29, 242, 131},{58, 253, 93}, {92, 253, 58},{130, 242, 30},{167, 221, 10},{201, 191, 1}, {228, 156, 3},{247, 118, 16},{254, 81, 39},{251, 48, 70}, {236, 22, 106},{212, 6, 144},{180, 1, 181},{143, 6, 213}, {105, 23, 237},{69, 49, 251},{39, 82, 254},{16, 119, 246}, {3, 157, 228},{1, 192, 200},{11, 222, 166},{31, 243, 129}, {59, 253, 91},{94, 253, 57},{132, 241, 29},{169, 219, 10}, {203, 189, 1},{230, 154, 4},{247, 116, 17},{254, 79, 41}, {250, 46, 72},{235, 21, 109},{210, 5, 147},{178, 1, 183}, {141, 7, 214},{103, 24, 238},{68, 51, 251},{37, 84, 254}, {15, 121, 245},{3, 159, 226},{1, 194, 198},{12, 223, 164}, {32, 243, 126},{61, 254, 89},{96, 252, 55},{134, 240, 27}, {171, 218, 9},{204, 187, 1},{231, 152, 4},{248, 114, 18}, {254, 77, 43},{250, 45, 74},{234, 20, 111},{208, 5, 149}, {176, 1, 185},{139, 8, 216},{101, 25, 239},{66, 52, 252}, {36, 86, 254},{14, 123, 245},{2, 161, 225},{2, 196, 196}, {12, 224, 162},{34, 244, 124},{63, 254, 87},{98, 252, 53}, {136, 239, 26},{173, 216, 8},{206, 186, 1},{232, 150, 5}, {249, 112, 20},{254, 75, 44},{249, 43, 76},{233, 19, 113}, {207, 4, 151},{174, 1, 187},{137, 8, 217},{99, 27, 240}, {64, 54, 252},{34, 88, 254},{13, 125, 244},{2, 163, 224}, {2, 198, 195},{13, 226, 160},{35, 245, 122},{65, 254, 85}, {100, 252, 51},{138, 238, 25},{175, 215, 7},{208, 184, 1}, {233, 147, 5},{249, 109, 21},{254, 73, 46},{248, 42, 78}, {231, 18, 115},{205, 4, 153},{172, 1, 188},{135, 9, 219}, {97, 28, 241},{62, 56, 253},{33, 90, 253},{12, 128, 243}, {2, 165, 222},{2, 199, 193},{14, 227, 158},{36, 246, 120}, {67, 254, 83},{102, 251, 50},{140, 237, 24},{177, 213, 7}, {128, 243, 31},{165, 222, 11},{199, 193, 1},{227, 158, 3}, {246, 120, 15},{254, 83, 38},{251, 50, 68},{237, 24, 104}, {213, 7, 142},{182, 1, 179},{145, 6, 211},{107, 22, 236}, {71, 47, 250},{40, 80, 254},{17, 117, 247},{3, 155, 229}, {1, 190, 202},{10, 220, 168},{29, 242, 131},{58, 253, 93}, {92, 253, 58},{130, 242, 30},{167, 221, 10},{201, 191, 1}, {228, 156, 3},{247, 118, 16},{254, 81, 39},{251, 48, 70}, {236, 22, 106},{212, 6, 144},{180, 1, 181},{143, 6, 213}, {105, 23, 237},{69, 49, 251},{39, 82, 254},{16, 119, 246}, {3, 157, 228},{1, 192, 200},{11, 222, 166},{31, 243, 129}, {59, 253, 91},{94, 253, 57},{132, 241, 29},{169, 219, 10}, {203, 189, 1},{230, 154, 4},{247, 116, 17},{254, 79, 41}, {250, 46, 72},{235, 21, 109},{210, 5, 147},{178, 1, 183}, {141, 7, 214},{103, 24, 238},{68, 51, 251},{37, 84, 254}, {15, 121, 245},{3, 159, 226},{1, 194, 198},{12, 223, 164}, {32, 243, 126},{61, 254, 89},{96, 252, 55},{134, 240, 27}, {171, 218, 9},{204, 187, 1},{231, 152, 4},{248, 114, 18}, {254, 77, 43},{250, 45, 74},{234, 20, 111},{208, 5, 149}, {176, 1, 185},{139, 8, 216},{101, 25, 239},{66, 52, 252}, {36, 86, 254},{14, 123, 245},{2, 161, 225},{2, 196, 196}, {12, 224, 162},{34, 244, 124},{63, 254, 87},{98, 252, 53}, {136, 239, 26},{173, 216, 8},{206, 186, 1},{232, 150, 5}, {249, 112, 20},{254, 75, 44},{249, 43, 76},{233, 19, 113}, {207, 4, 151},{174, 1, 187},{137, 8, 217},{99, 27, 240}, {64, 54, 252},{34, 88, 254},{13, 125, 244},{2, 163, 224}, {2, 198, 195},{13, 226, 160},{35, 245, 122},{65, 254, 85}, {100, 252, 51},{138, 238, 25},{175, 215, 7},{208, 184, 1}, {233, 147, 5},{249, 109, 21},{254, 73, 46},{248, 42, 78}, {231, 18, 115},{205, 4, 153},{172, 1, 188},{135, 9, 219}, {97, 28, 241},{62, 56, 253},{33, 90, 253},{12, 128, 243}, {2, 165, 222},{2, 199, 193},{14, 227, 158},{36, 246, 120}, {67, 254, 83},{102, 251, 50},{140, 237, 24},{177, 213, 7} }; // Basic Double Linked-List functions for LEDs List* createList(void); List* addNode(List* list, Node* node); Node* createNode(NodeType type, uint8_t pin); RGB* createRGB(uint8_t pin); LED* createLED(uint8_t pin); Node* createRGB(List* list, uint8_t pin, Runtimes runtimes, RGB *useSettings); Node* createLEDNode(uint8_t pin, Runtimes runTimes, uint8_t level); void iterateList(long time, List* list); void setRGBLED(long time, RGB *led); void setLED(long time, LED* led); //Cluster factories Node* createRGBCluster1(void); Node* createRGBCluster1a(void); Node* createLEDCluster2(void); void configureRGB(RGB* rgb, Runtimes runtimes, RGB *useSettings); #endif
The code uses a several single Linked Lists to manage all the LEDs, their colors, brightness, durations and other settings. Using a linked list was a mistake and I would manage memory in a different way. Why? The statically allocated rgbPattern uses a lot of memory, yet all the memory is contiguous. The linked lists are not contiguous by intention, rather by coincidence. The result is that you can have small blocks of memory being allocated for each node in the list. The allocator will grab the first block of memory that the node can fit in. This results in the allocator leaving small memory gaps between lists and nodes. These gaps are often not usable and the application can no longer allocate the maximum amount of memory.
Running out of memory is a little odd: the Arduino reboots. You wait several seconds and then watch it reboot again, and again, and you get the idea. I wrote a quick function, not included in the code, that would write a number of bits with a set interval to a pin to give me an idea how far the application got. I hooked up an oscilloscope to the pin and watched the bits. Knowing that I was rebooting was the first trick. The pin writer wrote six bits inside the setup() function and then would write five bits in the loop() function. The five bits were never displayed. Just the six bits every 15 or so seconds.
It was then just a question of playing around with a few things. I found out that commenting out a building's worth of LEDs let the application progress to the loop() and continue running. I reduced the number of colors the rgbPattern cycled through and then uncommented the building. The application ran normally again.
Each Color object is only 24 bytes, yet there are 64 lines with 4 Color objects each. A little more than 6k spent on color rotation.
The next thing would have been to allocate a simple array for all the nodes. Then it would have been one packed memory block for all the LEDs and freed up much more memory for the rest of the app.
However, the solution most likely to work for a larger network of LEDs is a bigger board with more memory. A Mega would easily handle a large number of buildings.
Step 6: Wire Up the Controller Board
Go to TLC5940 Wiring Guide and take a look at the diagram. Note, we use three TLC5940s. The third TLC5940 linked in the same way as the first to the second.
My image shows only two TLC5940's, one ribbon cables and one node. You will need up to three ribbon cables and two nodes if you wire up everything as I have. It will also be a rats nest of cables as the next image shows. Blech! But that can happen when you are using jumper wires, headers and breadboards to wire everything up.
An oscilloscope can be a bit eye opening. The pwm and sin pins show a lot of noise and jitter. The LEDs would flciker at times. The data sheet shows a 100nF capacitor (figure 22, page 21) between the VCC and GND. This does help. A decoupling circuit, such as the one described here reduces the jitter and noise. I used a 100nF and 10uF Electrolitic capacitor and the noise and jitter cleared up entirely. However, the choice of the electrolitic capacitor's value was based on what I had on hand, not the ripple. It still cleaned up a lot of jitter and noise and eliminated any LED flickering.
I'm wiring this up for only two buildings and three LED Clusters. I have 14 more buildings to light and that will require migrating off the Arduino power supply to a separate power supply for both the Arduino and the hundred or so LEDs. It may also require upgrading to an Arduino with more memory too. A LED configuration file on an SD card would be an excellent improvement.
Step 7: Build the Bridges, Nodes and Ribbon Cables
The bridges and nodes are nothing more than connectors for bridging jumper wires to ribbon cables. If you have soldering skills like mine, then grab your multimeter and set it to continuity test. Mine makes a steady beep if it finds can complete a circuit. Check each solder point against adjacent points to verify that you do not have any shorts. If you do have one, a pencil tipped soldering iron is small enough to swipe between the two contacts and break the connection. Well, usually. The point is that you should test the boards. It took far longer to solder everything together than it took to test everything. I didn't have any shorts, which was very surprising. If I can do it, you can too.
The ribbon cables can be a breeze or a nightmare. I'm not sure which. I had accidentally created both cross-over and straight-through cables. Making the ribbon cable is easy. A vice is really handy for the type of connectors (16 pins over 2 rows) I am using. You just slip the ribbon wire into the connector, which has a bunch of teeth or vampire taps. Put the connector and ribbon wire in the vice making sure that the connector is flush with the jaws. Then just tighten the vice. You'll feel the taps cut into the ribbon cable and you are done.
The connectors come with these little clip doodads. They lock into the base of the connector and lock it together. This is great when you know you made the cable right. It also helps disconnecting the cable from the male connector on the bridge and nodes. However, the clip really locks it together. If you make a mistake then you can only snip the connector off and use another connector. Leaving the clip off can cause the ribbon connector to fall apart if your cable is really jammed into the bridge or node, but you can correct a mistake like making a straight-through cable when you wanted a crossover. Buy extra connectors and it will save you a lot of time.
The bridge board
The Node
Step 8: Wire Up a LED Strip, Test, Box and Glue
We need to create our first LED strip to light four windows. The lights will turn on and off at approximately regular intervals. Begin by laying a channel strip along the windows. Cut the strip using good scissors. User a permanent marker to show where the LEDs and a room divider should go.
Take a nail and hammer and use it to punch two holes though the channel for each LED. Clear away the excess plastic with a file, sandpaper, x-acto knife or something sharp. Now use a file or similar tool to make notches for the LED's cathode and anode. Mark the anode (long lead) notch.
I sort of goofed the following up a bit. First, the pictures show 2 LEDs. Turns out that the paper on the windows was much thicker than I thought. I cut the box down so the LEDs were physically closer to the window. The box is consequently not square, it is only about 1/2 to 1/4 the depth of the box shown above. Also, I added one extra LED to each side because even shortening the distance was not enough for the light to shine through. I used a total of four LEDs to make the window bright enough to be seen with the room lights on.
Now the hard part, make the box. Measure the space the box needs to fit in. Be accurate because we want as little light to bleed through as possible. Corrugated cardboard makes the task a little easier because you can cut across the back of the board, without cutting the front of the board, to make a nice square box.
Punch four holes at the top of the box for the 4 LED cathodes to go through. Use something very small and hopefully the leads will be enough to go through what little cardboard remains. This will make a very light tight seal. Attach jumper wires, or some kind of crimp or solder wire to the anodes. Make two thin cuts at the bottom of the box and thread the wire through. Cardboard makes this very easy since it can be forced to wrap around the wire. You can also use foam or electrical tape to bend and cover the wire holes. Attach the cathode wires too.
The room divider is the easy bit. Your channel should be either at the very top of the box or just a little lower. Cut a piece of cardboard that is the height and depth of the box. Cut a small rectangular hole at the back of the piece of cardboard using another channel as a guide. Test fit the cardboard to see if it fits the box and is a tight fit on the LED strip. Then glue it room divider in place.
Now attach the box to the model. First try fitting it into place and see if you need to make any adjustments. Then use regular white glue to attache the box to the model. Trying to do this on an already assembled model can be challenging and have a lot of trial and error. I had to go through about five tries before I got the right size. I did manage to make the box tight enough on the shown model that it did not require glueing. I later noticed that one of the LEDs was not lighting and it was easy to pull the box and reattach the anode and cathode jumpers.
The longer LED strips are for the bar. I just cut a hole through the cardboard that was just big enough for the LEDs to fit through. My LEDs don't even get warm after hours of continuos use so I am not worried about a possible fire hazard. Hook up the LEDs and verify that they all light up and work correctly. The LEDs on this strip are spaced so that you can solder the anodes together into a single bus. You only need one jumper wire to connect all the LEDs to the node.
I mostly used a different colored jumper wire for each cathode. This helps debug wiring errors when the order of the LEDs matters. I also left them attached to each other which makes for something similar to a ribbon cable.
Step 9: Add the RGB's (optional)
I wanted to light the bar using RGB LEDs. I used balsa wood to make the floor and side walls. I used smaller strips to attach some translucent plastic cut from a lid I listed in the "other stuff" section of the parts list. You need some people in the bar, so attach some people that match the scale of the building. This bar is very quiet tonight. It's probably a Tuesday night.
Measure the width of the building. The floor should run the entire width of the building. You do not need to run the entire depth. It should only represent a few feet of scale measurement. Cut using an x-acto knife or something similar. Next, measure out the walls so they are as tall as you want the ceiling to be. Note, the larger LED strip in the previous step will be used to light the room as well. The height of the walls has to be high enough so the ceiling clears any windows. Use wood or white glue to attach the walls to the floor.
You need to get the walls to be 90 degrees to the floor. This can be hard. Use something square and tape the walls and floor to it while the glue dries. Remove the tape and hope the square thing isn't glued on too.
Then cut two small strips of wood for the back wall edges. Repeat the process of glueing and squaring them. The back wall helps hide any cracks or jagged cuts on plastic. It gives the back wall a more "finished" look.
I left out a tool from my list and you see it in the photo above. There are acrylic "Rite-way" clamps. They use two acrylic parts to make the clamp and the clamp locks tight because of some Neodymium magnets. I build everything model with them and the walls are always perfectly square. There are two versions of the clamps, one for N scale and the other for HO scale models. I have an HO scale model and use the appropriate clamps.
Measure the full interior of the model and cut a piece of foam that as precisely as possible matches the interior measurements. Calipers are helpful for this. Insert the foam so that it is slightly higher than your "ceiling" height. Attach the cardboard LED strip from the previous step to the top of the bar using tape.
Insert the bar area and RGB LED display into the building. The foam should cover the the LED strip and RGB display completely.You may have problems and answers are coming.
Step 10: Put It All Together
Making it all look clean and finished is the hardest part. Jumper wires are a little too inflexible to easily slip under the model. The nodes are big and take up a lot of space. The ribbon cables are pretty inflexible too.
The easiest thing to do is to cut a hole under the model and then lace the wires through the holes. The nodes can be attached to the bottom of the table or whatever supports the scenery. Then attach the jumpers from the node to the LEDs or from the LEDs to the nodes (easier). You'll probably need to patch a few light bleed areas or yank the controller board and adjust light levels and timing to get everything just right later.
The photos pretty much tell this tale too. Jumper wires are 24 AWG and you will get much better flexibility with 28 AWG. Using everyday jumpers to dry-fit everything is very important because it is very easy to make a mistake when you are working with 44 individual wires for just two buildings. It is much easier to crimp your own 28 AWG wire and d-sub connectors to get the flexibility.
The ribbon wire shown above was done for a very short distance, about 2 feet. It looks messy. The real wires are much longer, 6 or more feet, and result in a voltage drop. You'll likely need to adjust the IREF resistor to a somewhat lower value. The bright side (haha, bright side....) is that you can have a different IREF resistor for each TLC so you can configure the TLC to operate more efficiently for each building or block of buildings.
Finally, the RGB LEDs can be a pain to connect. Crimping your own wire and use 3 pin female d-subs make it much easier.
Step 11: The End
Like I said at the beginning, model railroading challenges you in nearly every way. A small setup can be done on the "4x8 foot" plywood sheet. Bigger layouts wrap around rooms, garages and basements. Making something that takes hours, days, weeks or months is almost nothing considering that some people spend years building their layout. For some, the train is just the excuse for building a really complicated model. It is like an art for just the artist and a few close friends. I hope this lighting system helps expand that world in some small way.
There is plenty of room for improvement. One area is the nodes since they are not a really good design. They are adequate and easy to build. A better design would be to run some kind of shielded cable to a node and send updates to change the state of the LEDs. Alternatively, create a better node use angled connectors and a custom PCB. Add either female headers or screw connectors if you are using male jumpers or just regular wire.
Another area for improvement is the RGB setup. Previous photographs show them attached to a small breadboard. The breadboard takes up too much space in the buildings. It would be better to use a PCB and set it vertically to take up less space.
The biggest area is the controller board itself. An UNO has too little memory to manage pre-calculated RGB displays and then dozens of LEDs that follow a script. A large memory system like a mega or a Due is more useful. Warning, the Due is not compatible with the library I used. There is a library just for the Due, but you are on your own. Or perhaps an industrious engineer will build a memory expander for the Uno or create a RAM drive.
Thank you for your time and best wishes. I'd love to hear any suggestions or enhancements you may have.
Thanks,
Andy