Introduction: Cylindrical Stainless Steel Decoration Lamp

About: PLC, Arduino - Do it yourself project

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

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.

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.

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

    DONE !!! Now, I can adjust this colorful lamp by using 3 ways switch and potentiometer rotary knob

    - 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.

    Fix It! Contest

    Runner Up in the
    Fix It! Contest