Introduction: INTERACTIVE RGB LED TABLE

About: PLC, Arduino - Do it yourself project

First of all, I would say, English is not my mother tongue; so please excuse any errors on my part.

In addition to usage as normal LED display, the "INTERACTIVE RGB LED TABLE" is like a "touch screen" made of LEDs with impressive animations. It is used as a dinner table, coffee table, decorated table in a bar, or living room table.... For the project I share below, this interactive LED table is sized in A2 with 256 RGB LEDs, uses infrared emitter and photo-transistor to locate the object on the table and create a nice effect at the corresponding position.

Step 1: How It Works

To know how this table works, you can see DEMO KIT above or at link:

INTERACTIVE LED TABLE - DEMO KIT 8x8

Or

INTERACTIVE LED TABLE - DEMO KIT16x16

Step 2: B.O.M

For this project, we'll use a lot of copper wires and do a lot of soldering works. Let prepare masks, gloves and exhaust fan for health protection.

Step 3: Table Template

See the picture above to understand how to arrange RGB Leds - Phototransistors - Infrared Leds. The size of the table you can choose reasonably, for me, it is the A2 size (594 x 420 mm) for table 16x16. Thus, on top of interactive table, there are a total of the following components:

  • Blue rows: RGB Leds - 256 pcs - Matrix 16x16.
  • Red rows: Phototransistors - 64 pcs - Matrix 8x8.
  • Pink rows: IR Leds - 56 pcs - Matrix 8x7.

Step 4: IR Led and Photo-transisitor

  • IR Led: connect to 5V with current limit resistor 100 ~ 150 ohm.
  • Photo-transistor: use the Common - Collector type with RE ~ 10Kohm.

Normally, when there is no object placed on the table, infrared LED is lit but the phototransistor will not receive this light, the RGB LED will turn off.

When objects are placed on the table, infrared rays emitted from the IR LED will reflect from the object and transmit to the phototransistors, the RGB LEDs around the phototransistor (4 RGB LEDs) will light up.

Step 5: Schematic

Based on circuit diagram above, we see that interactive circuit has two main blocks:

  1. Block 1: 256 or Matrix 16x16 RGB LED display using 4-bit B.A.M method:

2. Block 2: 64 Analog Signals Processing from photo-transistors:

  • Infrared emitter: The IR Leds (total of 56 pcs) are connected to a 5 V source through current limit resistors.
  • Infrared transceivers: A total of 64 photo-transistors are read through 8 - channel analog multiplexer/demultiplexer - 74HC4051 (8: 1) because Arduino Mega 2560 can only read 16 Analog channels directly. About 74HC4051 you can refer to: https://github.com/sparkfun/74HC4051_8-Channel_Mu...

Step 6: Table Coordination

For a better understanding, see the red cell on picture above, it represent for the 64 photo-transistors, marked from Zone00 to Zone63 and around each zone, we have 4 RGB LEDs correspondingly.

Example: Zone00 [4] [2] = {{6, 0}, {7, 0}, {6, 1}, {7, 1}}};

It tells us that photo-transistor number 0 is read by A0 pin of Arduino Mega 2560 and around it is 4 RGB LEDs with coordinates: {6, 0}, {7, 0}, {6, 1}, { 7, 1}. When photo-transistor receives infrared ray higher than LimitSense setting, corresponding 4 RGB LEDs will be affected (turned on, for example).

Step 7: Top and Bottom Table After Soldering

Before and after soldering, we have to check the RGB Led carefully. Note that, infrared light is not visible to the human eye but it can be detected by camera. If the object is reflective, (white or some other light color), then most of the radiation will get reflected by it, and will get incident on the photo-transistor. If the object is non-reflective, (black or some other dark color), then most of the radiation will get absorbed by it, and will not become incident on the photo-transistor.

Step 8: RGB Matrix 16x16

Program below for testing RGB Matrix Led 16x16 to ensure that no led is burned or damaged during soldering work.

RGB_Led_Test

//************************************************************************************************************//
// The 8x8 Interactive RGB LED Table
//************************************************************************************************************//
#include // SPI Library used to clock data out to the shift registers
#define latch_pin 4// Defines actual BIT of PortD for latch - is Arduino UNO pin 2, MEGA pin 4
#define blank_pin 5// Defines actual BIT of PortD for blank - is Arduino UNO pin 3, MEGA pin 5
#define data_pin 51// used by SPI, must be pin MOSI 11 on Arduino UNO, 51 on MEGA
#define clock_pin 52// used by SPI, must be 13 SCK 13 on Arduino UNO, 52 on MEGA
//***************************************************Layer*********************************************************//
#define layer1 26 // bottom layer
#define layer2 27
#define layer3 28
#define layer4 29
#define layer5 30
#define layer6 31
#define layer7 32
#define layer8 33 // top layer
//*************************************************Phototransistor******************************************//
#define sense_select1 36 //8
#define sense_select2 38 //9
#define sense_select3 40 //10
#define ir_array_enable 42 //11
#define LimitSense 150
#define fadetime 1
volatile int ir_sense_data[64];
volatile byte ir_group = 0; // to track current IR group
volatile byte ir_group_adder = 0; // needed in interrupt to set appropriate IR node voltages read from array
unsigned long samplingtime = 0;
//************************************************************************************************************//
int layerArray[8] = {layer1, layer2, layer3, layer4, layer5, layer6, layer7, layer8};
int lastAnode;
byte red[4][32];
byte blue[4][32];
byte green[4][32];
//*********** Defining the Matrix *************
#define BAM_RESOLUTION 4 // EG 4 bit colour = 15 variation of R, G & B (4096 colours)
const byte Size_Y = 16;//Number of Layers Y axis (levels/Layers)
const byte Size_X = 16; //Number of LEDs X axis (Left to right across front)
int level=0;//keeps track of which level we are shifting data to
int anodeLevel=0;//this increments through the anode levels
int BAM_Bit, BAM_Counter=0; // Bit Angle Modulation variables to keep track of things
//****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup
void setup(){
SPI.setBitOrder(MSBFIRST);//Most Significant Bit First
SPI.setDataMode(SPI_MODE0);// Mode 0 Rising edge of data, keep clock low
SPI.setClockDivider(SPI_CLOCK_DIV2);//Run the data in at 16MHz/2 - 8MHz
noInterrupts();// kill interrupts until everybody is set up
//We use Timer 1 to refresh the cube
TCCR1A = B00000000;//Register A all 0's since we're not toggling any pins
TCCR1B = B00001011;//bit 3 set to place in CTC mode, will call an interrupt on a counter match
//bits 0 and 1 are set to divide the clock by 64, so 16MHz/64=250kHz
TIMSK1 = B00000010;//bit 1 set to call the interrupt on an OCR1A match
OCR1A=40;
//finally set up the Outputs
// pinMode(latch_pin, OUTPUT);//Latch
pinMode (2, OUTPUT); // turn off PWM and set PortD bit 4 as output
pinMode (3, OUTPUT); // turn off PWM and set PortD bit 5 as output
pinMode(data_pin, OUTPUT);//MOSI DATA
pinMode(clock_pin, OUTPUT);//SPI Clock
//pinMode(blank_pin, OUTPUT);//Output Enable important to do this last, so LEDs do not flash on boot up
//*** Here layer pins are set as outputs
pinMode(layer1, OUTPUT);
pinMode(layer2, OUTPUT);
pinMode(layer3, OUTPUT);
pinMode(layer4, OUTPUT);
pinMode(layer5, OUTPUT);
pinMode(layer6, OUTPUT);
pinMode(layer7, OUTPUT);
pinMode(layer8, OUTPUT);
// set pin mode for ir array enable
pinMode(ir_array_enable, OUTPUT);
digitalWrite(ir_array_enable, LOW);
//pin modes for IR sense muxes
//analogReadResolution(8);
pinMode(sense_select1, OUTPUT);
pinMode(sense_select2, OUTPUT);
pinMode(sense_select3, OUTPUT);
SPI.begin();//start up the SPI library
interrupts();//let the show begin, this lets the multiplexing start
//Serial.begin(9600); // sets the serial port to 9600
}//***end setup***end setup***end setup***end setup***end setup***end setup***end setup***end setup***end setup***end setup
void LED(int CX, int CY, int CR, int CG, int CB) {
CX = constrain(CX, 0, Size_X - 1);//Matrix X axis
CY = constrain(CY, 0, Size_Y - 1);//Matrix Y axis
CR = constrain(CR, 0, (1 << BAM_RESOLUTION) - 1); //Red
CG = constrain(CG, 0, (1 << BAM_RESOLUTION) - 1); //Green
CB = constrain(CB, 0, (1 << BAM_RESOLUTION) - 1); //Blue
int WhichByte = int(CY*2+CX/8);
int WhichBit = CX%8;
for (byte I = 0; I < BAM_RESOLUTION; I++) {
//*** RED ***
bitWrite(red[I][WhichByte], WhichBit, bitRead(CR, I));
//*** GREEN ***
bitWrite(green[I][WhichByte], WhichBit, bitRead(CG, I));
//*** BLUE ***
bitWrite(blue[I][WhichByte], WhichBit, bitRead(CB, I));
}
}//****LED ROUTINE END****
ISR(TIMER1_COMPA_vect){//***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM
if(BAM_Counter==8)
BAM_Bit++;
else
if(BAM_Counter==24)
BAM_Bit++;
else
if(BAM_Counter==56)
BAM_Bit++;
BAM_Counter++;
switch (BAM_Bit){
case 0:
//Red
myTransfer(red[0][level+16]);
myTransfer(red[0][level+17]);
myTransfer(red[0][level]);
myTransfer(red[0][level+1]);
//Green
myTransfer(green[0][level+16]);
myTransfer(green[0][level+17]);
myTransfer(green[0][level]);
myTransfer(green[0][level+1]);
//Blue
myTransfer(blue[0][level+16]);
myTransfer(blue[0][level+17]);
myTransfer(blue[0][level]);
myTransfer(blue[0][level+1]);
break;
case 1:
//Red
myTransfer(red[1][level+16]);
myTransfer(red[1][level+17]);
myTransfer(red[1][level]);
myTransfer(red[1][level+1]);
//Green
myTransfer(green[1][level+16]);
myTransfer(green[1][level+17]);
myTransfer(green[1][level]);
myTransfer(green[1][level+1]);
//Blue
myTransfer(blue[1][level+16]);
myTransfer(blue[1][level+17]);
myTransfer(blue[1][level]);
myTransfer(blue[1][level+1]);
break;
case 2:
//Red
myTransfer(red[2][level+16]);
myTransfer(red[2][level+17]);
myTransfer(red[2][level]);
myTransfer(red[2][level+1]);
//Green
myTransfer(green[2][level+16]);
myTransfer(green[2][level+17]);
myTransfer(green[2][level]);
myTransfer(green[2][level+1]);
//Blue
myTransfer(blue[2][level+16]);
myTransfer(blue[2][level+17]);
myTransfer(blue[2][level]);
myTransfer(blue[2][level+1]);
break;
case 3:
//Red
myTransfer(red[3][level+16]);
myTransfer(red[3][level+17]);
myTransfer(red[3][level]);
myTransfer(red[3][level+1]);
//Green
myTransfer(green[3][level+16]);
myTransfer(green[3][level+17]);
myTransfer(green[3][level]);
myTransfer(green[3][level+1]);
//Blue
myTransfer(blue[3][level+16]);
myTransfer(blue[3][level+17]);
myTransfer(blue[3][level]);
myTransfer(blue[3][level+1]);
if(BAM_Counter==120){
BAM_Counter=0;
BAM_Bit=0;
}
break;
}
lastAnode = (anodeLevel-1);
if (anodeLevel == 0) { lastAnode = 7; } // if we are at the bottom, the last layer was the top
digitalWrite(layerArray[lastAnode], LOW); // turn off the previous layer
digitalWrite(layerArray[anodeLevel], HIGH); // turn on the current layer
PORTE |= 1<
PORTE &= ~(1<
delayMicroseconds(3); //???;
PORTE &= ~(1<
//delayMicroseconds(5); //???;
anodeLevel++;//inrement the anode level
level = anodeLevel*2;//increment the level variable by 1, which is used to shift out data, since the next level woudl be the next 1 bytes in the arrays
if(anodeLevel==8)//go back to 0 if max is reached
anodeLevel=0;
if(level==16)//if you hit 16 on level, this means you just sent out all 16 bytes, so go back// QUAN TRONG
level=0;
pinMode(blank_pin, OUTPUT);//moved down here so outputs are all off until the first call of this function
}
inline static uint8_t myTransfer(uint8_t C_data){
SPDR = C_data;
asm volatile("nop"); asm volatile("nop");
}
void clearfast ()
{
for (unsigned char j=0; j<32; j++)
{
red[0][j] = 0;
red[1][j] = 0;
red[2][j] = 0;
red[3][j] = 0;
green[0][j] = 0;
green[1][j] = 0;
green[2][j] = 0;
green[3][j] = 0;
blue[0][j] = 0;
blue[1][j] = 0;
blue[2][j] = 0;
blue[3][j] = 0;
}
}
void loop(){
for (byte x=0; x<16; x++)
{
for (byte y=0; y<16; y++)
{
LED(x,y,15,0,0);
delay(50);
}
}
delay (2000);
for (byte y=0; y<16; y++)
{
for (byte x=0; x<16; x++)
{
LED(x,y,0,15,0);
delay(50);
}
}
delay (2000);
for (byte x=0; x<16; x++)
{
for (byte y=0; y<16; y++)
{
LED(x,y,0,0,15);
delay(50);
}
}
}
view rawRGB_Led_Test hosted with ❤ by GitHub

Step 9: Interactive Test

According to above diagram, Arduino Mega 2560 will read 8 signal photo-transistors depending on 3 signals: Select 1 - Select 2 - Select 3. So if every 200us, Arduino Mega read one time (Each time Arduino read 8 analog signals), we take about 200x8 = 1600us ~ 1.6ms to read 64 analog channels and save data to the array: phototransistor_data [0] ~ phototransistor_data [63]. It is fast enough that interactive table is not responding late when the object goes through. This timing we can adjusted and you can see more detail in the program.

Interactive RGB Led Table Test Program

void Interactive_Table()
{
if ( (unsigned long) (micros() - samplingtime) > 200 ) // Read 8 phototransistors every 200us
{
// Read 8 phototransistors every 200us and store data into array
phototransistor_data[0 + photo_adder] = analogRead(A0);
phototransistor_data[8 + photo_adder] = analogRead(A1);
phototransistor_data[16 + photo_adder] = analogRead(A2);
phototransistor_data[24 + photo_adder] = analogRead(A3);
phototransistor_data[32 + photo_adder] = analogRead(A4);
phototransistor_data[40 + photo_adder] = analogRead(A5);
phototransistor_data[48 + photo_adder] = analogRead(A6);
phototransistor_data[56 + photo_adder] = analogRead(A7);
// update sensor group and mux selects for next time through
if (photo_adder < 7 )
{
photo_adder ++;
}
else
{
photo_adder = 0;
}
digitalWrite(Select1, bitRead(photo_adder, 2)); // sense mux MSB
digitalWrite(Select2, bitRead(photo_adder, 1));
digitalWrite(Select3, bitRead(photo_adder, 0)); // sense mux LSB
samplingtime = micros();
}
for (byte i=0; i<64; i++)
{
if (phototransistor_data[i] < LimitSense)
{
ClearLightZone(i); // clear color
}
else
{
SetLightZone(i, 15, 0, 0); // set RED color
}
}
}
void SetLightZone(byte Zone, byte R, byte G, byte B)
{
switch (Zone)
{
case 0: DrawLight(Zone00, R, G, B); break;
case 1: DrawLight(Zone01, R, G, B); break;
case 2: DrawLight(Zone02, R, G, B); break;
case 3: DrawLight(Zone03, R, G, B); break;
case 4: DrawLight(Zone04, R, G, B); break;
case 5: DrawLight(Zone05, R, G, B); break;
case 6: DrawLight(Zone06, R, G, B); break;
case 7: DrawLight(Zone07, R, G, B); break;
case 8: DrawLight(Zone08, R, G, B); break;
case 9: DrawLight(Zone09, R, G, B); break;
case 10: DrawLight(Zone10, R, G, B); break;
case 11: DrawLight(Zone11, R, G, B); break;
case 12: DrawLight(Zone12, R, G, B); break;
case 13: DrawLight(Zone13, R, G, B); break;
case 14: DrawLight(Zone14, R, G, B); break;
case 15: DrawLight(Zone15, R, G, B); break;
case 16: DrawLight(Zone16, R, G, B); break;
case 17: DrawLight(Zone17, R, G, B); break;
case 18: DrawLight(Zone18, R, G, B); break;
case 19: DrawLight(Zone19, R, G, B); break;
case 20: DrawLight(Zone20, R, G, B); break;
case 21: DrawLight(Zone21, R, G, B); break;
case 22: DrawLight(Zone22, R, G, B); break;
case 23: DrawLight(Zone23, R, G, B); break;
case 24: DrawLight(Zone24, R, G, B); break;
case 25: DrawLight(Zone25, R, G, B); break;
case 26: DrawLight(Zone26, R, G, B); break;
case 27: DrawLight(Zone27, R, G, B); break;
case 28: DrawLight(Zone28, R, G, B); break;
case 29: DrawLight(Zone29, R, G, B); break;
case 30: DrawLight(Zone30, R, G, B); break;
case 31: DrawLight(Zone31, R, G, B); break;
case 32: DrawLight(Zone32, R, G, B); break;
case 33: DrawLight(Zone33, R, G, B); break;
case 34: DrawLight(Zone34, R, G, B); break;
case 35: DrawLight(Zone35, R, G, B); break;
case 36: DrawLight(Zone36, R, G, B); break;
case 37: DrawLight(Zone37, R, G, B); break;
case 38: DrawLight(Zone38, R, G, B); break;
case 39: DrawLight(Zone39, R, G, B); break;
case 40: DrawLight(Zone40, R, G, B); break;
case 41: DrawLight(Zone41, R, G, B); break;
case 42: DrawLight(Zone42, R, G, B); break;
case 43: DrawLight(Zone43, R, G, B); break;
case 44: DrawLight(Zone44, R, G, B); break;
case 45: DrawLight(Zone45, R, G, B); break;
case 46: DrawLight(Zone46, R, G, B); break;
case 47: DrawLight(Zone47, R, G, B); break;
case 48: DrawLight(Zone48, R, G, B); break;
case 49: DrawLight(Zone49, R, G, B); break;
case 50: DrawLight(Zone50, R, G, B); break;
case 51: DrawLight(Zone51, R, G, B); break;
case 52: DrawLight(Zone52, R, G, B); break;
case 53: DrawLight(Zone53, R, G, B); break;
case 54: DrawLight(Zone54, R, G, B); break;
case 55: DrawLight(Zone55, R, G, B); break;
case 56: DrawLight(Zone56, R, G, B); break;
case 57: DrawLight(Zone57, R, G, B); break;
case 58: DrawLight(Zone58, R, G, B); break;
case 59: DrawLight(Zone59, R, G, B); break;
case 60: DrawLight(Zone60, R, G, B); break;
case 61: DrawLight(Zone61, R, G, B); break;
case 62: DrawLight(Zone62, R, G, B); break;
case 63: DrawLight(Zone63, R, G, B); break;
}
}
void ClearLightZone(byte Zone)
{
switch (Zone)
{
case 0: DrawLight(Zone00, 0, 0, 0); break;
case 1: DrawLight(Zone01, 0, 0, 0); break;
case 2: DrawLight(Zone02, 0, 0, 0); break;
case 3: DrawLight(Zone03, 0, 0, 0); break;
case 4: DrawLight(Zone04, 0, 0, 0); break;
case 5: DrawLight(Zone05, 0, 0, 0); break;
case 6: DrawLight(Zone06, 0, 0, 0); break;
case 7: DrawLight(Zone07, 0, 0, 0); break;
case 8: DrawLight(Zone08, 0, 0, 0); break;
case 9: DrawLight(Zone09, 0, 0, 0); break;
case 10: DrawLight(Zone10, 0, 0, 0); break;
case 11: DrawLight(Zone11, 0, 0, 0); break;
case 12: DrawLight(Zone12, 0, 0, 0); break;
case 13: DrawLight(Zone13, 0, 0, 0); break;
case 14: DrawLight(Zone14, 0, 0, 0); break;
case 15: DrawLight(Zone15, 0, 0, 0); break;
case 16: DrawLight(Zone16, 0, 0, 0); break;
case 17: DrawLight(Zone17, 0, 0, 0); break;
case 18: DrawLight(Zone18, 0, 0, 0); break;
case 19: DrawLight(Zone19, 0, 0, 0); break;
case 20: DrawLight(Zone20, 0, 0, 0); break;
case 21: DrawLight(Zone21, 0, 0, 0); break;
case 22: DrawLight(Zone22, 0, 0, 0); break;
case 23: DrawLight(Zone23, 0, 0, 0); break;
case 24: DrawLight(Zone24, 0, 0, 0); break;
case 25: DrawLight(Zone25, 0, 0, 0); break;
case 26: DrawLight(Zone26, 0, 0, 0); break;
case 27: DrawLight(Zone27, 0, 0, 0); break;
case 28: DrawLight(Zone28, 0, 0, 0); break;
case 29: DrawLight(Zone29, 0, 0, 0); break;
case 30: DrawLight(Zone30, 0, 0, 0); break;
case 31: DrawLight(Zone31, 0, 0, 0); break;
case 32: DrawLight(Zone32, 0, 0, 0); break;
case 33: DrawLight(Zone33, 0, 0, 0); break;
case 34: DrawLight(Zone34, 0, 0, 0); break;
case 35: DrawLight(Zone35, 0, 0, 0); break;
case 36: DrawLight(Zone36, 0, 0, 0); break;
case 37: DrawLight(Zone37, 0, 0, 0); break;
case 38: DrawLight(Zone38, 0, 0, 0); break;
case 39: DrawLight(Zone39, 0, 0, 0); break;
case 40: DrawLight(Zone40, 0, 0, 0); break;
case 41: DrawLight(Zone41, 0, 0, 0); break;
case 42: DrawLight(Zone42, 0, 0, 0); break;
case 43: DrawLight(Zone43, 0, 0, 0); break;
case 44: DrawLight(Zone44, 0, 0, 0); break;
case 45: DrawLight(Zone45, 0, 0, 0); break;
case 46: DrawLight(Zone46, 0, 0, 0); break;
case 47: DrawLight(Zone47, 0, 0, 0); break;
case 48: DrawLight(Zone48, 0, 0, 0); break;
case 49: DrawLight(Zone49, 0, 0, 0); break;
case 50: DrawLight(Zone50, 0, 0, 0); break;
case 51: DrawLight(Zone51, 0, 0, 0); break;
case 52: DrawLight(Zone52, 0, 0, 0); break;
case 53: DrawLight(Zone53, 0, 0, 0); break;
case 54: DrawLight(Zone54, 0, 0, 0); break;
case 55: DrawLight(Zone55, 0, 0, 0); break;
case 56: DrawLight(Zone56, 0, 0, 0); break;
case 57: DrawLight(Zone57, 0, 0, 0); break;
case 58: DrawLight(Zone58, 0, 0, 0); break;
case 59: DrawLight(Zone59, 0, 0, 0); break;
case 60: DrawLight(Zone60, 0, 0, 0); break;
case 61: DrawLight(Zone61, 0, 0, 0); break;
case 62: DrawLight(Zone62, 0, 0, 0); break;
case 63: DrawLight(Zone63, 0, 0, 0); break;
}
}
void DrawLight(byte zone_dots[4][2], byte R, byte G, byte B)
{
for (int i = 0; i < 4; i++)
{
LED(zone_dots[i][0], zone_dots[i][1], R, G, B);
//delay_ms(10);
}
}
view rawInteractive_Test hosted with ❤ by GitHub

Step 10: VU Meter Test

This spectrum analyzer is base on fix FFT. For more information, you can see at: http://forum.arduino.cc/index.php?topic=38153.0

Step 11: Main Program

This is program for whole project. You can download at address: https://github.com/tuenhidiy/Interactive-RGB-Led-T...

Step 12: More Pictures

Some more project PICTURES....

Step 13: Full Test - Videos

Step 14: Summary

  • The photo-transistor receives infrared rays from sunlight so this table does not use interactive mode during the daytime. Note that, black objects will almost completely absorb infrared light so it will not be reflected to photo-transistor.
  • Clear acrylic is infrared transmitting, it allows infrared light through. So we can place clear acrylic on interactive led table and then put objects such as books, glasses, cups, teapot ... easily on it.
  • This table can be used as a VU meter (spectrum analyzer) or play some games like Tetris, Pong ....
  • Interactive RGB LED table will be much better if it is increased to size 16x32 LEDs (equal 2 times current table) and is placed in a wooden table. Let imagine how it is great if whole family is sitting on this table and have dinner together under lights and amazing effects .... !!!

LED Contest 2017

Participated in the
LED Contest 2017

Epilog Challenge 9

Participated in the
Epilog Challenge 9

Arduino Contest 2017

Participated in the
Arduino Contest 2017