RGB-led Ljusstake
Reinkarnation av ljusstake
En gammal ljusstake, vars gamla trasiga glödlampor var hopplösa få tag
på, har legat och skräpat några år. Ett utmärkt julpyssel eller pyssel när som helst, för den delen.
RGB LED
En
RGB LED är 3 stycken lysdioder (led) monterade i samma höjle.
Det finns 2 varianter att hålla isär för att det skall fungera. Den ena
varianten har gemensam katod (-).
Den andra har gemensam anod (+). De som fungerar tillsammans med kretsen
WS2811, vilket är den kretsen jag har tillgång till (mer om det längre ner), har gemensam anod.
Skall du mata dessa med ström direkt bör du seriekoppla en resistor på
kanske 330Ω för 3.3 volt eller 500Ω för 5 volt på ingångarna för att
begränsa strömstyrkan, annars kan du ha sönder lysdioden (strömmen bör inte överstiga 10-20 mA genom lysdioden).
Det går eventuellt att driva RGB LED'n direkt från utgångarna på en
enkortsdator. Men du bör kolla upp detta - hur stor ström ungångarna kan
driva - för att inte riskera ha sönder ditt kontrollkort. Räkna med att
lysdiod (en RGB har 3 stycken) konsumerar kanske 10-15 mA eller något
sånt. I detta fall använder jag en styr/drivkrets för lysdioderna så jag
behöver inte bry mig om denna detalj.
WS2811, WS2812B, osv.

För att styra strömmen till alla lysdioder underlättar det med
kontrollkretsar för detta ändamål. Varje lysdiod innehåller 3 lysdioder
och 9 RGB-led blir alltså 27 regulatorer. En liten krets, WS2811, gör
problemet enklare.
Kretsen WS2811 finns monterad på ett bekvämt kretskort. Det gör det
marginellt enklare att löda fast kablar. Första kretsen tar seriedata på
sin in-pinne. Nästa krets i serie, dess insignal, matas med föregående
krets utsignal. Signallinan flödar alltså igenom kretsarna och varje
krets fångar upp sin adress. Denna lösning har fördelen att många LEDs
kan kopplas i serie men enbart 3 ledningar.
Montering
För ändamålet behövs väldigt många sladdar. Ett seriöst terapiarbete klippa lagom längder och skala dess ändar.

Sladdar löds fast på kretskorten där kretsen WS2811 sitter förmonterad. Här är det viktigt att hålla ordning på
färgerna annars kommer det snabbt urarta, då det är väldigt mycket
sladdar.

Jag använder mig av krympslang för att fixa isolering hela vägen.

Sladdinferno. Det rosa är också krympslang som jag sedan trär över kretsarna när allt är färdiglött.

Från en annan vinkel...
Kontrollknappar

Jag har räknat ut att det behövs åtminståne 3 kontrollknappar här. Dels
en knapp med vilken man kan välja ett program som ljusstaken använder.
Jag har spånat ihop en ide om att när man trycker på denna knapp 1 gång
så kan man låta första lampan lysa. Trycker man tre gånger kommer lampa 3
på ljusstaken lysa. osv. Tanken är sedan att man trycker så många
gånger att man kommer fram till det ljusprogram man önskar och sedan
trycker man inte mer. Efter t.ex. 2 sekunder (om man inte trycker något
mer) startar sedan programmet.
Två ytterligare reglage (potentiometrar) används till ljusstyrka
respektive valfritt. Valfritt är oftast hastigheten med vilket ett
program körs, förutom i något program där man istället väljer färg med detta reglage.
Potentiometrarna kopplas in som spänningsdelare med mitt-pinnen in på
lediga analogportar på arduino-kortet. På så vis kam man alltså justera
en spänningsnivå mellan 0 och 5 volt med potentiometern och värdet läses
av i mjukvaran. En bild på en testkoppling nedan.

På det
arduino kompatibla dccduino -kort jag använder för detta projekt finns det hål i vilka man kan löda fast kablarna vilket jag råkar gilla. OBS att ovan bild är en arduino orginal jag använde vid labbandet. Dessa kort är dock kompatibla så det är inget att grunna så mycket över.

Så stoppar vi in det i en låda. Det finns många arduino -lådor där ute.
Jag valde en där det fanns rum för en knapp och 2 potentiometrar.

Och så trycker vi ner allting och sätter på locket...
Hur tung last kan USB dra?
I skarpt läge kommer ljusstaken drivas av en nätdel men när jag utvecklar mjukvaran kommer ljusstaken drivas av USB-porten.
Om man kopplar ihop ett system med 10 stycken RGB LEDs, då bör man
instinktivt ställa sig frågan var dessa drar för ström tillsammans. Nu
är styrkretsarna inkopplade till GND och 5V på arduinokortet och dessa
är i sin tur kopplade till USBn. Så det är alltså USBn som sätter taket
för maxströmmen. Det är såklart bra, eftersom den strömmen är högre än
vad processorns utgångar tål, men samtidigt lite läskigt eftersom man
riskerar skada datorn USB är inkopplad till.
Lite närmare granskning av kopplingsschemat till arduino uno visar dock att konstruktörerna varit förutseende och placerat en
500 mA polysäkring
i serie med USBns strömmatning. Denna säkring begränsar strömmen till
ungefär 500 mA. Det råkar vara ungefär vad max ström tillgänglig är för en USB 2.0.
Lite om färger
Koden nedan bygger på att vi arbetar med HSV istället för RGB. För att begripa den enorma nyttan med att arbeta med HSV istället för RGB klicka här och läs lite om färger.
Program
Programmet använder
Adafruit_NeoPixel.h som innehåller kod för att enkelt kommunicera med WS2811/12 -kretsarna. Så det får du ladda ner först.
Kort beskrivning
WS281X -kretsarnas datasignal är kopplad till datapinne 6. Det kan
justeras som du ser i koden nedan. Potentiometrarnas mittpinne är
kopplad till analogingångarna A0 resp. A2. Knappen med vilket vi väljer
ljusprogram är kopplad mellan GND och datapinne 2.
Det finns 9 olika ljusprogram. Man väljer program genom att trycka på
knappen. Vill man se program 3, så trycker man 3 gånger. Vill man köra
program, så 9 trycker man 9 gånger. Beroende på program valt kan man
sedan justera ytterligare med potentiometrarna, den ena ljusstyrka och
den andra hastighet eller färg.
Det finns lite debugutskrifter till serieporten som ligger kvar. Programmet är inte jätteoptimerat, men det behövs inte heller.
Koden
// Ljusstake www.el.st
#include <Adafruit_NeoPixel.h>
#define PIN 6
#define NUMLIGHTS 10
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMLIGHTS, PIN, NEO_RGB + NEO_KHZ800);
volatile int pot1, pot2; // 2 trimpotentiometrar, analog 0 & 1
volatile int r1,g1,b1;
float lightstrength; // ljusstyrka regleras med pot1
volatile int nrOfPushButton = 0;
volatile int quarterSecCount = 0;
volatile int doubleSecCount = 0;
volatile int cnt;
volatile int run_program=0;
volatile int abort_program=0;
void setup()
{
pixels.begin(); // setup neopixel
Serial.begin(9600); // setup serial
// setup trigger interrupt signal change pin 2
pinMode(2, INPUT);
digitalWrite(2, HIGH);
attachInterrupt(0, keyPress, FALLING);
// setup timer interrupt
cli(); // interrupt disable, innan justering görs
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0; // räknaren noll från start
OCR1A = 3905; // compare match register (4 ggr/sek)
TCCR1B |= (1 << WGM12); // CTC-mode=ON, dvs TCNT1=0 när TCNT1>=OCR1A
TCCR1B |= (1 << CS12) | (1 << CS10); // 1024 prescaler
TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
sei(); // interrupt enable, alla inställningar klara
}
// kortare och tar hänsyn till ljusstyrka
void light(int i, int r, int g, int b)
{
r=(int)((float)r*lightstrength);
g=(int)((float)g*lightstrength);
b=(int)((float)b*lightstrength);
pixels.setPixelColor(i, pixels.Color(r,g,b));
}
// slack alla
void allDark()
{
for(int i=0;i<NUMLIGHTS;i++)
light(i,0,0,0);
pixels.show();
}
// tand 1 lampa
void lightOne(int i, int r, int g, int b)
{
allDark();
light(i,r,g,b);
pixels.show();
}
// tand alla
void lightAll(int r, int g, int b)
{
for(int i=0;i<NUMLIGHTS;i++)
light(i,r,g,b);
pixels.show();
}
// lampa rinner fram och tillbaka som torkarblad
void wipingLight()
{
for(int j=0;j<2;j++)
{
for(int i=0;i<NUMLIGHTS;i++)
{
lightOne(i,10,10,10);
pixels.show();
delay(30);
}
for(int i=NUMLIGHTS-2;i>0;i--)
{
lightOne(i,10,10,10);
pixels.show();
delay(30);
}
}
}
void wipeClean()
{
for(int i=0;i<NUMLIGHTS;i++)
{
light(i,10,10,10);
if(i)
light(i-1,0,0,0);
pixels.show();
delay(30);
}
for(int i=NUMLIGHTS-1;i>=0;i--)
{
lightOne(i,10,10,10);
pixels.show();
delay(30);
}
}
void randomLight(int green)
{
int a,b,c;
for(int i=0;(i<15)&&(!abort_program);i++)
{
a=random(20,255-green*2); // styrka
light(random(NUMLIGHTS),a,a,a);
pixels.show();
delay(random(50,200));
}
for(int i=0;(i<green)&&(!abort_program);i++)
{
a=random(30,255); // styrka
light(random(NUMLIGHTS),0,a,0);
pixels.show();
delay(random(50,200));
}
for(int i=0;(i<(15-green))&&(!abort_program);i++)
{
b=random(0,5); // styrka
light(random(NUMLIGHTS),b,b,b);
pixels.show();
delay(random(10,50));
}
}
void winter()
{
for(int i=0;(i<8)&&(!abort_program);i++)
{
for(int j=0;j<5;j++)
randomLight(0);
if(i<9)wipeClean();
}
}
void spring()
{
for(int i=0;i<30;i++)
randomLight(i/2);
for(int j=0;(j<13)&&(!abort_program);j++)
{
for(int i=0;(i<5)&&(!abort_program);i++)
{
light(5+i, 0,j,0);
light(4-i, 0,j,0);
pixels.show();
delay(40);
}
}
}
void summer_rainbow()
{
int i=0;
for(int x=0;(x<1800)&&(!abort_program);x++)
{
light(i++,sin(x/10)*128+1127,sin(x/10+60)*128+127,sin(x/10+120)*128+127);
pixels.show();
i%=NUMLIGHTS;
delay(100);
}
}
void summer_colors()
{
int i=0;
for(int x=0;(x<180)&&(!abort_program);x++)
{
light(i++,sin(x)*100+100,sin(x+90)*100+100,sin(x+180)*100+100);
pixels.show();
i%=NUMLIGHTS;
delay(100);
}
}
void autumn1()
{
int i=0;
for(int x=0;(x<1800)&&(!abort_program);x++)
{
light(i++,sin(x/10)*128+1127,sin(x/10+45)*128+127,sin(x/10+90)*128+127);
pixels.show();
i%=NUMLIGHTS;
delay(100);
}
}
void autumn2(int white)
{
int a,b,x;
for(int i=0;(i<(15-white))&&(!abort_program);i++)
{
a=random(3,8); // styrka
x=random(1800);
light(random(NUMLIGHTS),sin(x/10)*128+1127,sin(x/10+45)*128+127,sin(x/10+90)*128+127);
pixels.show();
delay(random(50,200));
}
for(int i=0;(i<white)&&(!abort_program);i++)
{
a=random(3,8); // styrka
light(random(NUMLIGHTS),a,a,a);
pixels.show();
delay(random(50,200));
}
for(int i=0;(i<(15-white))&&(!abort_program);i++)
{
light(random(NUMLIGHTS),0,0,0);
pixels.show();
delay(random(10,50));
}
}
void autumn()
{
autumn1();
for(int t=0;(t<10)&&(!abort_program);t++)
{
autumn2(t);
}
}
void winterdarkness()
{
for(int i=0;(i<NUMLIGHTS)&&(!abort_program);i++)
{
pixels.setPixelColor(i, pixels.Color(50,0,0));
pixels.show();
delay(10+pot2);
}
for(int i=0;(i<NUMLIGHTS)&&(!abort_program);i++)
{
pixels.setPixelColor(i, pixels.Color(0,0,50));
pixels.show();
delay(10+pot2);
}
for(int i=0;(i<NUMLIGHTS)&&(!abort_program);i++)
{
pixels.setPixelColor(i, pixels.Color(0,50,0));
pixels.show();
delay(10+pot2);
}
}
void seasons()
{
while(1)
{
winter();
spring();
summer_colors();
summer_rainbow();
autumn();
}
}
void whiteLight()
{
lightAll(255,255,255);
}
void colorFlame()
{
r1+=random(12)-6; if(r1>255){r1=255;}; if(r1<0){r1=255;};
g1+=random(8)-6; if(g1>128){g1=128;}; if(g1<0){g1=128;};
b1+=random(9)-6; if(b1>128){b1=128;}; if(b1<0){b1=128;};
light(random(NUMLIGHTS),r1,g1,b1);
pixels.show();
delay(10);
}
void gradient()
{
int i;
while(1)
for(int x=0;(x<18000)&&(!abort_program);x++)
{
light(i++,sin(x/100)*128+1127,sin(x/100+45)*128+127,sin(x/100+90)*128+127);
pixels.show();
i%=NUMLIGHTS;
delay(100);
}
}
int * HSVtoRGB(float h, float s, float v)
{
static int vr[3];
int i;
float r,g,b,f,p,q,t;
i=(int)(h*6);
f=h*6-(float)i;
p=v*(1-s);
q=v*(1-f*s);
t=v*(1-(1-f)*s);
switch (i%6)
{
case 0: r=v, g=t, b=p; break;
case 1: r=q, g=v, b=p; break;
case 2: r=p, g=v, b=t; break;
case 3: r=p, g=q, b=v; break;
case 4: r=t, g=p, b=v; break;
case 5: r=v, g=p, b=q; break;
}
vr[0]=(int)(r*255);
vr[1]=(int)(g*255);
vr[2]=(int)(b*255);
return vr;
}
void selectColor()
{
int *v;
v=HSVtoRGB((float)(pot2-100)/(1024-100),1.0,0.4) ;
lightAll(v[0],v[1],v[2]);
pixels.show();
}
void surfontherainbow()
{
int *v;
int l;
while(1)
for(int i=0;(i<1000)&&(!abort_program);i++)
{
for(l=0;(l<NUMLIGHTS)&&(!abort_program);l++)
{
v=HSVtoRGB((float)((i+l*10)%1000)/1000,1.0,0.4) ;
light(l,v[0],v[1],v[2]);
}
pixels.show();
delay((10+pot2)/5);
}
}
void colortransition()
{
int *v;
while(1)
for(int i=0;(i<10000)&&(!abort_program);i++)
{
v=HSVtoRGB((float)i/10000,1.0,0.4) ;
lightAll(v[0],v[1],v[2]);
pixels.show();
delay((10+pot2)/20);
}
}
// succesivt stigande ljusstyrka
void pulstransition()
{
int *vr;
while(1)
for(int v=30;v<100;v++)
for(int i=0;i<100;i++)
{
if(abort_program)
return;
vr=HSVtoRGB((float)i/100,1.0,(float)v/100) ;
lightAll(vr[0],vr[1],vr[2]);
pixels.show();
delay((10+pot2)/5);
}
}
void showProgram(int p)
{
if(abort_program)
return;
switch(p)
{
case 1:whiteLight(); break;
case 2:selectColor(); break;
case 3:colorFlame(); break;
case 4:gradient(); break;
case 5:winterdarkness(); break;
case 6:colortransition(); break;
case 7:pulstransition(); break;
case 8:surfontherainbow(); break;
case 9:seasons(); break;
}
}
void gauge(int v)
{
for(cnt=0;cnt<NUMLIGHTS;cnt++)
{
light(cnt, 0,0,0);
}
light((v-1)%NUMLIGHTS, 255,(v>NUMLIGHTS)?0:255,(v>NUMLIGHTS)?255:0);
pixels.show();
}
void keyPress()
{
abort_program=1;
// åtminstone 3x0.25 sek sedan senaste tryck
if(quarterSecCount>2)
{
quarterSecCount=0;
// do whatever you wanna do when key press, in this case we just count
nrOfPushButton++;
nrOfPushButton%=10;
if(nrOfPushButton==0)
{
nrOfPushButton=1;
}
gauge(nrOfPushButton);
doubleSecCount=0;
}
}
// anropas 4 ggr per sekund
ISR(TIMER1_COMPA_vect)
{
pot1=analogRead(0); // ljusstyrka
pot2=analogRead(2); // funktionsvred
lightstrength=((float)pot1)/1024.0;
if(nrOfPushButton)
{
gauge(nrOfPushButton);
}
quarterSecCount++;
if(!(quarterSecCount%8))
{
doubleSecCount++;
if((doubleSecCount>0)&&(nrOfPushButton>0))
{
abort_program=0;
run_program=nrOfPushButton;
nrOfPushButton=0;
//showProgram(p);
}
}
Serial.print("pot1: ");
Serial.print(pot1);
Serial.print(" pot2: ");
Serial.print(pot2);
Serial.print(" strength: ");
Serial.println(lightstrength);
Serial.println(doubleSecCount);
Serial.print(" program: ");
Serial.println(nrOfPushButton);
}
void loop()
{
showProgram(run_program);
}
Ref.
WS2811 - WS2812B
http://www.adafruit.com/datasheets/WS2811.pdf
http://www.adafruit.com/datasheets/WS2812B.pdf
https://github.com/adafruit/Adafruit_NeoPixel