Finally got around to making an LED table top, as it turns out — for my wedding reception. We decided to have a lounge area and an LED coffee table seemed like the perfect centerpiece for it. I decided instead of making a full table that I would make a table top that fit onto an existing ottoman. I affixed the LED strips to a plywood board which had a 2″ raised frame with aluminium duct tape, to help with brightness.
Arranging the LED’s in a proper matrix turned out to be quite a job as the strips I used came pre-wired and there isn’t all that much length between LED’s on the strip. I ended up having to cut and re-splice the connection leads for each row of the 7 x 7 matrix, after that the construction went quickly. You could get around this by using a more modular LED strip solution, I initially had ShiftBrites slated for this project, but I made something else with them and when I got around to this table top there were much less expensive options available.
I created an outer frame with a bevel to support a glass top. Initially I went with plexiglass but it would bow in the middle with anything of weight on the table, I didn’t want to add supports as this would disrupt the light diffusion, so I opted for a piece of tempered glass (actually intended for table tops to boot).
Adding adhesive obscuring film to the glass didn’t have the diffusion effect I’d hoped for so I sandwiched a sheet of white tracing paper between the tempered glass and a similarly sized piece of plexiglass and this gave the soft white diffused look I wanted.
The issue of programming the animations took a little longer. I wanted to use the disco(esc) animations available from the fine folks responsible for the 1E Disco Dance Floor — which I also used in my ohDisco! app for iPad. These animations are 32 x 16 and can have hundreds of frames — too much to load completely into the Arduino’s memory. But I didn’t want to have to deal with reducing the animations to 7 x 7, or reducing their total frame count, as this would affect the quality and overall impression. Instead I opted to add an SD card reader to the setup which stores the animations. The 7 x 7 section of each frame is loaded on-demand from each animation file and displayed on the table, with this setup the Arduino has no memory problems whatsoever and with a little more code it could index and play animations from the SD card without the need for code changes.
Worth noting is that the SdFat library used to interface with the Seeedstudio SD Card Shield wouldn’t run reliably (or at all sometimes) on an ATmega128 so be sure to use a more powerful Arduino running an ATmega328.
Parts
- Adafruit 12mm Diffused Flat Digital RGB LED Pixels, ~$39
- Seeedstudio SD Card Shield, ~$14
- SD Card
- Arduino Uno USB, ~$24
- 1E Disco Dance Floor Animations
- 5v Power Adapter
- Aluminium Duct Tape
- 30 x 30 Wood Panel for base and inner frame
- 30 x 30 Plexiglass/Tempered Glass
- 12′ Length of 4″ x 1″ pine for outer frame
- 12′ Length of 1″ x 1″ pine for inner ridge to support glass
- Translucent tracing paper/vellum for diffusion
Arduino Sketch
const int chipSelect = 10; #include <SdFat.h> #include "SPI.h" #include "Adafruit_WS2801.h" uint8_t dataPin = 2; uint8_t clockPin = 3; SdFat sd; SdFile myFile; int rows = 32; int cols = 16; long framesize = rows*cols*3; long rowsize = cols*3; int ledrows = 6; int ledcols = 6; int rep = 0; long reps = 5; int brightness = 15; int delaytime = 40; char* files[]={ "pulsar.ddf", "snake.ddf", "inter3.ddf", "inter4.ddf", "inter5.ddf", "rings.ddf", "rings2.ddf", "rings3.ddf", "matrix.ddf" }; int fileCount = 9; char* file; int frame = 0; int frames; Adafruit_WS2801 strip = Adafruit_WS2801(50, dataPin, clockPin); // strip to matrix addressing array byte addressMatrix[7][7] = { 1,2,3,4,5,6,7, 14,13,12,11,10,9,8, 15,16,17,18,19,20,21, 28,27,26,25,24,23,22, 29,30,31,32,33,34,35, 42,41,40,39,38,37,36, 43,44,45,46,47,48,49 }; void setup() { Serial.begin(9600); randomSeed(analogRead(0)); delay(400); // catch Due reset problem if (!sd.begin(chipSelect, SPI_FULL_SPEED)) sd.initErrorHalt(); file = files[ random(fileCount) ]; strip.begin(); strip.setPixelColor(0, 0, 0, 0); strip.show(); } void loop() { if (!myFile.open(file, O_READ)) { sd.errorHalt("failed"); rep = reps + 1; return; } // seek to next frame if(myFile.fileSize() < ((frame*framesize)+1)) { myFile.close(); frame = 0; rep = rep + 1; if(rep > reps) { rep = 0; file = files[ random(fileCount) ]; Serial.println(file); } return; } else { myFile.seekSet(frame*framesize); } // adjust reps for number of frames if(frame == 0) { frames = myFile.fileSize()/framesize; reps = 750/frames; Serial.println(frames,DEC); Serial.println(reps,DEC); } int data; int column = 0; int row = 0; while (row <= ledrows) { while (column <= ledcols) { data = myFile.read(); // read red int r = map(data,0,255,0,255); // read green data = myFile.read(); int g = map(data,0,255,0,255); // read blue data = myFile.read(); int b = map(data,0,255,0,255); // set pixel address byte address = addressMatrix[row][column]; // set pixel color strip.setPixelColor(address, map(r,0,255,0,brightness), map(g,0,255,0,brightness), map(b,0,255,0,brightness)); // next column column = column + 1; } // reset column count column = 0; // increment row row = row + 1; // skip extra pixels myFile.seekSet((frame*framesize)+(row*rowsize)); } // turn off first pixel (7x7 matrix, 1 unused pixel) strip.setPixelColor(0, 0, 0, 0); // send current frame to strip strip.show(); // close the file myFile.close(); // increment frame frame = frame + 1; // rest delay(delaytime); }