Introduktion till programmering, del 12- Julspecial med X-mas lights

Onsdag igen, den läsvillige har med största sannolikhet listat ut vad det handlar om idag! 😀

Introduktion till programmering

Förra veckan hade vi en liten julspecial där vi gick igenom programmet vi använder för att välja vinnare i vår adventskalendertävling. Vi gick också igenom hur man kan testa sin kod för att verifiera att den fungerar som önskat.

Idag ska vi gå igenom hur man kan programmera om sin X-mas lights by m.nu, och detta gör vi genom att lägga till ett nytt läge vi kallar ”Snow Glitter”!

Om du vill hämta originalkoden för att läsa finns den här:

X-Mas lights by m.nu - arduino sketch (540 nedladdningar)

Vi börjar med att gå igenom de viktigaste funktionerna i koden, och går sen vidare på det nya läget!

Kodens uppbyggnad

Som kanske tidigare nämnts har en Arduino två lägen, ”setup()” och ”loop()”. Det första läget körs en gång så där gör man alla inställningar med mera som bara ska göras en gång. Därefter går Arduinon in i loop(), där den stannar tills den stängs av. De rader i setup() som är av intresse är ”strip.begin()”, som säger till slingan att vi ska använda den, och ”strip.show()” som uppdaterar slingan med de färgvärden man har valt (i setup() har inga färger valts, så inga färger kommer att skickas ut).

Det finns också ett antal hjälpfunktioner som används i loop(), bland annat button() som läser av knappen och om den tryckts ner, ökar ”buttonPushCounter” som avgör vilket läge slingan kör. Tänkte inte gå in så mycket på hjälpfunktionerna, utan vi tittar på hur ett av de simplare lägena fungerar: det första läget, som skickar 10 vita LEDs genom hela slingan. Därmed får vi titta på underfunktionen läget använder, ”colorRun”:

”void” betyder att funktionen inte skickar tillbaka någon data. Invärdena c, l och exitButton anger färg, antal LEDs på rad och hur många knapptryckningar som krävs för att hamna i det här läget (detta då funktionen används på fler ställen och därför har olika värden på ”buttonPushCounter”).

Därefter körs en for-loop som sätter angiven färg på varje lampa, och om fler lampor än l lyser så släcker loopen också efterföljande lampor! Det vi inte tagit upp redan som är intressant här är ”strip.setPixelColor(i, c)”, som sätter färg ”c” på LED ”i”. För att förändringen ska gå igenom måste man köra strip.show(), så ska man ändra flera lampor kan det vara idé att ändra dem först och därefter köra show()-kommandot.

Också delay(wait) är intressant. Först görs en analogRead på den pinne som potentiometern är ansluten till, och det värde som utvinns används för att bestämma hur lång paus som görs efter varje iteration av funktionen. Så länge man inte trycker på knappen fortsätter loopen som anropar denna funktion att köra, och då kommer detta göras om i all oändlighet!

Nu när vi vet vilka funktioner som är viktigast för att kunna skapa sitt eget läge, ska vi definiera hur vårt läge ska fungera!

Förberedelser

Det nya läget vill vi ska långsamt tända och därefter släcka ett antal LEDs slumpmässigt. Detta kommer skapa en effekt som skulle kunna liknas vid snö som glittrar, därav namnet ”snow glitter”. För enkelhetens skull låter vi den första slingan speglas i efterföljande slingor om man använder flera.

RGB-LED:sen vi använder har 255 färglägen per färg (0-254). Dessa anger man som heltal (integers) som sen kombineras av funktionen ”Color()” till en uint32_t som innehåller alla dessa tre värden. uint32_t är en ”Unsigned integer”, vilket innebär att den enbart lagrar positiva tal. Just denna är också 32 bitar, vilket räcker för att lagra de tre färgerna på 8 bit vardera. Färgkombinationerna blir alltså 0-0-0 när dioden är släckt, och 254-254-254 när den lyser maximalt med färgen vit.

Antal LEDs då? En tredjedel av slingan borde vara lagom. För att alla inte ska tändas och släckas med samma sekvens vill vi också att de börjar på olika ställen i ”faden”, så smidigt är nog att hälften tänds och hälften släcks när läget påbörjas. Vi vill alltså att en framslumpad tredjedel av alla LEDs på slingan ska alterneras mellan fade-in och fade-out så det finns lika många av varje. Utöver detta ska de börja på olika ställen i sekvensen.

Enklaste sättet att hålla reda på var dioderna är, vilket håll de fade:ar och vilka dioder som körs just nu är att lagra all denna information i en varsin array (tror jag, i alla fall).

Här är den sekvens i vilken arbetet ska utföras:

  • Slumpa fram 1/3 av slingans dioder (som vi startar med), spara i en array.
  • Slumpa fram lika många booleans som håller koll på om det är fade in eller fade out på varje diod
  • Och för att inte alla ska börja på samma ljus, slumpa fram ett startvärde mellan 0 och MAX
  • I loop(), gå igenom varje diod, öka eller minska dess ljusstyrka baserat på motsvarande boolean-variabel. Om det är flera slingor, kör en for-loop som gör samma ändring på t.ex. 1, 51, 101 osv. Om värdet är större än MAX, vänd boolean-värdet så ljuset börjar minska nästa varv. Om värdet är mindre än 0, slumpa fram en ny diod som ska börja lysa istället

Mer avancerat än så blir det inte! För att förenkla koden kommer vi skapa hjälpfunktioner för att slumpa fram en ny diod, avgöra om en slumpad diod redan finns i vår array och i så fall slumpa fram en ny, samt för att få ut ett färgvärde på nuvarande steg på dioden.

Koden

Det första vi måste göra är göra några modifikationer högst upp i koden, samt skapa våra arrayer. Dessa får vara globala för att vi ska kunna läsa och skriva:

 numStrands anger antal slingor medan strandLEDs anger hur många LED-lampor varje slinga har. Detta för att beräkningarna i funktionerna ska baseras på detta, och bli korrekta. numLED är det totala antalet LEDs, vilket också används av vissa funktioner. Vi har satt dessa till const för att motverka gnäll från kompilatorn, vad det betyder är att dessa värden inte förändras under tiden koden körs.

Därefter definierar vi hur stor del av dioderna som ska lysa åt gången i vårt nya läge. 50/3 blir ~16,7 men värdet trunkeras till 16 eftersom vi räknar med heltal. Även detta värde är konstant och kan inte ändras under körning.

Därefter definierar vi våra tre arrayer. På Arduino görs det genom att skapa en variabel som vanligt, men sätta [] efter variabelnamnet. Det värde som anges inuti klamrarna är antalet platser i arrayen, så i detta fall är det ”diodes” antal, och diodes är 16… ni fattar. 🙂

Inga konstigheter, eller hur!? Nu tittar vi på hjälpfunktionerna. En för att slumpa fram en ny diod, en för att kontrollera om en diod redan finns bland de som lyser, och en som skickar tillbaka färgvärde baserat på vilket steg dioden är på i sekvensen.

WhiteFade(), som räknar ut faden, är väldigt simpel. Den multiplicerar invärdet med 6, och använder den befintliga Color()-funktionen för att skicka tillbaka en färg. Varför just 6? Därför att just i detta fall har jag valt att använda 40 steg i fade-in istället för 254, för att läget inte ska gå för långsamt. Det betyder att det högsta värdet blir 6*40 = 240. Spelar inte så stor roll, tror jag.

Contains() är en klassisk boolean-funktion som kontrollerar om den array man skickar in innehåller det heltal man också skickar in. Den går igenom varje värde i arrayen och jämför det med heltalet. Är det samma? Då returneras ”true”. Om inte, returneras ”false” när vi gått igenom hela listan och inte hittat något.

Sist ut är findNewLED(), som först slumpar fram en ny diod när en annan är färdig med sin sekvens. Denna funktion slumpar först fram ett heltal, och använder sen contains() för att kontrollera om värdet redan finns i arrayen. Gör det det, körs en loop som slumpar fram ett nytt värde tills ett värde som inte finns är valt.

 

Förutom dessa hjälpfunktioner finns bara setup-fasen och själva huvudfunktionen, som anropas varje varv i loopen av Arduinon. Så här ser den ut:

Inte allt för avancerat. Först hittar vi en ny LED om den befintliga är färdig med sin sekvens (fade-värdet lägre än 1). Är fadevärdet större än 40 (max-värdet, som multipliceras med 6, se ovan), så ändrar vi riktning på sekvensen genom att sätta fadeIn-boolean för den dioden till false.

Sen börjar det roliga! För varje slinga sätter vi ljuset på nuvarande diod till det fade-värde den för närvarande har. Kort och enkelt! Sen ökar eller minska vi dess värde baserat på om dess boolean är sann eller falsk, och kontrollerar om knappen tryckts in. Har den det, avbryter vi. Vi kör också en delay på 1

Det sista som händer är att vi kör strip.show(), som skickar ut färginformationen till dioderna. Denna information låg först i loopen tillsammans med strip.setPixelColor(), men då gick beräkningarna extremt långsamt så det här fungerar mycket bättre!

Det var allt jag tänkte gå igenom idag, men för all del, gräv ner er i koden! 😀

Här är den nya koden:

X-Mas lights by m.nu - version 2 (517 nedladdningar)

Alla slingor som skickas från och med nu kommer ha detta nya läge inbyggt! Det kan komma fler förbättringar framöver, håll utkik! Här är en video som demonstrerar hur det ser ut:

Det var allt för denna vecka, åter igen nästa onsdag! 🙂

16 svar till Introduktion till programmering, del 12- Julspecial med X-mas lights

  • Jag har den stora slingan med 3 LED-strands. Har nu testat lite, och ser att när man adresserar alla 3 LED-strands, så går slingan betydligt långsammare än om man bara använder 1 eller 2. Finns det något smart jag kan göra för att få lite mer fart på slingan? Speciellt när det nya, coola läget ”snowglitter” körs, så vore det kul om man kunde speeda upp den lite. Kan ju försöka med en parallellmotstånd på det variabla motståndet, men om det går att ändra något i koden vore det ännu bättre. Spännande att testa lite, och härligt att det till slut fungerade med kommunikationen mellan Arduinon och datorn.

  • Det körs en kort delay som baseras på input från potentiometern. Det ser ut så här i koden:

    wait = analogRead(analogPin)/10/numStrands;
    delay(wait);

    Du skulle kunna testa att kommentera ut ”delay(wait);” i det/de lägen du vill snabba upp.
    Gissar att ett större antal LEDs segar ner eftersom det blir fler chipp som behöver uppdateras, så vet inte hur pass mycket det hjälper. Vi kanske får försöka optimera koden om det går.

  • Jag har testat att ändra koden, men tyckte inte det var någon större skillnad. Däremot har jag nu upptäckt att ljuset från dioderna i modus SnowGlitter är något blåaktig i färgen.. Speciellt om man tittar på lite större distans. Om jag skulle vilja ändra lite på färgen på dioderna i detta moduset, är det en enkel process? Jag har försökt att ändra lite försiktigt i koden för att få detta till, men har tydligen inte lyckas hitta rätt ställe att förändra detta.. Skulle vilje ändra färgen till något mer gulaktigt.. Det är verkligen en grym slinga, och det är många som har kommit och tittat på den 🙂

    • Det är denna funktion du ska ändra:
      uint32_t WhiteFade(byte pos)
      {
      int value = pos;
      value = value * 6;
      return Color(value, value, value);
      }

      Som du ser tar den in en godtycklig lampas position i sekvensen, multiplicerar med 6 och skickar tillbaka en färg med samma värden på R, G, B. Detta ger det som är motsvarigheten till vitt. Vill du ändra till en annan färg får du testa att ändra balansen mellan dessa tre värden 🙂

    • Tackar djupt för detta 🙂 Provade ändra siffran 6 till 30, och upptäckte då att det var hastigheten på byten mellan vilka LED som gick allt för snabbt. Nu ska jag testa om det går att ändra själva färgen också. Hör av mig igen senare.

  • Hej!

    Jag undrar varför det följande inte fungerar rätt för mig. Här är ett utdrag av koden. Jag skapar några variabler för att göra det enkelt för mig. Testar och donar…. Saxar lite i koden från x-mas, och har inte finslipat något än. Måste ju först fatta hur detta fungerar.. 😛

    I början vill jag försöka få det att fungera med att kunde bestämma färg på vissa LED:s, och gör detta på ett lite jobbigt sätt just nu, men sedan kan jag bygga på med diverse formler och räknare. När jag kompilerar koden, så är allt ok. Men efter att koden är överfört till Arduinon, så lyser inte dom röda pixlarna.. Grön och blå lyser, men röd vill inte…

    #include ”SPI.h”
    #include ”Adafruit_WS2801.h”

    const int numStrands = 1; // number of LED strands to address
    const int strandLEDs = 50; //Number of LEDs per strand
    const int numLED = strandLEDs*numStrands; //Total number of LEDs

    uint8_t dataPin = 17; //Analog 3 (Analog pins used to simplify wiring.
    uint8_t clockPin = 19; // Analog 5
    uint8_t buttonPin = 18; // Analog 4
    uint8_t analogPin = 14; // Analog 0 – Potentiometer wiper
    // outside leads to ground and +5V

    int wait = 0; //Läser av potentiometeret
    int red = (255,0,0);
    int green = (0,255,0);
    int blue = (0,0,255);

    Adafruit_WS2801 strip = Adafruit_WS2801(numLED, dataPin, clockPin);

    void setup() {
    pinMode(buttonPin, INPUT); //init Pins
    pinMode(analogPin, INPUT);
    pinMode(clockPin, OUTPUT);
    pinMode(dataPin, OUTPUT);
    Serial.begin(57600);

    strip.begin(); //Lets start!
    // Update LED contents, to start they are all ‘off’
    strip.show();
    }

    void loop() {
    strip.setPixelColor(0, red);
    strip.setPixelColor(9, red); // set LED 0-9-19-29-39-49 to red color)
    strip.setPixelColor(19, red);
    strip.setPixelColor(29, red);
    strip.setPixelColor(39, red);
    strip.setPixelColor(49, red);

    strip.setPixelColor(1,0,255,0);
    strip.setPixelColor(10,0,255,0); // set LED 1-10-20-30-40 to green color)
    strip.setPixelColor(20,0,255,0);
    strip.setPixelColor(30,0,255,0);
    strip.setPixelColor(40,0,255,0);

    strip.setPixelColor(2, 0, 0,255);
    strip.setPixelColor(11, 0, 0,255); // set LED 10 to blue color)
    strip.setPixelColor(21, 0, 0,255);
    strip.setPixelColor(31, 0, 0,255);
    strip.setPixelColor(41, 0, 0,255);

    strip.show(); // Update all LEDs
    }

    • Den enda förklaring jag kan se är att du använder:
      setPixelColor(x, red);
      för röd, men för övriga använder du:
      setPixelColor(x, 0, 0, 0);

      Så testa med:
      strip.setPixelColor(x, 255, 0, 0);

      istället för det nuvarande. 🙂

    • Hehe, tack för detta 🙂 Men jag visste ju redan det 😉 Längst upp i koden min, så har jag skapat några variabler. En av variablerna ser ut såhär: int red = (255,0,0);

      Då tänkte jag att det ville fungera när jag använder variabeln istället för att skriva in alla siffrorna hela tiden..

      Istället för att behöva skriva ” strip.setPixelColor(x, 255, 0, 0); ” så ville jag skriva ” strip.setPixelColor(x, red); ”

      Då är det också lättare att se vilken färg jag använder i koden. Jag hade tänkt testa med många olika kombinationer och att ge dom namn istället för alla siffror.. 😛

      Dessutom letar jag efter någon stans att stoppa in en siffra för färgintensitet också.. Men jag läser på allt jag klarar, och ska försöka klura ut det hela..

      Tusen hjärtligt tack för all hjälp och tålamod med mig 🙂

    • Tänkt till lite extra nu.
      Det kan vara så enkelt som att du skickar in fel datatyp i funktionen.
      Testa med denna funktion från vår kod för att generera färgen från 3x byte:

      uint32_t Color(byte r, byte g, byte b)
      {
      uint32_t c;
      c = r;
      c <<= 8; c |= g; c <<= 8; c |= b; return c; } Vad som händer när du sätter int x = (255,0,0) vet jag inte, testa Serial.println(red) och se vad det står. Du kan dessutom minska antalet kodrader med en for-loop: for ( int i = 0; i < 5; i++ ) { strip.setPixelColor(1+(i*9), red); } Det behövs en för varje färg och det fungerar bara förutsatt att stegen mellan LEDsen är lika, men blir rätt smidigt kodmässigt 😀 EDIT2: Ser också att din sekvens är ojämn, mellan första och andra LEDen är det 9 LEDs och därefter 10. Men det kanske är meningen?

    • Hej! 🙂

      Tack alltså!

      Till att börja med, så kan jag säga att min kod blev ojämn av ett misstag. Jag ville ha lamporna att ljusa efter följande mönster: Led 0 red – Led 1 green – Led 2 blue – Led 3 red – Led 4 green – Led 5 blue ….. helt till Led 49. Jag fick det till efter massa arbete och pill, lade koden i en .txt fil på adressen http://karl-ove.com/KOLtest2.txt

      När de gäller det med loopen, så har jag rakt ut sagt för litet kunskap om hur jag ska implementera detta för att det ska fungera. Jag programmerade lite i TurboPascal en gång i tiden, men det är säkert 20 år sedan. Just nu försöker jag fatta sytax och uppbyggnad på arduino, så får vi se om mitt tålamod räcker till.. 😉

      Har hittat en bra sajt: http://arduino.cc/en/Reference/

      Jag uppskattar verkligen all hjälp jag har fått, och ska nog höra av mig när jag fått den nya koden till att fungera. Blev ju rätt jobbigt att skriva in varenda LED lampa med färgkod i exemplet i textfilen.

    • Efter att ha läst din kod har jag två tips till dig, dels en for-loop som körs lika många gånger som du har LEDs:

      for (int i = 0 ; i < numLED ; i++) { // kod här } Därefter en case-sats som sätter färg baserat på pixelns nummer. Dessutom använder jag modulo, som kontrollerar om det blir en rest om man delar x med något angivet tal. Exempel: x % 3 returnerar 0 för 3,6,9,12 osv. 1 för 4, 7, 10 osv... Vi använder modulo av 3 eftersom det finns tre olika RGB-färger: switch (i % 3) { case 0: strip.setPixelColor(i,50,0,0); //LED i = red break; case 1: strip.setPixelColor(i,0,50,0); //LED i = green break; default: // (case 2) strip.setPixelColor(i,0,0,50); //LED i = blue } Allt detta tillsammans blir alltså: for (int i = 0 ; i < numLED ; i++) { switch (i % 3) { case 0: strip.setPixelColor(i,50,0,0); //LED i = red break; case 1: strip.setPixelColor(i,0,50,0); //LED i = green break; default: // (case 2) strip.setPixelColor(i,0,0,50); //LED i = blue } } Och denna kod gör alltså precis samma sak som den kod på ~50 rader du har för att sätta färgen nu 🙂

    • De var häftigt var effektiv den koden var!! Jag klistrade och limmade lite, och på första försöket fungerade det 😀

      Vilka grymma kommandon det är med modulo!

      Det var otroligt kul att få denna hjälpen.

      Jag har också lyckats fixa en liten programsnutt som räknar från 0-10 och skriver ut det via serial monitor.. 😛

      Tack igen!! 🙂

    • Ja modulo är smidigt. Det hade nog inte behövts i detta fallet dock, kom jag på. Istället kan man ändra sin for-loop enligt följande:
      for (int i = 0 ; i < numLED ; i += 3 ) { strip.setPixelColor(i,50,0,0); //LED i = red strip.setPixelColor(i+1,0,50,0); //LED i = green strip.setPixelColor(i+2,0,0,50); //LED i = blue } På det här sättet börjar vi med i = 0, sätter 0, 0+1 och 0+2 till respektive färg och nästa steg i loopen har vi ökat i med 3 och gör då samma sam för 3, 3+1 och 3+2. Jag vet inte vilken som är effektivast, men om jag minns rätt är inte modulo den minst krävande beräkningen så detta kan vara bättre.

  • Kul! Jag ska testa detta imorgon, nu tar jobben upp det mesta av tiden igen…

    Har ledig helg och ska nog testa lite fram och tillbaka på detta. Roligt att få denna hjälpen av dig, det har gett mig en riktig pangstart!

    Med din hjälp och ganska mycket egen insats har jag nu börjat fatta lite mer av kodens uppbyggnad och användning i ardunio-världen 🙂 Fast när jag provar gräva mig ner i koden för snow-glitter så måste det ha varit ett otroligt planeringsarbete innan koden skapades….? Jag menar, hålla ordning på alla funktioner, variabler, argumenter och loopar… Ofattbart.. -än så länge.. 🙂

    Gött jobbat kan man väl lugnt säga!

    • Hej!

      Nu har jag testat den nya koden, och den gjorde ju precis samma sak som den andra koden. Jag ser däremot att den nya koden kräver lite mindre plats. Om man tittar på storleken på den färdigt kompilerade koden, så ser det ut såhär:

      Min första kod blev på 4840 bytes
      Din första kod blev på 4142 bytes
      Din andra kod ble på 4130 bytes

      Det betyder tydligtvis mycket att optimera koden!! 🙂

  • Kommentera