Introduction: Arduino Multi-mode Lamp With Soft Touch Switch
In this Arduino-based project, we will build a lamp with multiple light displays: color sequencer, dimming light, color chaser, firelight - all selected by a touch bar on the circuit board.
All the functions are done in software, including the touch sensor, which is a unique feature.
All the functions are done in software, including the touch sensor, which is a unique feature.
Step 1: What Is Needed?
We will be going the minimalist way for this project, filling the board with just a microchip, the LEDs, a handful of resistors and some capacitors, all for under $10, along with the necessary connecting hardware.
The circuit will be using 3 RGB LEDs. These are common-anode Piranha type available here and contains three LEDs within its body. Each color will need a single dropping resistor (220-ohm for green and blue and 330-ohm for Red). We can also add a small LED with a 1k-ohm as an indicator.
The IC we are using is an ATMega-328 microchip, available for about $5 here You will also need a 16Mhz resonator for about 35c, also available at the same site.
The development and testing of the software is done using the Arduino system, so a suitable 'host' is necessary. I've used an Arduino 'Nano', a Boarduino and a RBBB board and they all work fine.
The circuit will be using 3 RGB LEDs. These are common-anode Piranha type available here and contains three LEDs within its body. Each color will need a single dropping resistor (220-ohm for green and blue and 330-ohm for Red). We can also add a small LED with a 1k-ohm as an indicator.
The IC we are using is an ATMega-328 microchip, available for about $5 here You will also need a 16Mhz resonator for about 35c, also available at the same site.
The development and testing of the software is done using the Arduino system, so a suitable 'host' is necessary. I've used an Arduino 'Nano', a Boarduino and a RBBB board and they all work fine.
Step 2: Getting Started
It is a very good idea to put the microchip in a socket. Here, I've used 2 x 16-pin sockets end-to-end, because that is what I have available... The ATMel chip only has 28-pins so we'll have a few empty sockets on the end.
In this picture, the LEDs are along the bottom, with a resistor for each of the primary colors.
The little pushbutton on the left is for the Reset, although this is not strictly needed. The yellow blob in the center is the resonator.
After I've done the preliminary wiring, the circuit (and programming) is tested through jumpers connected to the 'host', an RBBB (Really Bare Bones Board), also from Modern Devices. This lets me make sure the wiring is correct before we commit the Microchip.
The process is quite straightforward - run / test with the host, then simply transplant the IC over to the circuit board.
In this picture, the LEDs are along the bottom, with a resistor for each of the primary colors.
The little pushbutton on the left is for the Reset, although this is not strictly needed. The yellow blob in the center is the resonator.
After I've done the preliminary wiring, the circuit (and programming) is tested through jumpers connected to the 'host', an RBBB (Really Bare Bones Board), also from Modern Devices. This lets me make sure the wiring is correct before we commit the Microchip.
The process is quite straightforward - run / test with the host, then simply transplant the IC over to the circuit board.
Step 3: A Quick Test...
If you're really impatient, you can actually run the program with just the IC, and the resonator! Just add a 5v source and Presto! There is light!
Step 4: The Circuit
The picture clearly shows the point-to-point wiring used.
A major effort-saver is the use of Teflon wire, which does not melt even when routed close to soldered parts. Teflon wire is also available silver-plated which allows me to use thinner wires (#28) and still handle the current. These wires can be found here on eBay.
The 6-pin header in the back is so I can connect a USB-serial port ("USB BUB") and make programming changes directly to the chip.
The wiring at the front is used as the touch sensor. The program measures the voltage drop between the wires and can tell if it is touched. The duration is measured and we can tell if it is a tap, a press or a hold, and the program uses it to control the light patterns.
A major effort-saver is the use of Teflon wire, which does not melt even when routed close to soldered parts. Teflon wire is also available silver-plated which allows me to use thinner wires (#28) and still handle the current. These wires can be found here on eBay.
The 6-pin header in the back is so I can connect a USB-serial port ("USB BUB") and make programming changes directly to the chip.
The wiring at the front is used as the touch sensor. The program measures the voltage drop between the wires and can tell if it is touched. The duration is measured and we can tell if it is a tap, a press or a hold, and the program uses it to control the light patterns.
Step 5: The 'Sketch', or Program.
Currently the lamp cycles through 4 different modes: Sequencing through all 4000 color combinations; different levels of white light; a color chaser where each LED morphs into all possible shades; and a firelight simulator (seen below), where the program tries to emulate the flickering effects of a fireplace.
It's only taking up 5k-bytes of the 32k-byte program space, so there is room for a lot more features.
I'm constantly adding things to it, so send email to: qs (at) quantsuff.com and I'll send you the latest version of the sketch.
Firefly function added (progMode==4): Emulates the courtship behavior of the species. Each LED represents a Male, each with his own flashing sequence. Once in a while, all 3 LEDs go off, which is the Female sequence to get the males excited.
Per your requests, I will post the most current program listing in the following step.
It's only taking up 5k-bytes of the 32k-byte program space, so there is room for a lot more features.
I'm constantly adding things to it, so send email to: qs (at) quantsuff.com and I'll send you the latest version of the sketch.
Firefly function added (progMode==4): Emulates the courtship behavior of the species. Each LED represents a Male, each with his own flashing sequence. Once in a while, all 3 LEDs go off, which is the Female sequence to get the males excited.
Per your requests, I will post the most current program listing in the following step.
Step 6: The Latest Sketch.
/*
int matrix[5]={
0660,0626,0466,0,0}
; // Rainbow display
int matrix02[5][2] = { // Morphing routine & firelight
{
511,7 }
,{
511,56 }
,{
511,504 }
,{
511,0 }
,{
511,504 }
};
unsigned int firefly[4]= {
0xdd42, 0xff00, 0x8a00, 0x2200} ;
/* Fireflies:
// For adjusting stepping sequences
long bigDelay= 300, bigD;
int fsuitors=0, i,j,val, LED, c0, c1,r0, g0, b0, r1, g1, b1, white=7;
int progMode=0 ; // The switch...case selector (99 is TEST)
int controlPin=12, sw02 = 0; // Switch status on A02
unsigned long dttimer02=0, timer02=0, ffTimer=0; // watchdog timers
int threshold= 100; // milliseconds for a 'touch', triple for 'hold'
void setup() {
for (int i=2; i<=7; i++) {
pinMode(i,OUTPUT);
digitalWrite(i,i<5);
}
randomSeed(analogRead(0));
pinMode(12,OUTPUT); // Our master touch switch control
digitalWrite(12,LOW);
analogReference(INTERNAL); // Boost (touch) sensitivity! Aref is offlimits!
for (i=1; i<4; i++) { // Set up fireflies' routine
j= random(2,4); // Type of male
j+= ((j+i-3)<<2) ; // Stamina (lack of) 0-7
firefly[i]= (j<<8) | (j<<1) |
((i+3)<<13) ; // Energy (brightness)
}
}
void loop() {
bigD= bigDelay+millis(); // Calc timeout
switch (progMode) {
case 0:
do {
for (LED=0; LED<=2; LED++)
writeLED(LED,matrix[LED]);
}
while (bigD>=millis());
if ((sw02&7)==0) { // Hold is HOLD!
matrix[0]= matrix[0]++ & 511 ; // funny math
i= (matrix[1] + 8) ; // Increase green
if (i>511) i=(i&7) + 1 ;
matrix[1]= i;
i= (matrix[2] + 64) ; // Increase blue
if (i>511) i= (i&63) + 1 ;
matrix[2]= i;
}
break ;
case 1:
{
r1= (white<<6)|(white<<3)|white ;
while (bigD>=millis())
for (LED=0; LED<=2; LED++)
writeLED(LED,r1);
if ((sw02&7)!=0) {
white--;
if (white<=0) white=7;
}
break;
}
case 2:
{
for (LED=0; LED<=2; LED++) {
if ((sw02&7)==0) { // Hold is HOLD!
c0= matrix02[LED][0] ;
c1= matrix02[LED][1] ;
if (c0!=c1) {
r0= c0 & 7;
g0= (c0>>3) & 7;
b0= (c0>>6) & 7;
r1= c1 & 7;
g1= (c1>>3) & 7;
b1= (c1>>6) & 7;
r1 -= constrain(r1-r0,-1,1);
g1 -= constrain(g1-g0,-1,1);
b1 -= constrain(b1-b0,-1,1);
matrix02[LED][1]= (b1<<6) | (g1<<3) | r1;
}
else
matrix02[LED][0]= random(0,512) ; // New target lights
}
}
while (bigD>=millis())
for (LED=0; LED<=2; LED++)
writeLED(LED,matrix02[LED][1]);
break;
}
case 3: // Log fire
{
for (LED=0; LED<=2; LED++) {
if ((sw02&7)==0) { // Hold is HOLD!
c0= matrix02[LED][0] ;
c1= matrix02[LED][1] ;
if (c0!=c1) {
r0= c0 & 7;
g0= (c0>>3) & 7;
b0= (c0>>6) & 7;
r1= c1 & 7;
g1= (c1>>3) & 7;
b1= (c1>>6) & 7;
r1 -= constrain(r1-r0,-1,1);
g1 -= constrain(g1-g0,-1,1);
b1 -= constrain(b1-b0,-1,1);
matrix02[LED][1]= (b1<<6) | (g1<<3) | r1;
}
else {
if (LED==1) {
r1=random(4,8);
g1=r1-2;
b1=0;
}
else {
r1= random(2,7);
g1= r1-2;
b1= 0;
}
matrix02[LED][0]= (b1<<6) | (g1<<3) | r1 ; // New target lights reds*greens
}
}
}
bigD= millis()+130;
do{
for (LED=0; LED<=2; LED++)
writeLED(LED,matrix02[LED][1]);
}
while (bigD>=millis()) ;
break;
}
case 4: // Fireflies
{
if (ffTimer<millis()) { // must wait for elapsed time
for (i=0; i<4; i++) {
int ff0= firefly[i];
int ff1= ff0>>8;
ff0= ff0 & 127;
int ff= ff0&7;
if (ff>0) { // Flash on
j= (ff1>>2)&070 ; //Pre-load green
if ((ff1&3)!=1) { //A Male
writeLED(i-1,j);
writeLED(i-1,j);
firefly[i]-- ;
fsuitors++;
}
else // A female!
if (fsuitors==0) {
j+=3;
writeLED(0,j);
writeLED(1,j);
writeLED(2,j);
writeLED(0,j);
writeLED(1,j);
writeLED(2,j); // Females show a longer, yellower pulse
firefly[i]-- ;
i= 99; // Early exit
}
}
else { // No flash
if (i==1) fsuitors=0;
j= (ff0>>3)&15;
if (j==0) //Wait expired
firefly[i]= (firefly[i] & 0xff00) | 0x40 | ((ff1&31)<<1) ;
else
firefly[i]-= 8;
}
}
ffTimer=millis()+450 ;
}
break;
}
case 99: // Test mode...
{
i= ((sw02>1) & 1)<<2 | // check for tap
((sw02>2) & 1)<<6 ;
writeLED(2,i) ; //tap=red + hold=blue
break;
}
default:
progMode=0;
break;
}
/*
if (readTouch(2)) { // We see something on pin 2!
if ((sw02&1)==0) { // First time 'round
digitalWrite(13,HIGH); // so we SHOW smth.
sw02= 1; // Flag the touch
timer02= millis(); //Clock first touch
}
else { // 1-bit already set: we've been here before.
unsigned long t= millis()-timer02; // but how long ago?
if ((sw02&2)==2) { // Look at 'hold' only if 'press' is set
if (t>750) {
sw02= sw02 | 4;
if (t>8000) progMode=99;
}
}
else if (t>=200) sw02= sw02 | 2; // Set 'Press', more differentiation
}
}
else if (sw02&&1) { // Process key off
if ((millis()-timer02)>100) // Allow some 'give'before calling it quits
{
digitalWrite(13,LOW); // Show it
if ((sw02&6)==2) { //Process a 'tap'
unsigned long t= millis()-dttimer02; //
dttimer02= millis(); // Allows us to keep 'tapping'
if (t<600) { // 2 taps within time limit (600ms)
sw02= 0x10 ; // Turn on double tap'
progMode++ ; // Next Program
}
}
else {
sw02= sw02 & 0xff00 ;
timer02= 0;
}
}
}
}
# define lowThreshold 180 // Low is 180mV
# define highThreshold 450 // 450mV [600] - Skin resistance is 8Meg
boolean readTouch(int pin) {
int i=analogRead(pin);
i=analogRead(pin); //Ignore 1st reading
// if (i<lowThreshold) i=analogRead(pin); // Noise rejection
if (i<lowThreshold) {
digitalWrite(controlPin,HIGH);
if (progMode==99) writeLED(0,02); // Test: octal red
i=analogRead(pin);
digitalWrite(controlPin,LOW);
if (i>highThreshold) {
if (progMode==99) writeLED(0,020); // Test: octal yellow
//return (analogRead(pin)<lowThreshold);
return true;
}
else return false ;
} // Test passed - key pressed
else return false;
}
#define waitFactor 6 // Brightness factor
void writeLED(int LED, int value) { // LED== 0(D7), 1(D6), 2(D5)
//value is 3bits for r[lsb],g,b
digitalWrite(7-LED,HIGH); // Bring anode high
int r= value & 7;
int g= (value>>3) & 7;
int b= (value>>6) & 7;
if (r>0) {
digitalWrite(2,LOW);
delayMicroseconds(waitFactor<<r);
}
digitalWrite(2,HIGH);
delayMicroseconds(waitFactor<<(7-r));
if (g>0) {
digitalWrite(3,LOW);
delayMicroseconds(waitFactor<<g);
}
digitalWrite(3,HIGH);
delayMicroseconds(waitFactor<<(7-g));
if (b>0) {
digitalWrite(4,LOW);
delayMicroseconds(waitFactor<<b);
}
digitalWrite(4,HIGH);
delayMicroseconds(waitFactor<<(7-b));
digitalWrite(7-LED,LOW);
}
- Tri-LED (piranha) matrix & touch test
- (C) Copyright 2009 qs@quantsuff.com
- Modules (selected by progMode)
- 0 - Sequence
- 1 - White light. Dims while on 'hold'.
- 2 - Morphing colors. Random lights slowly blending.
- 3 - Firelight. Flickering flames (hard to do when there's no real yellow!)
- 4 - Fireflies. Emulate the courting ritual of the species.
- 20mA integrated color LEDs - COMMON anode!!
- D02 - red (LOW to turn on!)
- D03 - green
- D04 - blue
- D05 - rightmost LED (HIGH to turn on)
- D06 - center LED
- D07 - leftmost LED
int matrix[5]={
0660,0626,0466,0,0}
; // Rainbow display
int matrix02[5][2] = { // Morphing routine & firelight
{
511,7 }
,{
511,56 }
,{
511,504 }
,{
511,0 }
,{
511,504 }
};
unsigned int firefly[4]= {
0xdd42, 0xff00, 0x8a00, 0x2200} ;
/* Fireflies:
- msb - (7:5) Intensity (0-7)
- (4:2) Delay in 1/2sec units
- (1:0) Type of bug (1=female, 2,3 = males)
// For adjusting stepping sequences
long bigDelay= 300, bigD;
int fsuitors=0, i,j,val, LED, c0, c1,r0, g0, b0, r1, g1, b1, white=7;
int progMode=0 ; // The switch...case selector (99 is TEST)
int controlPin=12, sw02 = 0; // Switch status on A02
unsigned long dttimer02=0, timer02=0, ffTimer=0; // watchdog timers
int threshold= 100; // milliseconds for a 'touch', triple for 'hold'
void setup() {
for (int i=2; i<=7; i++) {
pinMode(i,OUTPUT);
digitalWrite(i,i<5);
}
randomSeed(analogRead(0));
pinMode(12,OUTPUT); // Our master touch switch control
digitalWrite(12,LOW);
analogReference(INTERNAL); // Boost (touch) sensitivity! Aref is offlimits!
for (i=1; i<4; i++) { // Set up fireflies' routine
j= random(2,4); // Type of male
j+= ((j+i-3)<<2) ; // Stamina (lack of) 0-7
firefly[i]= (j<<8) | (j<<1) |
((i+3)<<13) ; // Energy (brightness)
}
}
void loop() {
bigD= bigDelay+millis(); // Calc timeout
switch (progMode) {
case 0:
do {
for (LED=0; LED<=2; LED++)
writeLED(LED,matrix[LED]);
}
while (bigD>=millis());
if ((sw02&7)==0) { // Hold is HOLD!
matrix[0]= matrix[0]++ & 511 ; // funny math
i= (matrix[1] + 8) ; // Increase green
if (i>511) i=(i&7) + 1 ;
matrix[1]= i;
i= (matrix[2] + 64) ; // Increase blue
if (i>511) i= (i&63) + 1 ;
matrix[2]= i;
}
break ;
case 1:
{
r1= (white<<6)|(white<<3)|white ;
while (bigD>=millis())
for (LED=0; LED<=2; LED++)
writeLED(LED,r1);
if ((sw02&7)!=0) {
white--;
if (white<=0) white=7;
}
break;
}
case 2:
{
for (LED=0; LED<=2; LED++) {
if ((sw02&7)==0) { // Hold is HOLD!
c0= matrix02[LED][0] ;
c1= matrix02[LED][1] ;
if (c0!=c1) {
r0= c0 & 7;
g0= (c0>>3) & 7;
b0= (c0>>6) & 7;
r1= c1 & 7;
g1= (c1>>3) & 7;
b1= (c1>>6) & 7;
r1 -= constrain(r1-r0,-1,1);
g1 -= constrain(g1-g0,-1,1);
b1 -= constrain(b1-b0,-1,1);
matrix02[LED][1]= (b1<<6) | (g1<<3) | r1;
}
else
matrix02[LED][0]= random(0,512) ; // New target lights
}
}
while (bigD>=millis())
for (LED=0; LED<=2; LED++)
writeLED(LED,matrix02[LED][1]);
break;
}
case 3: // Log fire
{
for (LED=0; LED<=2; LED++) {
if ((sw02&7)==0) { // Hold is HOLD!
c0= matrix02[LED][0] ;
c1= matrix02[LED][1] ;
if (c0!=c1) {
r0= c0 & 7;
g0= (c0>>3) & 7;
b0= (c0>>6) & 7;
r1= c1 & 7;
g1= (c1>>3) & 7;
b1= (c1>>6) & 7;
r1 -= constrain(r1-r0,-1,1);
g1 -= constrain(g1-g0,-1,1);
b1 -= constrain(b1-b0,-1,1);
matrix02[LED][1]= (b1<<6) | (g1<<3) | r1;
}
else {
if (LED==1) {
r1=random(4,8);
g1=r1-2;
b1=0;
}
else {
r1= random(2,7);
g1= r1-2;
b1= 0;
}
matrix02[LED][0]= (b1<<6) | (g1<<3) | r1 ; // New target lights reds*greens
}
}
}
bigD= millis()+130;
do{
for (LED=0; LED<=2; LED++)
writeLED(LED,matrix02[LED][1]);
}
while (bigD>=millis()) ;
break;
}
case 4: // Fireflies
{
if (ffTimer<millis()) { // must wait for elapsed time
for (i=0; i<4; i++) {
int ff0= firefly[i];
int ff1= ff0>>8;
ff0= ff0 & 127;
int ff= ff0&7;
if (ff>0) { // Flash on
j= (ff1>>2)&070 ; //Pre-load green
if ((ff1&3)!=1) { //A Male
writeLED(i-1,j);
writeLED(i-1,j);
firefly[i]-- ;
fsuitors++;
}
else // A female!
if (fsuitors==0) {
j+=3;
writeLED(0,j);
writeLED(1,j);
writeLED(2,j);
writeLED(0,j);
writeLED(1,j);
writeLED(2,j); // Females show a longer, yellower pulse
firefly[i]-- ;
i= 99; // Early exit
}
}
else { // No flash
if (i==1) fsuitors=0;
j= (ff0>>3)&15;
if (j==0) //Wait expired
firefly[i]= (firefly[i] & 0xff00) | 0x40 | ((ff1&31)<<1) ;
else
firefly[i]-= 8;
}
}
ffTimer=millis()+450 ;
}
break;
}
case 99: // Test mode...
{
i= ((sw02>1) & 1)<<2 | // check for tap
((sw02>2) & 1)<<6 ;
writeLED(2,i) ; //tap=red + hold=blue
break;
}
default:
progMode=0;
break;
}
/*
- Check for key pressed
if (readTouch(2)) { // We see something on pin 2!
if ((sw02&1)==0) { // First time 'round
digitalWrite(13,HIGH); // so we SHOW smth.
sw02= 1; // Flag the touch
timer02= millis(); //Clock first touch
}
else { // 1-bit already set: we've been here before.
unsigned long t= millis()-timer02; // but how long ago?
if ((sw02&2)==2) { // Look at 'hold' only if 'press' is set
if (t>750) {
sw02= sw02 | 4;
if (t>8000) progMode=99;
}
}
else if (t>=200) sw02= sw02 | 2; // Set 'Press', more differentiation
}
}
else if (sw02&&1) { // Process key off
if ((millis()-timer02)>100) // Allow some 'give'before calling it quits
{
digitalWrite(13,LOW); // Show it
if ((sw02&6)==2) { //Process a 'tap'
unsigned long t= millis()-dttimer02; //
dttimer02= millis(); // Allows us to keep 'tapping'
if (t<600) { // 2 taps within time limit (600ms)
sw02= 0x10 ; // Turn on double tap'
progMode++ ; // Next Program
}
}
else {
sw02= sw02 & 0xff00 ;
timer02= 0;
}
}
}
}
# define lowThreshold 180 // Low is 180mV
# define highThreshold 450 // 450mV [600] - Skin resistance is 8Meg
boolean readTouch(int pin) {
int i=analogRead(pin);
i=analogRead(pin); //Ignore 1st reading
// if (i<lowThreshold) i=analogRead(pin); // Noise rejection
if (i<lowThreshold) {
digitalWrite(controlPin,HIGH);
if (progMode==99) writeLED(0,02); // Test: octal red
i=analogRead(pin);
digitalWrite(controlPin,LOW);
if (i>highThreshold) {
if (progMode==99) writeLED(0,020); // Test: octal yellow
//return (analogRead(pin)<lowThreshold);
return true;
}
else return false ;
} // Test passed - key pressed
else return false;
}
#define waitFactor 6 // Brightness factor
void writeLED(int LED, int value) { // LED== 0(D7), 1(D6), 2(D5)
//value is 3bits for r[lsb],g,b
digitalWrite(7-LED,HIGH); // Bring anode high
int r= value & 7;
int g= (value>>3) & 7;
int b= (value>>6) & 7;
if (r>0) {
digitalWrite(2,LOW);
delayMicroseconds(waitFactor<<r);
}
digitalWrite(2,HIGH);
delayMicroseconds(waitFactor<<(7-r));
if (g>0) {
digitalWrite(3,LOW);
delayMicroseconds(waitFactor<<g);
}
digitalWrite(3,HIGH);
delayMicroseconds(waitFactor<<(7-g));
if (b>0) {
digitalWrite(4,LOW);
delayMicroseconds(waitFactor<<b);
}
digitalWrite(4,HIGH);
delayMicroseconds(waitFactor<<(7-b));
digitalWrite(7-LED,LOW);
}