Start Elkretssimulator Karnaughdiagram Quine McCluskey


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