Tag: Arduino
-
Arduino ✕ PlatformIO
De electronica en code voor de Rheinturm zijn al een hele tijd af, maar ik moet nog steeds een “behuizing” voor het geheel bedenken. Ik twijfel nog tussen een eenvoudige houten plaat of een uitgesneden vorm, maar een 3D geprint model zou ook heel tof zijn. Blijkbaar zijn 3D-printers tegenwoordig ook niet meer super duur als je van
prutsentinkeren houdt… Anyway, dat is weer een heel andere hobby!Voorlopig ligt de boel dus veilig in een doos opgeborgen te wachten op de afwerking. Tot die tijd wil ik de code wel een beetje netjes achterlaten, zodat het later makkelijk weer op te pakken is. Daarbij heb ik al gemerkt dat de Arduino IDE niet het beste stuk software is om dat mee te doen. Arduino-Rheinturm gebruikt een aantal software-bibliotheken die ingeladen moeten worden en de vraag is of die over twee of drie jaar nog werken in de Arduino IDE.
Daarnaast is het prototype wat ik nu heb gebouwd weliswaar gebaseerd op een Arduino UNO, maar heb ik tegen de tijd dat ik de definitieve versie maak, misschien een ander type controller waar de Arduino IDE helemaal niet mee samenwerkt. Wat ik nodig heb, is een manier om te zeggen welke bibliotheken ik nodig heb en hoe de broncode moet worden gebouwd voor de controller die ik op dat moment heb. Als je ooit een Java-project hebt gemaakt, zul je denken aan Maven of Gradle. Voor Ruby heb je Gemspecs en Bundler, voor Python is er
setup.py
(of tegenwoordigproject.toml
) enpip
en in de JavaScript-wereld gebruik jepackage.json
ennpm
.PlatformIO
Het van oorsprong Oekraïense project PlatformIO is zo’n tool voor embedded software development. Een enkele
platformio.ini
in je project en de sources op een aangewezen plek (Convention over Configuration), meer heb je niet nodig om snel weer op weg te zijn als je een tijd weg bent geweest van je project.[env:uno] platform = atmelavr board = uno framework = arduino lib_deps = bxparks/AceTime @ ^2.0.1 adafruit/Adafruit NeoPixel @ ^1.11.0 northernwidget/DS3231 @ ^1.1.2 rlogiacco/CircularBuffer @ ^1.3.3
Doordat spullen op voor PlatformIO bekende plaatsen staan, kan die ook de benodigde projectbestanden genereren om het te openen in je IDE, zonder dat die bestanden in Git hoeven te worden gezet. Vandaag de dag werk ik met JetBrains CLion, maar misschien is dat morgen Visual Studio Code. Die snappen elkaars projectstructuur niet, dus dan is het fijn dat je niet helemaal opnieuw hoeft te beginnen met de inrichting, maar gewoon
platformio -c clion init
ofplatformio -c vscode init
kan doen en alles staat weer recht.Daar houdt het echter niet op, want als ik een andere microcontroller gebruik, dan kan ik eenvoudig een nieuwe minimale configuratie aanmaken en ben ik ook weer op weg. Met de juiste hardware zou het zelfs mogelijk moeten zijn om unit testen te schrijven die de code kan verifiëren.
CLion
De overstap van Arduino IDE via PlatformIO naar CLion was wel fundamenteel, want plotseling had ik een hele krachtige omgeving tot mijn beschikking waarmee het programmeren in C++ daadwerkelijk ondersteund wordt. Het nodigde uit tot opschonen en opsplitsen van de code, waardoor ik nu een heel nette structuur heb die ik over een jaar of twee ook nog begrijp. En ja, dan kan je de code ook maar beter publiek maken, want misschien heeft iemand er ooit nog iets aan.
Links
-
Stabiel de tijd bijhouden tussen herstarten
Met baby steps, zo komen we er wel. Eerder had ik het erover dat de Arduino geen eigen RTC (Real Time Clock) heeft en daardoor ten eerste een vrij grote afwijking van enkele seconden per dag heeft en ten tweede de tijd-instelling steeds kwijtraakt bij herstarten.
Er zijn twee goed ondersteunde RTCs voor Arduino verkrijgbaar; de DS1307 en de DS3231. Qua prijs ontlopen ze elkaar niet veel, maar qua precisie is de DS3231 absoluut superieur met een afwijking van maar ±2 minuten per jaar. Aansluiten op de Arduino, het wordt langzaam een beetje voorspelbaar, is weer een fluitje van een cent.
VCC
enGND
naar de plus en min,SDA
direct op analoge poortA4
enSCL
direct naar analoge poortA5
.
DS3231 met batterijhouder en pinsVervolgens kan je met de DS3231 library de boel besturen:
#include <DS3231.h> #include <AceTime.h> BasicZoneProcessor zoneProcessor; TimeZone tz = TimeZone::forZoneInfo(&zonedb::kZoneEurope_Amsterdam, &zoneProcessor); void setup() { // Initialiseer de verbinding Wire.begin(); Serial.begin(9600); } void loop() { // Haal de huidige tijd op uit de RTC DateTime rtcNow = RTClib::now(); ZonedDateTime now = ZonedDateTime::forUnixSeconds64(rtcNow.unixtime(), tz); now.printTo(Serial); delay(100); }
De tijd instellen
De kans dat de RTC de juiste tijd geeft als je hem voor het eerst gebruikt is op zich vrij klein, dus moet je hem eerst instellen. Via de seriële poort van de Arduino kunnen we de module instellen:
void loop() { // Als er iets op de seriële poort klaar staat... if (Serial.available()) { // ... dan lees het als een integer long readLong = Serial.parseInt(); // ... en lees de rest van de buffer leeg, zoals een <enter> of spaties while (Serial.available()) { Serial.read(); } // stel de RTC in op de integer die van de seriële poort is ingelezen rtc.setEpoch(readLong); } ZonedDateTime now = ZonedDateTime::forUnixSeconds64(RTClib::now().unixtime(), tz); now.printTo(Serial); delay(100); }
Vervolgens kunnen we de tijd instellen door de Unix epoch timestamp over de seriële poort naar de Arduino te sturen. De huidige Unix epoch krijg je met het commando
date +%s
. Je kan de uitvoer daarvan in de Arduino IDE naar de seriële poort sturen (Tools | Serial monitor), maar het kan ook vanaf de command line met hetcu
commando. Eerst moet je weten welk device je seriële poort heeft; in de Arduino IDE zie je dat onderin het scherm als je Arduino verbonden is. Bij mij is dat/dev/cu.usbmodem101
, vervolgens start jecu
met het commando:$ sudo cu -l /dev/cu.usbmodem101
Nu zie je alles wat over de seriële poort binnenkomt, dus in dit geval elke 100 milliseconden de huidige tijd. Het
cu
commando kan echter ook berichten terugsturen (zieman cu
), en heeft zelfs nog functionaliteit om de invoer uit een ander programma te halen door middel van~$<command>
. We krijgen de huidige epoch metdate +%s
(zie ookman date
).Als je door de stroom aan timestamps heen het commando
<enter>~$date +%s
invoert, zul je zien dat de timestamps opeens de huidige tijd weergeven. Aangezien je dit maar af en toe hoeft te doen zou je kunnen overwegen dit in een apart programma te doen, want dat scheelt weer in het geheugengebruik. Je sluitcu
netjes af met het commando<enter>~.
.Andere tijdsignaal-bronnen
Een RTC is zeker niet de enige bron van tijdsignalen. Als je een WiFi module toevoegt aan de Arduino, kan de tijd opgehaald worden vanaf een NTP-server. Met een GPS-ontvanger kan je de tijd ophalen van satellieten. Een nog andere optie is om een tijdsignaal van de DCF77-zender in Mainflingen bij Frankfurt op te vangen. Ook de originele klok in de Rheinturm doet dit!
Ik wil daar in de toekomst nog wel eens naar kijken, maar op dit moment is de klok volledig zelfstandig operationeel! De volgende stap is eerst iets compleet anders: de lichtstrip moet worden ingebouwd in een meer permanente opstelling.
-
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:
De analoge poort
A0
lees je eenvoudig uit met de functieanalogRead(A0)
. De waarde uit de analoge poort wordt omgezet naar een 10-bit integer, dus tussen 0 en 1024. OmdatsetBrightness
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 (deloop()
functie heeft eendelay(10)
die ervoor zorgt dat die aan het eind van elk rondje 10 milliseconden wacht) en hysterese wordt voorkomen. -
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 looplichtLogische 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 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!
-
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!
Drie maal WS2812B in serieJe 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 schakelingHet 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’ demonstratieDe enige uitdaging die er nu nog overblijft, is de code voor de klok schrijven. Dát is voor een volgende post!