Introduction: Cylindrical Stainless Steel Decoration Lamp
Here was a broken lamp I picked up in scrap yard that at first I really did not know what it was. It looked very nice & it gave me the idea to turn this broken lamp into a colorful decoration lamp.
According to technical information on internet, this is a modern stainless steel outdoor wall light which has 2 light fittings for directing light upwards and downwards, suitable for lighting doorways and paths or to light up features.
Take a look at some videos & pictures after completion of fixing from broken up-down wall lighting to a colorful decoration lamp
- FINISH PRODUCT PICTURES
- VIDEOS:
This decoration lamp is controlled with 3 modes: Color-wheel, Morph & HSBtoRGB by using 3 ways switch and potentiometer rotary knob.
It looks awesome when this lamp is put in the dark.
Follow me on the next steps to see how I did for reviving this broken lamp...
Step 1: Ideas
Diameter of lamp's stainless steel body is about 60mm. My idea is to put 32 RGB leds into the cylindrical stainless steel casing at one side (upwards – 32 RGB LED; downwards – 32 RGB LED). Of course these leds will also be arranged in cylindrical shape – diameter 50mm, too.
In order to arrange leds in cylindrical form D50mm, I made a flat wooden template with LED spacing 19.7mm. And after all leds were soldered, I bent them to a cylindrical shape of 50mm outer diameter.
To calculate arc length and number of LEDs, we can refer to table below:
Step 2: Bill of Materials
- TIP42C: https://www.digikey.com/product-detail/en/on-semic...
- 2N2222: https://www.amazon.in/2N2222-Transistor-Plastic-Pa...
- 74HC595: https://www.sparkfun.com/products/13699
- ULN2803: https://www.digikey.com/product-detail/en/stmic
- RGB Led - Common Anode: https://www.amazon.com/dp/B077XD5T8P/ref=sspa_dk_d...
- Arduino Uno/ Nano: https://store.arduino.cc/usa/arduino-uno-rev3
Beside main components, we need to buy some more parts like: power adapter 5V - 2A, capacitor 0.1uF (x4), computer ribbon cable ( LINK ), power wires, small bolts & nuts, A4 single clad copper plate, Ferric Chloride for etching....
Tools: wooden plate, gloves, round rod D3mm, ruler, soldering iron...
Step 3: Project Schematic
There are 2 options below to control RGB LEDs using Bit Angle Modulation:
- Option 1: Layers are selected from shift register 74HC595 to control transistor 2N2222 & TIP42C. It save Arduino pins but response time is low. It doesn't matter in my case when I just used transistors (not MOSFET).
- Option 2: Layers are directly controlled from Arduino pins. We used 8 pins for layer selection, plus 4 pins for shift register controlling and no digital pins remain for other application.
For high resolution schematic, you can down load PDF version at LINK.
Attachments
Step 4: Eagle Design
To put all the printed circuit boards, ribbon cables, power cables and cylindrical LEDs inside the tube lamp, I had to design all printed circuit boards in circle shape and drilled some holes for wires passing through. Due to big amount of ribbon cables, during the actual assembly I have to drill and cut in some more positions.
I also marked two circles outside PCB for later cutting them in circle form. You can see this tip on next step.
- Shift Register Board: It includes 2 x 74HC595, 2 x ULN2803, 16 x R100. For this project, we need 2 shift register boards for layer selecting and RGB led - BAM color controlling.
Layer board: It includes 4 x NPN-Transistor 2N2222, 4 x PNP-Transistor TIP42C, 8 x R1K, 4 x R10K. For this project, we also need 2 layers boards, these boards receive control signal from shift register boards to determine which layer to be selected (total 8 layers).
Total print out: It fit to A4 size to print out and save paper.
You can download printable PCB Eagle files with real size atLINK.
Attachments
Step 5: PCB Etching – Drilling – Components Installation
- Clothes Iron Toner Transfer
- Etching PCB by Ferric Chloride
- Cutting PCB in circular shape
TIP: To cut PCB in circular shape by hand, I drilled some holes between two circles as marked on the printed circuit board. It's really effective for small print circuits.
- Drilling, assembly and soldering components: Due to limited size, I have to change 2 x Capacitor 0.1uF to bottom size.
Step 6: Cylindrical LED Soldering
- Prepare led
Leds are RGB common anode type. I use a stainless steel round rod with a diameter about 3mm to bend the led pins.
- RGB pin soldering:
TIP: Cut the gloves and wear only the middle finger. It will help you not getting hot during soldering and not get encumbered as you wear the entire gloves.
- Led pins separated by ruler.
- Finish RGB pins
- Bending & soldering common anode pin
Continue to use stainless steel round rod above to bend the common anode pins.
- Finish RGB pins and common anode pin:
- Led testing
Let keep all LEDs in wooden template, we will easily fix if there was any LED damaged.
It looks good to me!
- Bending led into cylindrical form.
Bending the LEDs slowly until they form cylindrical LED. Due to putting inside stainless body, I have to bend LED pins towards outside and LED bulbs towards center of cylindrical lamp.
- Cylindrical LED finish and ready to be put inside lamp stainless steel body
Step 7: Connection All PCB Boards to Arduino & Cylindrical RGB LED
I took time and effort to do this work. Note: I used ribbon cables to connect all together in this case because this cable is small, steady and flexible. I also drilled some more holes on the PCB so that I could thread all ribbon cable through the holes
- Cylindrical RGB LED connecting to Layer & Shift Register Board
- Prepare stainless steel casting body
- Final assembly
Finally, I put everything in stainless body as pictures. I also added one 3 ways switch to change lamp's mode and one potentiometer to adjust lamp's color.
TIP: For electrical isolation between PCB (or cylindrical LED) and stainless body, I used a clear plastic PVC sheet - book cover
Step 8: Programming
#include <SPI.h> #define latch_pin 2 #define blank_pin 4 #define data_pin 11 #define clock_pin 13 #define SWITCH_A 6 #define SWITCH_B 7 #define POTPIN 0 #define COLORWHEEL 0 #define MORPH 1 #define HSBTORGB 2 #define LIMIT_BAND 15 // Switch & potentiometer variables byte Switch_State = 0; // to store switch reading 0 = COLORWHEEL, 1 = MORPH, 2 = HSBTORGB int POT; int OLD_POT; //*************************************************ColorWheel******************************************// #define myPI 3.14159265358979323846 #define myDPI 1.2732395 #define myDPI2 0.40528473 #define COLOUR_WHEEL_LENGTH 256 uint8_t colourR[COLOUR_WHEEL_LENGTH]; uint8_t colourG[COLOUR_WHEEL_LENGTH]; uint8_t colourB[COLOUR_WHEEL_LENGTH]; int16_t ColPos = 0; uint16_t colourPos; uint8_t R, G, B; //************************************************************************************************************// byte anode[8];//byte to write to the anode shift register, 8 of them, shifting the ON level in each byte in the array byte red[4][8]; byte blue[4][8]; byte green[4][8]; #define BAM_RESOLUTION 4 const byte Size_X = 8; const byte Size_Y = 8; int level=0; int anodelevel=0; int BAM_Bit, BAM_Counter=0; void setup(){ SPI.setBitOrder(MSBFIRST); SPI.setDataMode(SPI_MODE0); SPI.setClockDivider(SPI_CLOCK_DIV2); noInterrupts(); TCCR1A = B00000000; TCCR1B = B00001011; TIMSK1 = B00000010; OCR1A=5; // Best value in my case, without camera flicker anode[0]=B00000001; anode[1]=B00000010; anode[2]=B00000100; anode[3]=B00001000; anode[4]=B00010000; anode[5]=B00100000; anode[6]=B01000000; anode[7]=B10000000; pinMode (latch_pin, OUTPUT); //pinMode (3, OUTPUT); pinMode(data_pin, OUTPUT); pinMode(clock_pin, OUTPUT); pinMode(SWITCH_A, INPUT); pinMode(SWITCH_B, INPUT); SPI.begin(); interrupts(); fill_colour_wheel(); } void loop() { POT = map(analogRead(POTPIN), 0, 1023, 0, 255); // Read the switch and put the result is Switch_State if (digitalRead(SWITCH_A) == HIGH) Switch_State = MORPH; else if (digitalRead(SWITCH_B) == HIGH) Switch_State = HSBTORGB; else Switch_State = COLORWHEEL; switch(Switch_State){ case 0: if (POT < (OLD_POT * 0.95) || POT > (OLD_POT * 1.05)) { OLD_POT = POT; // save the changed value fillTable_colorwheelRGB(POT, R, G, B); } break; case 1: if (abs(POT - OLD_POT) > LIMIT_BAND) { OLD_POT = POT; // save the changed value get_colour(POT, &R, &G, &B); fillTable(R, G, B); } break; case 2: if (abs(POT - OLD_POT) > LIMIT_BAND) { OLD_POT = POT; // save the changed value fillTable_HSBToRGB(map(POT, 0, 255, 0, 7), R, G, B); } break; } } void LED(int Y, int X, int R, int G, int B) { Y = constrain(Y, 0, Size_Y - 1); X = constrain(X, 0, Size_X - 1); R = constrain(R, 0, (1 << BAM_RESOLUTION) - 1); G = constrain(G, 0, (1 << BAM_RESOLUTION) - 1); B = constrain(B, 0, (1 << BAM_RESOLUTION) - 1); for (byte BAM = 0; BAM < BAM_RESOLUTION; BAM++) { //*** RED *** bitWrite(red[BAM][Y], X, bitRead(R, BAM)); //*** GREEN *** bitWrite(green[BAM][Y], X, bitRead(G, BAM)); //*** BLUE *** bitWrite(blue[BAM][Y], X, bitRead(B, BAM)); } } ISR(TIMER1_COMPA_vect) { PORTD |= 1<<blank_pin; 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 SPI.transfer(red[0][level]); //Green SPI.transfer(green[0][level]); //Blue SPI.transfer(blue[0][level]); break; case 1: //Red SPI.transfer(red[1][level]); //Green SPI.transfer(green[1][level]); //Blue SPI.transfer(blue[1][level]); break; case 2: //Red SPI.transfer(red[2][level]); //Green SPI.transfer(green[2][level]); //Blue SPI.transfer(blue[2][level]); break; case 3: //Red SPI.transfer(red[3][level]); //Green SPI.transfer(green[3][level]); //Blue SPI.transfer(blue[3][level]); if(BAM_Counter==120) { BAM_Counter=0; BAM_Bit=0; } break; } SPI.transfer(anode[anodelevel]);//finally, send out the anode level byte PORTD &= ~(1<<latch_pin); PORTD |= 1<<latch_pin; //delayMicroseconds(3); PORTD &= ~(1<<blank_pin);//Blank pin LOW to turn on the LEDs with the new data //delayMicroseconds(3); anodelevel++; level++; if(anodelevel==8) anodelevel=0; if(level==8) level=0; pinMode(blank_pin, OUTPUT); } void clearfast () { for (unsigned char j=0; j<8; 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 fillTable(byte R, byte G, byte B) { for (byte x=0; x<8; x++) { // scan thru every column for (byte y=0; y<8; y++) { // scan thru every panel LED(y, x, R, G, B); } } } void fillTable_colorwheelRGB(int potentio, byte R, byte G, byte B){ // This subroutine fills the cube with a colour for (byte x=0; x<8; x++) { for (byte y=0; y<8; y++) { get_colour(potentio + 24*x , &R, &G, &B); // 24*x - Best in my case LED(y, x, R, G, B); } } } void fillTable_HSBToRGB(byte hue, uint8_t R, uint8_t G, uint8_t B) { byte inSaturation, inBrightness; inSaturation = rand()%15+8; inBrightness = rand()%15+8; for (byte x = 0 ; x < 8 ; x++) { for (byte y = 0 ; y < 8 ; y++) { HSBToRGB(hue + y + x, inSaturation, inBrightness, &R, &G, &B); LED(y, x, R, G, B); } } } ///////////////////////////////////////////////////////////////////////////////// //FAST SINE APPROX float mySin(float x){ float sinr = 0; uint8_t g = 0; while(x > myPI){ x -= 2*myPI; g = 1; } while(!g&(x < -myPI)){ x += 2*myPI; } sinr = myDPI*x - myDPI2*x*myAbs(x); sinr = 0.225*(sinr*myAbs(sinr)-sinr)+sinr; return sinr; } //FAST COSINE APPROX float myCos(float x){ return mySin(x+myPI/2); } float myTan(float x){ return mySin(x)/myCos(x); } //SQUARE ROOT APPROX float mySqrt(float in){ int16_t d = 0; int16_t in_ = in; float result = 2; for(d = 0; in_ > 0; in_ >>= 1){ d++; } for(int16_t i = 0; i < d/2; i++){ result = result*2; } for(int16_t i = 0; i < 3; i++){ result = 0.5*(in/result + result); } return result; } //MAP NUMBERS TO NEW RANGE float myMap(float in, float inMin, float inMax, float outMin, float outMax){ float out; out = (in-inMin)/(inMax-inMin)*(outMax-outMin) + outMin; return out; } //ROUND A NUMBER int16_t myRound(float in){ int8_t s = in/myAbs(in); return (int16_t)(s*(myAbs(in) + 0.5)); } //ABSOLUTE VALUE float myAbs(float in){ return (in)>0?(in):-(in); } void fill_colour_wheel(void) { float red, green, blue; float c, s; int32_t phase = 0; int16_t I = 0; while (phase < COLOUR_WHEEL_LENGTH) { s = (1 << BAM_RESOLUTION)*mySin(myPI*(3 * phase - I*COLOUR_WHEEL_LENGTH) / (2 * COLOUR_WHEEL_LENGTH)); c = (1 << BAM_RESOLUTION)*myCos(myPI*(3 * phase - I*COLOUR_WHEEL_LENGTH) / (2 * COLOUR_WHEEL_LENGTH)); red = (I == 0 ? 1 : 0)*s + (I == 1 ? 1 : 0)*c; green = (I == 1 ? 1 : 0)*s + (I == 2 ? 1 : 0)*c; blue = (I == 2 ? 1 : 0)*s + (I == 0 ? 1 : 0)*c; colourR[phase] = red; colourG[phase] = green; colourB[phase] = blue; if (++phase >= (1 + I)*COLOUR_WHEEL_LENGTH / 3) I++; } } void get_colour(int16_t p, uint8_t *R, uint8_t *G, uint8_t *B) { if (p >= COLOUR_WHEEL_LENGTH) p -= COLOUR_WHEEL_LENGTH; *R = colourR[p]; *G = colourG[p]; *B = colourB[p]; } void get_next_colour(uint8_t *R, uint8_t *G, uint8_t *B) { if (++ColPos >= COLOUR_WHEEL_LENGTH) ColPos -= COLOUR_WHEEL_LENGTH; *R = colourR[ColPos]; *G = colourG[ColPos]; *B = colourB[ColPos]; } void increment_colour_pos(uint8_t i) { colourPos += i; while (colourPos >= COLOUR_WHEEL_LENGTH) { colourPos -= COLOUR_WHEEL_LENGTH; } } ///////////////////////////////////////// void HSBToRGB( unsigned int inHue, unsigned int inSaturation, unsigned int inBrightness, uint8_t *RED, uint8_t *GREEN, uint8_t *BLUE ) { if( inSaturation == 0 ) { // achromatic (grey) *RED = *GREEN = *BLUE = inBrightness; } else { unsigned int scaledHue = (inHue * 6); unsigned int sector = scaledHue >> BAM_RESOLUTION; // sector 0 to 5 around the color wheel unsigned int offsetInSector = scaledHue - (sector << BAM_RESOLUTION); // position within the sector unsigned int p = (inBrightness * ( ((1 << BAM_RESOLUTION) - 1) - inSaturation )) >> BAM_RESOLUTION; unsigned int q = (inBrightness * ( ((1 << BAM_RESOLUTION) - 1) - ((inSaturation * offsetInSector) >> BAM_RESOLUTION) )) >> BAM_RESOLUTION; unsigned int t = (inBrightness * ( ((1 << BAM_RESOLUTION) - 1) - ((inSaturation * ( ((1 << BAM_RESOLUTION) - 1) - offsetInSector )) >> BAM_RESOLUTION) )) >> BAM_RESOLUTION; switch( sector ) { case 0: *RED = inBrightness; *GREEN = t; *BLUE = p; break; case 1: *RED = q; *GREEN = inBrightness; *BLUE = p; break; case 2: *RED = p; *GREEN = inBrightness; *BLUE = t; break; case 3: *RED = p; *GREEN = q; *BLUE = inBrightness; break; case 4: *RED = t; *GREEN = p; *BLUE = inBrightness; break; default: *RED = inBrightness; *GREEN = p; *BLUE = q; break; } } }
Step 9: First Functional Testing Before Final Assembly
Before putting all PCBs and LEDs into stainless steel casting body, I have to do some functional tests to avoid mistakes.
- COLORWHEEL TEST
- HSBTORGB TEST
- MORPH TEST
You can see my testing on this video:
It look very nice, right !!!
Step 10: Second Testing After Putting All Parts Inside Stainless Body
Step 11: FINISH PROJECT
- COLORWHEEL: Lamp's color is adjusted by rotating the knob according to the color-wheel rule.
- MORPH: Lamp's color is transformed (256 colors) using the rotary knob..
- HSBTOGRB: similar to color-wheel above, but lamp's color is mixed by the parameters hue, saturation, brightness through rotary knob.