Tag: Ws2812b

  • Automatisch dimmen als het donker wordt

    Het project ligt even een beetje stil, maar ik heb in de tussentijd wel een kleine uitbreiding gemaakt zodat je ‘s nachts niet wordt verblind door een zee aan licht, maar dat je tegelijk overdag de klok ook nog kan aflezen.

    Met een fotodiode meet ik het omgevingslicht en dankzij de in de Arduino ingebouwde analoog-digitaalomzetter is het weer een poepsimpel circuitje geworden:

    Schakeling voor lichtsensor

    De analoge poort A0 lees je eenvoudig uit met de functie analogRead(A0). De waarde uit de analoge poort wordt omgezet naar een 10-bit integer, dus tussen 0 en 1024. Omdat setBrightness een waarde tussen 0 en 255 wil, delen we die door 4 en om te voorkomen dat het licht helemaal uitgaat in het donker zetten we er een minimum op.

    void updateBrightness()
    {
      int brightnessReading = analogRead(A0) / 4;
      pixels.setBrightness(max(brightnessReading, 1));
    }
    

    Dit kan echter — zoals bij veel regelsystemen — leiden tot hysterese, waarbij het licht feller gaat branden omdat het licht wegvalt, maar doordat het licht feller gaat branden het weer lichter wordt en de helderheid bij de volgende keer dat de sensor wordt uitgelezen weer naar beneden wordt bijgesteld, waarna het hele circus opnieuw begint. Dit kunnen we voorkomen door de helderheid afhankelijk te maken van een langere periode.

    Hieronder slaan we de ingelezen waarde tijdelijk op in een circulaire buffer van lengte 10, om vervolgens het gemiddelde aan setBrightness te voeren. Het voordeel in het gebruik van zo’n type buffer is dat je waardes erin kan blijven duwen (push()) en als hij vol is valt de oudste waarde eruit. Omdat ik een lopend gemiddelde wil bepalen, haal ik die oudste waarde er zelf uit (shift()) en trek die van het gemiddelde af.

    #include <CircularBuffer.h>
    
    CircularBuffer<int, 10> brightnessReadings;
    int brightnessAverage = 0;
    
    void updateBrightness()
    {
      int brightnessReading = analogRead(A0) / 4;
      brightnessAverage += brightnessReading;
    
      if (brightnessReadings.isFull()) {
        brightnessAverage -= brightnessReadings.shift();
      }
    
      brightnessReadings.push(brightnessReading);
    
      pixels.setBrightness(max(brightnessAverage / brightnessReadings.size(), 1));
    }
    

    De waarde die uiteindelijk naar setBrightness gaat is het gemiddelde van de 10 laatste waarden. Een plotse verandering in helderheid wordt daardoor uitgesmeerd over 100 milliseconden (de loop() functie heeft een delay(10) die ervoor zorgt dat die aan het eind van elk rondje 10 milliseconden wacht) en hysterese wordt voorkomen.

    Lees verder
  • De tijd weergeven op 60 LEDs

    In de vorige post zagen we de hardwarekant van mijn model van de Lichtzeitpegel. In deze post gaan we de softwarekant verkennen, om er vervolgens achter te komen dat we nog lang niet klaar zijn met de hardware…

    NeoPixel

    Vorige keer hadden we de strandtest al gedaan om te bevestigen dat de schakeling werkt. Nu gaan we een simpel looplicht maken om de NeoPixel library te leren kennen.

    Onderstaande code is alles om mee te beginnen:

    #include <Adafruit_NeoPixel.h>
    
    #define LED_PIN    6            // Which pin on the Arduino is connected to the strip?
    #define LED_COUNT  60           // How many LEDs are on the strip?
    #define HUE_STEP   65536 / 90   // Full rainbow cycle in 90 frames
    
    // Declare the strip object:
    Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
    int hue = 0;
    
    void setup() {
      strip.begin();                // Initialize library
      strip.show();                 // Initialize LEDs
      strip.setBrightness(50);      // Set default brightness at about 1/5
    }
    
    void loop() {
      for (int i = 0; i < LED_COUNT; i++) {
        strip.clear();              // Clear LED color in controller RAM
        for (int j = 0; j <= 9; j++) {
          strip.setPixelColor(
            (i - j + LED_COUNT) % LED_COUNT, // Makes for a fluid run-off at the end
            strip.ColorHSV(hue, 255, 255 - j * (255 / 9))
          );
        }
        strip.show();               // Update LEDs according to controller RAM
        delay(10);
        hue += HUE_STEP;
      }
    }
    

    En dat levert dan iets dergelijks op als dit:


    Regenboog looplicht

    Logische klok

    LED index – 0-based Kleur Omschrijving
    56 – 59 Geel Geen functie
    54 – 55 Wit Tien uren
    53 Geel Scheidingslamp
    44 – 52 Wit Uren
    43 Geel Scheidingslamp
    42 Rood Luchtbaken
    37 – 41 Wit Tien minuten
    36 Geel Scheidingslamp
    27 – 35 Wit Minuten
    26 Rood Luchtbaken
    25 Geel Scheidingslamp
    20 – 24 Wit Tien seconden
    19 Geel Scheidingslamp
    10 – 18 Wit Seconden
    0 – 9 Geel Geen functie

    Werkende Lichtzeitpegel van 19:59:55 tot 20:00:02

    Er zijn online niet heel veel details te vinden van de werking van de klok. Op de Duitstalige Wikipedia-pagina van de Lichtzeitpegel staat wel een tabel met de verdeling van de lampen. Het origineel heeft er 62, maar omdat ik maar 60 LEDs in de strip heb, heb ik de eerste en laatste lamp zonder functie laten vervallen. Ik hoop dat iemand me kan vergeven.

    De gele lampen zijn niet geheel zonder functie. Aan het eind van elke minuut gaan alle scheidingslampen een seconde lang aan. Aan het eind van elk uur gaan alle scheidingslampen bovendien een hele minuut lang aan. Het effect bij de volle minuut is ook te zien in een video op YouTube. De precieze werking van het hele uur is daar echter niet zichtbaar. Ik gok dat ik daarvoor echt zelf naar Düsseldorf zal moeten.

    Code

    Opvallend genoeg is het algoritme eenvoudiger te lezen dan de tijd aflezen van een klok op een zendmast.

    void updateTime(const ZonedDateTime& dt)
    {
      for (int i = 0; i < NUMPIXELS; i++) theLEDs[i] = 0; // Reset
      for (int i = 0; i < dt.second() % 10; i++) theLEDs[10 + i] = 1; // Single seconds
      for (int i = 0; i < dt.second() / 10; i++) theLEDs[20 + i] = 1; // Ten seconds
      for (int i = 0; i < dt.minute() % 10; i++) theLEDs[27 + i] = 1; // Single minutes
      for (int i = 0; i < dt.minute() / 10; i++) theLEDs[37 + i] = 1; // Ten minutes
      for (int i = 0; i < dt.hour() % 10; i++) theLEDs[44 + i] = 1; // Single hours
      for (int i = 0; i < dt.hour() / 10; i++) theLEDs[54 + i] = 1; // Ten hours
      /* Whole Minute and Whole Hour effect */
      if (dt.second() == 59 || dt.minute() == 59) {
        for (int i = 0; i < 10; i++) theLEDs[i] = 2;
        theLEDs[19] = 2;
        theLEDs[25] = 2;
        theLEDs[36] = 2;
        theLEDs[43] = 2;
        theLEDs[53] = 2;
        for (int i = 56; i < 60; i++) theLEDs[i] = 2;
      }
    }
    

    Nu de tijd getoond kan worden op de strip, hebben we natuurlijk de tijd zelf nog nodig.

    Een Arduino komt niet met een eigen klok en een losse RTC (Real Time Clock) module heb ik (nog) niet. Voorlopig zet ik de tijd handmatig tijdens compilatie en kan de tijd bijgesteld worden via de seriële interface. En dat moet regelmatig gebeuren, want de millis()functie die Arduino biedt om het aantal milliseconden sinds het starten te bepalen is niet erg precies. Na een nacht heb je al een afwijking van zo’n 7 seconden te pakken.

    Daarnaast is onmogelijk om de helderheid van de LEDs te bepalen voor zowel een zonnige dag als een donkere nacht. Er moet dus een dimmer in komen die afhangt van het omgevingslicht.

    Twee onderwerpen voor een volgende post; wordt vervolgd!

    Lees verder
  • Lichtzeitpegel in je woonkamer

    Naast programmeren was ik altijd al geïnteresseerd in de hardware-kant, wat zelfs in een vlaag van verstandsverbijstering leidde tot een studiejaar elektrotechniek. Wat me altijd een beetje er vanaf hield om er meer mee te doen was de enorme verscheidenheid aan componenten en het grillige gedrag van analoge schakelingen wat ik niet altijd goed begreep. Er was eigenlijk gewoon teveel wat ik niet wist om goed te beginnen.

    Sinds een paar maanden rommel ik soms in de avonduren wat met de Arduino Starter Kit die ik een tijd geleden heb aangeschaft. Eigenlijk zijn hiermee beide problemen opgelost, want ik hoefde nu niet zelf te bepalen welke componenten ik nodig zou gaan hebben en de voorbeelden zijn van een niveau dat analoog gedoe geen roet in het eten gooit. Hierdoor begon ik beter te begrijpen wat de verschillende componenten doen; in ieder geval beter dan tijdens het college Netwerken.

    Rheinturm in Düsseldorf

    Van het een kwam het ander en zo kwam ik uiteindelijk terecht op een ander verloren project uit de jaren 90; het nabouwen van de decimale klok in de Rheinturm in Düsseldorf. Dit vereist wellicht wat meer uitleg, want de Lichtzeitpegel zoals de installatie heet is nét iets minder bekend dan pakweg de verlichting van de Eiffeltoren of de melodie van Big Ben.

    Langs de toren zijn 62 lampen aangebracht die van boven naar onder de decimalen van een 24-uurs klok voorstellen. De eerste twee lampen geven de tientallen uren aan, de volgende tien lampen de enkele uren, daaronder zes lampen die tientallen minuten aangeven en tien lampen de enkele minuten. Ten slotte zijn er nog zes lampen voor de tientallen seconden en tien lampen voor de enkele seconden.

    De klok hiernaast geeft 16:56:39 aan, omdat van boven naar onder 1, 6, 5, 6, 3 en 9 lampen branden. Er zijn nog wat extra’s, zoals scheidingslampen tussen de groepen die op elk volle uur een minuut lang branden, maar dit zijn de pure regels voor de klok.

    Het Elektor project

    De toren met klok staat al sinds 1981, maar eind jaren 90 werd in het blad Elektor 05-1998 een elektrisch schakelschema gepubliceerd waarmee de klok na te bouwen was. Omdat het een erg populair artikel was, werd het in Elektor 01-2000 nog eens dunnetjes overgedaan.

    In onderstaande video laat Roger Leifert zijn versie zien die nog steeds voldoende aandacht krijgt op tentoonstellingen. Vanaf 6:20 zie je de ingewikkelde schakeling en lijmblob die alle leds bij elkaar houdt.

    … en toen kwam de WS2812B

    De WS28xx serie RGB LED modules veroorzaakten een revolutie in aanstuurbare verlichting. Voeding en aansturing werd gescheiden, waardoor je schakeling een stuk eenvoudiger kan worden. Daarnaast biedt de WS28xx ook een uitgangssignaal, wat alle inkomende berichten doorstuurt minus het bericht wat voor de huidige LED is bedoeld! Dat betekent dat je er ketens van kan bouwen die je met een enkele data-lijn kan aansturen!

    Schakelschema van drie WS2812B in serie
    Drie maal WS2812B in serie

    Je kan zelf de losse WS2812Bs aan elkaar koppelen, maar er zijn ook kant-en-klare LED-strips die zelf op lengte te maken zijn. Ze zijn er met 30, 60 en zelfs 144 LEDs per meter. Ze zijn er trouwens ook in de vorm van ringen, arrays (meerdere strips naast elkaar), ze zijn waterdicht en stofdicht verkrijgbaar. Voor mijn toepassing is 60 / meter optimaal; het model van de Lichtzeitpegel wordt dan ongeveer een meter hoog.


    Belachelijk simpele schakeling

    Het resultaat is in de basis een belachelijk simpele schakeling met alleen twee draadjes voor de power en een weerstandje tussen de I/O poort van de Arduino en de data-lijn van de WS2812B strip. De Arduino in combinatie met de NeoPixel library doet de rest. In de Arduino IDE kan je voorbeeld-code voor de NeoPixel library inladen. Deze laat onder andere onderstaande bewegende regenboog zien. Mooi! De schakeling werkt!


    NeoPixel ‘strandtest’ demonstratie

    De enige uitdaging die er nu nog overblijft, is de code voor de klok schrijven. Dát is voor een volgende post!

    Lees verder