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 (994 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 colorRun (uint32_t c, uint32_t l, int exitButton) { //By Adafruit
  int i;
  for (i=0; i < strip.numPixels()+l+1; i++) {
    strip.setPixelColor(i, c);
    if (i>l)
      strip.setPixelColor(i-l-1, 0);
    strip.show();
    button(); //Check button
    if (buttonPushCounter !=exitButton) //Is button pressed (Variable because we use this function in two if statements above and need to know when to exit
      break;
    wait = analogRead(analogPin)/10/3;
    delay(wait);
  }
}

“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:

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


// values used by the "snow glitter" mode
/* ************************************************************************************** */
const int diodes = strandLEDs/3; //Truncated, so if 50 LEDs per strand, 16 will be lit at a time
  
int diodePos[diodes]; //One third of the LEDs will be lit at one time
int diodeProgress[diodes]; //Progress of specific LED, 0-170
boolean diodeFadeIn[diodes]; //Is LED fading in or out?

/* ************************************************************************************** */

 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.

//return next Color value in sequence (white fade-in/-out)
uint32_t WhiteFade(byte pos)
{
  int value = pos;
  value = value * 6;
  return Color(value, value, value);
}

//check whether array contains an integer value
boolean contains(int array[], int val)
{
  int i;
  for (i = 0; i < strandLEDs/3; i++)
  {
    if (array[i] == val)
    {
      return true;
    }
  }
  return false;
}

//Works with global variables at the top. When a LED has finished its sequence,
// it uses this function to find a new LED.
void findNewLED(int diodePosition)
{
  int newRandVal = random(strandLEDs);
    
    //If the random value is already present, choose a new one until a unique one is found
    //definition of "contains" is below
    while (contains(diodePos, newRandVal))
    {
      newRandVal = random(strandLEDs);
    }
    diodePos[diodePosition] = newRandVal;
}

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:

void snowGlitter() { //By www.m.nu
  int i;
  for (i=0; i < diodes; i++)
  {
    button();
    int currentDiode = diodePos[i];
    
    //reverse fade direction if diode is at the end, if faded out randomize new diode
    if (diodeProgress[i] < 1) {
      findNewLED(i);
      diodeFadeIn[i] = true;
    }
    else if (diodeProgress[i] > 40) {
      diodeFadeIn[i] = false;
    }
    
    //set LED light (based on diodeProgress)
    //"show" below to speed up processing
    int j;
    for (j = 0; j < numStrands; j++) {
      strip.setPixelColor(currentDiode+(j*strandLEDs), WhiteFade(diodeProgress[i]));
    }
  
    if (diodeFadeIn[i]) {
      diodeProgress[i] += 1;
    }
    else diodeProgress[i] -= 1;
    
    if (buttonPushCounter !=4)
      break;
    wait = analogRead(analogPin)/30/numStrands;
    // 1 is optimal pause for this mode
    delay(wait);
  }
  strip.show();
}

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 (984 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! 🙂