I love Tabasco and always though that their cute little miniature bottles could be the basis for a great specialty lamp. I was very happy to find that a 3mm LED fits perfectly in the bottle opening of the miniature bottles. By designing a custom circuit board I was able to make a lamp that looks like a bundle of dried peppers.

Each bottle is individually controlled with a TLC5940 16-channel PWM driver. The software running on the ATmega328 is Arduino code that implements different lighting effects. Here it is in action:

### Hardware

I designed a circuit board for the ATmega328 and the TLC5940. This is basically a custom Arduino and the design files are available below in case you want to make one. The LEDs connect from the bottom of the board. I am using a 5V regulated power supply, so there’s no need for an on-board voltage regulator. I included a 6-pin serial header for easy Arduino programming and mounting holes on the corners so I can hang it from wires. I also included a tactile button in case I wanted to allow some input to control the lamp (currently unused). Also note the Tabasco logo right on the board silkscreen!

I photographed the assembly process so you could see how I constructed the lamp.

### Software

Here is the Arduino code running on the lamp. I used the TLC5940 Arduino library to control the PWM driver. You can download the code from here.

#include "Tlc5940.h" #define NPINS 16 #define NVISUALIZATIONS 6 #define NFUNCTIONS 3 #define BUTTON 2 // pointers to the current and last visualizations void (*visualization)() = rise; void (*lastVisualization)() = rise; // array of visualizations void (*visualizations[NVISUALIZATIONS])() = {throb, blinkRandom, onOff, upDown, rise, fall}; // An array of all the functions. int (*allFunctions[NFUNCTIONS])(float x) = {linear, sine, exponential}; // Each pin has a function associated with it. The function determines how the brightness // of the LED changes over time. int (*function[NPINS])(float x); // The x values for the function associated with each LED. The x value is the current position along // the x axis for the function. There are are 256 x values on the x axis. That is, the domain of the // function is [0-255]. float x[NPINS]; // dx describes (for each LED) how the x value changes over time. If dx=1, then x is increased by 1 // for each duty cycle. If dx = -1, it is decreased by 1. If dx=5, then the brightness of the LED will // change faster according to the function because we are moving along the function curve faster. float dx[NPINS]; // y is the current value of the function at x. If the function is 'linear' and x=128, then y=128. // If the function is 'sine' and x=10, then y=0x03. See the sineValues array below that defines // the sine function. int y[NPINS]; // For each LED we can specify whether the x value should "wrap" around when reaching the end. // There are 256 possible values for x [0-255]. If we reach 255 and dx=1, then we can wrap around // back to x=0 if wrapFunction=true. If false, then adding 1 to 255 keeps x=255. boolean wrapFunction[NPINS]; // Definition of sine function. Array lookup executes faster than actually computing the sine. unsigned char sineValues[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x02,0x03,0x03,0x04,0x05,0x06,0x07,0x08, 0x09,0x0a,0x0c,0x0d,0x0f,0x10,0x12,0x13,0x15,0x17,0x19,0x1b,0x1d,0x1f,0x21,0x23, 0x25,0x27,0x2a,0x2c,0x2e,0x31,0x33,0x36,0x38,0x3b,0x3e,0x40,0x43,0x46,0x49,0x4c, 0x4f,0x51,0x54,0x57,0x5a,0x5d,0x60,0x63,0x67,0x6a,0x6d,0x70,0x73,0x76,0x79,0x7c, 0x80,0x83,0x86,0x89,0x8c,0x8f,0x92,0x95,0x98,0x9c,0x9f,0xa2,0xa5,0xa8,0xab,0xae, 0xb0,0xb3,0xb6,0xb9,0xbc,0xbf,0xc1,0xc4,0xc7,0xc9,0xcc,0xce,0xd1,0xd3,0xd5,0xd8, 0xda,0xdc,0xde,0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xed,0xef,0xf0,0xf2,0xf3,0xf5, 0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfc,0xfd,0xfe,0xfe,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xfe,0xfd,0xfc,0xfc,0xfb,0xfa,0xf9,0xf8,0xf7, 0xf6,0xf5,0xf3,0xf2,0xf0,0xef,0xed,0xec,0xea,0xe8,0xe6,0xe4,0xe2,0xe0,0xde,0xdc, 0xda,0xd8,0xd5,0xd3,0xd1,0xce,0xcc,0xc9,0xc7,0xc4,0xc1,0xbf,0xbc,0xb9,0xb6,0xb3, 0xb0,0xae,0xab,0xa8,0xa5,0xa2,0x9f,0x9c,0x98,0x95,0x92,0x8f,0x8c,0x89,0x86,0x83, 0x80,0x7c,0x79,0x76,0x73,0x70,0x6d,0x6a,0x67,0x63,0x60,0x5d,0x5a,0x57,0x54,0x51, 0x4f,0x4c,0x49,0x46,0x43,0x40,0x3e,0x3b,0x38,0x36,0x33,0x31,0x2e,0x2c,0x2a,0x27, 0x25,0x23,0x21,0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x12,0x10,0x0f,0x0d,0x0c,0x0a, 0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x03,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00 }; void setup() { randomSeed(analogRead(5)); pinMode(BUTTON, INPUT); digitalWrite(BUTTON, HIGH); Tlc.init(0); } void loop(){ lastVisualization = visualization; visualization = visualizations[random(NVISUALIZATIONS)]; // call the current visualization function (*visualization)(); } // Execute n duty cycles. // First we call stepXAll() which adds dx to each x value. This moves the LED along the x axis // of its function. // Then, for each LED, we compute the new y value for the current x value according to which // function (linear, sine, exponential) that is associated with the LED. // For any LED that is supposed to be on at all (any pin with a y value greater than 0), we turn it on. // A duty cycle is a loop of 256 steps executed quickly in succession. At the beginning of the duty cycle // each LED that has a value of y>0 will be on. At each step we see if there are any LEDs that need // to be turned off for the remainder of the duty cycle. So if an LED has y=100, we will turn it off at // step 100. That LED will have been on for the first 100 steps of the duty cycle and off for the // remaining 156 steps. So it is dimmer. If an LED has y=255, then it will remain on during the whole // duty cycle and be at full brightness. An LED with a low value y=10 will be very dim because we turn // it off very early in the duty cycle. // This 256 step cycle is executed n times as specified by the input parameter n. void dutyCycle(int n) { for(int i=0;i<n;i++) { stepXAll(); computePinValueAll(); for(int p=0;p<NPINS;p++) { Tlc.set(p, y[p]*16); } Tlc.update(); delay(10); } } void stepXAll() { for(int p=0;p<NPINS;p++) { stepX(p); } } // Increment the x value for pin p by the value dx. // Wrap the function around back to 0 (or back to 255) // if wrapFunction is true for the pin p. void stepX(int p) { x[p] = x[p] + dx[p]; if (wrapFunction[p]) { if (x[p] > 255) { x[p] = x[p] - 256; } else if (x[p] < 0) { x[p] = x[p] + 256; } } else { if (x[p] > 255) { x[p] = 255; } else if (x[p] < 0) { x[p] = 0; } } } void computePinValueAll() { for(int p=0;p<NPINS;p++) { computePinValue(p); } } // Compute value y for pin p. // Apply the function by invoking it with the x value. void computePinValue(int p) { y[p] = (*function[p])(x[p]); } ////////////////////////////////////////////////////////////////////////////////// // Mathematical Functions ////////////////////////////////////////////////////////////////////////////////// int linear(float x) { return (int)(x + 0.5); } int sine(float x) { return sineValues[(int)x]; } int exponential(float x) { return (int) (255.0 * pow(50, ((x/127.5)-2))); } ////////////////////////////////////////////////////////////////////////////////// // Visualizations ////////////////////////////////////////////////////////////////////////////////// // Do 30 blinks. Each LED that is blinked will fade // quickly down to 0 according to the exponential function. void blinkRandom() { for(int p=0;p<NPINS;p++) { function[p] = exponential; wrapFunction[p] = false; x[p] = 0; dx[p] = -4; } for(int i=0;i<30;i++) { int p = random(0, NPINS); x[p] = 255; dutyCycle(10); } } void onOff() { allOff(); for(int p=0; p<NPINS; p++) { x[p] = 0; dx[p] = 0; function[p] = linear; wrapFunction[p] = false; } int on = 0; int p; int d; while (on < NPINS) { p = random(0, NPINS); if (x[p] == 0) { on++; x[p] = 255; dutyCycle(1); d = 200 - (on * 10); delay(d); } } delay(1000); while (on > 0) { p = random(0, NPINS); if (x[p] == 255) { on--; x[p] = 0; dutyCycle(1); d = 40 + (on * 10); delay(d); } } delay(100); } void throb() { eachDown(); float minSpeed = 0.4; float maxSpeed = 1.0; // Set random speeds for LEDs for(int i=0;i<NPINS;i++) { dx[i] = minSpeed + (random(0, 17) * ((maxSpeed - minSpeed) / NPINS)); } for(int p=0; p<NPINS; p++) { function[p] = sine; wrapFunction[p] = true; } // Run for a while. Each pin will throb according to sine wave. dutyCycle(random(500, 2000)); eachDown(); delay(500); } void fall() { int down[NPINS] = {5, 0, 15, 9, 11, 4, 2, 3, 7, 12, 14, 1, 8, 6, 10, 13}; allOff(); for(int p=0; p<NPINS; p++) { function[down[p]] = linear; wrapFunction[down[p]] = false; x[down[p]] = 0; dx[down[p]] = 8; dutyCycle(8); } delay(200); allOff(); } void rise() { int up[NPINS] = {13, 10, 6, 8, 1, 14, 12, 7, 3, 2, 4, 11, 9, 15, 0, 5}; allOff(); for(int p=0; p<NPINS; p++) { function[up[p]] = linear; wrapFunction[up[p]] = false; x[up[p]] = 0; dx[up[p]] = 8; dutyCycle(8); } delay(200); allOff(); } void upDown() { allUp(); allDown(); } void allUp() { if (lastVisualization == allUp) { return; } for(int p=0; p<NPINS; p++) { x[p] = y[p]; dx[p] = 1.0; function[p] = linear; } dutyCycle(256); } void allDown() { if (lastVisualization == allDown) { return; } for(int p=0; p<NPINS; p++) { x[p] = y[p]; dx[p] = -1.0; function[p] = linear; } dutyCycle(256); } void eachDown() { boolean done = true; if (lastVisualization == eachDown) { return; } for(int p=0; p<NPINS; p++) { x[p] = y[p]; dx[p] = -1; function[p] = linear; wrapFunction[p] = false; } done = false; while (!done) { dutyCycle(1); done = true; for(int p=0; p<NPINS; p++) { if (y[p] > 0) { done = false; break; } } } } void allOn() { for(int p=0; p<NPINS; p++) { function[p] = linear; x[p] = 255; dx[p] = 0; } dutyCycle(1); } void allOff() { for(int p=0; p<NPINS; p++) { function[p] = linear; x[p] = 0; dx[p] = 0; } dutyCycle(1); }

### Hardware Design Files

The schematic and board design are open source hardware, so you are welcome to have a board fabricated yourself! Download the design files here.

Hi.

Code is not compiled. Shows me this error:

TabascoLamp:9: error: ‘rise’ was not declared in this scope

void (*visualization)() = rise;

^

TabascoLamp:10: error: ‘rise’ was not declared in this scope

void (*lastVisualization)() = rise;

^

TabascoLamp:13: error: ‘throb’ was not declared in this scope

void (*visualizations[NVISUALIZATIONS])() = {throb, blinkRandom, onOff, upDown, rise, fall};

^

TabascoLamp:13: error: ‘blinkRandom’ was not declared in this scope

void (*visualizations[NVISUALIZATIONS])() = {throb, blinkRandom, onOff, upDown, rise, fall};

^

TabascoLamp:13: error: ‘onOff’ was not declared in this scope

void (*visualizations[NVISUALIZATIONS])() = {throb, blinkRandom, onOff, upDown, rise, fall};

^

TabascoLamp:13: error: ‘upDown’ was not declared in this scope

void (*visualizations[NVISUALIZATIONS])() = {throb, blinkRandom, onOff, upDown, rise, fall};

^

TabascoLamp:13: error: ‘rise’ was not declared in this scope

void (*visualizations[NVISUALIZATIONS])() = {throb, blinkRandom, onOff, upDown, rise, fall};

^

TabascoLamp:13: error: ‘fall’ was not declared in this scope

void (*visualizations[NVISUALIZATIONS])() = {throb, blinkRandom, onOff, upDown, rise, fall};

^

TabascoLamp:16: error: ‘linear’ was not declared in this scope

int (*allFunctions[NFUNCTIONS])(float x) = {linear, sine, exponential};

^

TabascoLamp:16: error: ‘sine’ was not declared in this scope

int (*allFunctions[NFUNCTIONS])(float x) = {linear, sine, exponential};

^

TabascoLamp:16: error: ‘exponential’ was not declared in this scope

int (*allFunctions[NFUNCTIONS])(float x) = {linear, sine, exponential};

^

exit status 1

‘rise’ was not declared in this scope

It compiles in Arduino 1.5.8 for me. I’m not sure why it doesn’t compile for you.