Zeg vaarwel tegen stereotype firmwarecodering: Praktische oplossingen

Stereotype firmware codering kan leiden tot operationele onderbrekingen, waardoor ontwikkelaars terug moeten naar de ontwerptafel. Wat als men MCU-netwerkconnectiviteit verliest of te maken krijgt met IP-conflicten? In dit artikel heroverwegen we conventionele codeerpraktijk en verkennen we slimmere oplossingen om de betrouwbaarheid te verbeteren en de kosten in embedded projecten te verlagen.
Veel embedded projecten die ik tegenkom in de media bieden briljante oplossingen voor ontwikkelaars, maar schieten tekort bij het beantwoorden van kritieke vragen. Welke actie moet ik ondernemen als de microcontroller (MCU) tijdens runtime de netwerkconnectiviteit verliest? Hoe lost men IP-conflicten op in LAN? Is het nodig om een RTC te gebruiken om een nauwkeurige timing te handhaven? Moet de gebruiker ingrijpen om de tijd aan te passen? Moeten tijdens de uptime van de MCU sensoren voortdurend ingeschakeld zijn?
We moeten even buiten de gebaande paden gaan en nadenken over stereotype codering! In dit artikel bespreek ik een aantal conventionele, of stereotype om precies te zijn, coderingen van MCU's die praktisch niet uitvoerbaar zijn en waarbij van tijd tot tijd tussenkomst van de gebruiker nodig is om operationele storingen op te lossen. Normaal gesproken zijn er in dergelijke gevallen drukknoppen en displays nodig om de MCU te controleren, te resetten, vooraf in te stellen of te onderbreken om een corrigerende actie uit te voeren. Het faciliteren van gebruikersinterventie kan averechts werken als GPIO's schaars zijn om dergelijke knoppen en displays te ondersteunen naast het interfacen van sensoren.
Wanneer men met de Arduino IDE werkt, komt men altijd twee verschillende secties van de code tegen, namelijk de setup() en de loop(). De eerste sectie is bedoeld voor het initialiseren van de MCU en alle randapparatuur en sensoren die eraan gekoppeld zijn. Het tweede deel handelt runtime activiteiten af waarbij sensoren kunnen worden uitgelezen en acties kunnen worden ondernomen. Door secties op deze manier in te delen, worden ontwikkelaars voor de gek gehouden, die het vanzelfsprekend vinden dat hun embedded systeem vlekkeloos zal werken, maar verrast zijn dat er operationele storingen opdoemen die hen terug naar de ontwerptafel kunnen dwingen. Ik heb zo'n situatie meegemaakt en werd geconfronteerd met de eerdergenoemde vragen. Ik kwam echter tot zinnige oplossingen om operationele storingen te voorkomen en de kosten van het uitvoeren van mijn projecten te verlagen door enkele traditionele setup() activiteiten te integreren in de loop() sectie. In de volgende casestudies zal ik deze strategie verder uitwerken.
Beperking van de Wi-Fi configuratie tot de setup() sectie
Wanneer het AP opnieuw opstart door tussenkomst van de gebruiker of door stroomherstel, verliest de aangesloten MCU de verbinding met het WLAN en moet daarom opnieuw opstarten om setup() opnieuw uit te voeren om de Wi-Fi-connectiviteit opnieuw te initialiseren. Dus het controleren of Wi-Fi-connectiviteit bestaat, moet regelmatig worden uitgevoerd in de loop() sectie en er moet dienovereenkomstig worden gereageerd op een verbroken verbinding met het AP. Het verliezen van de verbinding met het internet, aan de andere kant, is een ander probleem dat detectie vereist voor webgebaseerde toepassingen, zelfs met geldige WLAN-connectiviteit. Dergelijke strategieën worden zelden behandeld in gepubliceerde projecten die lijken te vertrouwen op de beschikbaarheid van menselijke interventie.
Gelukkig heeft de ESP8266 een zeer interessante Wi-Fi-bibliotheek, de ESP8266WiFi i>a>, die een veelheid aan functies en mogelijkheden biedt die zelden worden aangetroffen in praktische projecten. Random Nerd Tutorials besprak de draadloze verbindingsalternatieven die deze bibliotheek biedt. Een combinatie van de volgende statements in de setup() sectie zou de magie doen:
WiFi.setAutoReconnect(true);
WiFi.persistent(true);
Als alternatief maakt het controleren van de status van de draadloze verbinding in de loop() sectie het mogelijk om een herstelactie uit te voeren, zoals het herstarten van de MCU. In Figuur 1 staat het codesegment dat we regelmatig gebruiken in de setup() sectie, maar dat we met een kleine aanpassing ook kunnen gebruiken in de loop() sectie.

Random Nerd Tutorials biedt ook een softwarematig interrupt-alternatief voor het detecteren van verbroken verbindingen en het herstellen ervan. Voor ESP8266 gebruik ik een andere aanpak om de netwerkconnectiviteit te controleren. Ik codeer de MCU om de toegang tot de Wi-Fi-gateway regelmatig te controleren in de loop() sectie door deze te pingen met de functie ping(gateway_IP) uit de ESP8266Ping bibliotheek . Dit wordt "Ping for Life" genoemd, wat door netwerkbeheerders wordt gebruikt om de verbindingsstatus te controleren en te beschermen tegen time-outs. Als de ping mislukt, forceer ik de MCU om opnieuw op te starten en probeer ik verbinding te maken met een alternatief AP. Ik gebruik het pingen ook om de toegankelijkheid tot het internet te controleren, maar dan gericht op een DNS-server zoals Google's <8.8.4.4> server in plaats van de lokale WiFi gateway, met behulp van de functie ping(primaryDNS_IP). Als zo'n regelmatige ping mislukt, dan moet de MCU opnieuw opstarten om verbinding te maken met een AP met actieve internetconnectiviteit. Ik neem de ESP8266Ping bibliotheek op in mijn sketch om deze strategie te bereiken, zoals in het codesegment in Listing 1.
Listing 1: Ping of Life implementatie voor WLAN-gateway en een globale DNS-server.
#include <ESP8266WiFi.h>
#include <ESP8266Ping.h>
IPAddress local_IP(192, 168, 1, 180);
IPAddress subnet(255, 255, 255, 0); // Subnet Mask
IPAddress gateway(192, 168, 1, 1); // Default Gateway
IPAddress primaryDNS(8, 8, 4, 4);
IPAddress secondaryDNS(8, 8, 8, 8);
// place this code segment in the loop() section for periodic checks
if (Ping.ping(gateway)) {
Serial.println("Ping gateway Success!!");
} else {
Serial.println("Gateway Ping Error!!");
ESP.restart();
}
// place this code segment in the loop() section for periodic checks
if (Ping.ping(primaryDNS)) {
Serial.println("Ping DNS Success!!");
} else {
Serial.println("DNS Ping Error!!");
ESP.restart();
}
Het herstarten van de ESP8266 MCU wordt uitgevoerd door de ESP.restart() functie uit te voeren. Deze functie is inherent ingebed in het ESP-hardwareplatform dat in de Arduino IDE is geïnstalleerd. Om over te schakelen naar een ander AP bij het herstarten, kan de gebruiker de connectiviteit met alternatieve AP's controleren met dezelfde ping-aanpak en de draadloze verbinding met het AP dat reageert herstellen in de setup() sectie. Listing 1 illustreert code segmenten die zowel de lokale AP-connectiviteit als de internettoegankelijkheid controleren.
Gebruik van de Realtime Clock (RTC)-module om de tijd bij te houden
Een RTC-module handhaaft zijn timerwerking nauwkeurig ondanks de toestand van de bijbehorende MCU door een back-upbatterij te hebben die de continue werking ondersteunt. Wanneer de MCU opnieuw opstart, haalt deze de huidige tijd op uit de RTC en gebruikt deze regelmatig om zijn tijdvariabelen bij te werken. Veel MCU's hebben ingebouwde timers die milliseconden kunnen bijhouden sinds de laatste herstart. Ik gebruik zo'n functie graag in mijn code om de tijdvariabelen van de MCU bij te werken tijdens de uptime. Als de MCU toegang heeft tot het Internet, dan is het zinvol om NTP-servers aan te spreken om de huidige tijd te krijgen bij het opnieuw opstarten. Ik gebruik beide strategieën om mijn ESP8266 modules te configureren en uit te voeren, waarbij ik in de setup() sectie een NTP-server aanspreek om de tijd op te halen als start, en in de loop() sectie gebruik ik de millis() functie om die tijd bij te werken naar het huidige moment. Ik gebruik de NTP-server ook regelmatig in de loop() sectie om mijn softwarematige klok bij te stellen en te compenseren voor eventuele afwijkingen. Met een dergelijke strategie kan ik de RTC-integratie in mijn projecten achterwege laten en GPIO-toewijzingen en stroomverbruik besparen.
In het volgende codefragment laat ik zien hoe men toegang kan krijgen tot NTP en de lokale kloktimer kan corrigeren om een nauwkeurige realtime klok te krijgen zonder een RTC. Ik heb twee hoofdroutines opgezet die de klok manipuleren: de functie getNtpTime() voor het updaten van de klok op basis van NTP-toegang (zie Listing 2) en de functie update_time() voor het verwerken van de interne MCU-timer (zie Listing 3).
Listing 2: MCU-klok updaten tijdens NTP-synchronisatie.
void getNtpTime() {
epochTime = timeClient.getEpochTime();
Serial.print("Epoch Time: ");
Serial.println(epochTime);
formattedTime = timeClient.getFormattedTime();
Serial.print("Formatted Time: ");
Serial.println(formattedTime);
currentHour = timeClient.getHours();
Serial.print("Hour: ");
Serial.println(currentHour);
currentMinute = timeClient.getMinutes();
Serial.print("Minutes: ");
Serial.println(currentMinute);
currentSecond = timeClient.getSeconds();
Serial.print("Seconds: ");
Serial.println(currentSecond);
weekDay = weekDays[timeClient.getDay()];
Serial.print("Week Day: ");
Serial.println(weekDay);
//Get a time structure
struct tm *ptm = gmtime((time_t *)&epochTime);
monthDay = ptm->tm_mday;
Serial.print("Month day: ");
Serial.println(monthDay);
currentMonth = ptm->tm_mon + 1;
Serial.print("Month: ");
Serial.println(currentMonth);
currentMonthName = months[currentMonth - 1];
Serial.print("Month name: ");
Serial.println(currentMonthName);
currentYear = ptm->tm_year + 1900;
Serial.print("Year: ");
Serial.println(currentYear);
//Print complete date:
currentDate = String(currentYear) + "-"
+ String(currentMonth) + "-" + String(monthDay);
Serial.print("Current date: ");
Serial.println(currentDate);
Serial.println("");
}
Listing 3: MCU interne timerupdate van de klok.
void update_time() {
int lapse = millis() - oldSec; // passed milliseconds since last clock update
if (lapse >= 1000) { // add passed seconds to clock reading
oldSec = millis();
currentSecond += lapse/1000;
if (currentSecond >= 60) { // add a minute for every 60 passed seconds
currentSecond -= 60;
++currentMinute;
if (currentMinute >= 60) { // add an hour for every 60 passed minutes
currentMinute -= 60;
++currentHour;
if (currentHour >= 24) { // adjust clock for AM transition
currentHour -= 24;
}
}
}
}
String hr, min;
if (currentHour < 10) hr = "0" + String(currentHour);
else hr = String(currentHour);
if (currentMinute < 10) min = "0" + String(currentMinute);
else min = String(currentMinute);
formattedTime = hr + ":" + min;
}
Binnen de loop() sectie roep ik de eerste functie eens per uur aan om te compenseren voor kloktolerantie en roep ik de laatste functie elke minuut aan om de minuten en uren van de klok bij te werken. Om de MCU-klok bij het herstarten te initialiseren, roep ik de NTP-functie eenmaal aan in het setup()-gedeelte en declareer ik globale variabelen die nodig zijn om de verwerkte huidige datum en tijd weer te geven (zie Listing 4). Deze opeenvolging van activiteiten zorgt voor een nauwkeurige RTC-substitutie. Helaas moest ik mijn voorraad RTC’s op ijs leggen sinds deze strategie vruchten heeft afgeworpen.
Listing 4: Sketch initialisatie met bibliotheken en globale variabelen voor tijdverwerking.
// Define NTP Client to get time
#include <NTPClient.h>
#define NTPUPDATE 3600000 // frequency of accessing the NTP, every hour
const long utcOffsetInSeconds = 7200; // GMT +2
float oldSec = 0; // passed seconds since last reboot
WiFiUDP ntpUDP; // required UDP access by NTP client
NTPClient timeClient(ntpUDP, "pool.ntp.org"); // my chosen NTP server
time_t epochTime;
String formattedTime, weekDay, currentMonthName, currentDate;
int currentHour, currentMinute, currentSecond, monthDay, currentMonth, currentYear;
//Week Days
String weekDays[7] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
//Month names
String months[12] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };
// Variables to save date and time
String formattedDate;
String dayStamp;
String timeStamp;
Dynamische IP-adressering van MCU-gebaseerde webservers
Wanneer een webserver draait op een hardware met een dynamisch toegewezen IP-adres, moet de gebruiker na elke herstart het IP-adres van de server registreren en dat adres doorgeven aan zijn webclients. Een dergelijke netwerkopstelling vereist een AP met DHCP, zoals in Listing 5 zonder het WiFi.config() segment (Figuur 2).
wordt gespecificeerd naast andere instellingen voor internettoegankelijkheid.
De connectiviteit van LAN-apparaten op een dergelijke manier onderhouden is echter een vervelende klus en heeft dus een facelift nodig. Daarom ben ik geneigd om het IP-adres van webservers in mijn persoonlijke LAN vast te leggen om het gemakkelijker te maken om webclients verspreid over mijn IoT-netwerk te onderhouden. Listing 5 toont een voorbeeld waarin ik de statische netwerkgegevens van mijn tuinverlichtingscontroller instel.
Listing 5: Vaste WLAN-netwerk credentials.
IPAddress local_IP(192, 168, 1, 140); // MCU fixed IP address
IPAddress subnet(255, 255, 255, 0); // Subnet Mask
IPAddress gateway(192, 168, 1, 1); // Default Gateway
IPAddress primaryDNS(8, 8, 4, 4);
IPAddress secondaryDNS(8, 8, 8, 8);
const char *ssid = "YOUR_SSID";
const char *password = "YOUR_PASSWORD ";
void setup() {
WiFi.mode(WIFI_STA);
// Configure static wlan credentials, remove this segment for dynamic allocation
if (!WiFi.config(local_IP, gateway, subnet, primaryDNS)) {
Serial.println("STA Failed to configure");
}
else {
Serial.println("Static IP Configuration Succeeded!");
}
// Connect to Wi-Fi network with SSID and password
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// . . . . .
} // setup() end
Toegang tot een webserver via de domeinnaam in plaats van het IP-adres is een andere alternatieve manier om het dilemma van toegankelijkheid van de server op te lossen. Dus, voor een MCU waarvan het IP-adres dynamisch wordt toegewezen, gebruik ik de mDNS-bibliotheek speciaal voor ESP8266 om een domeinnaam toe te wijzen aan de ESP-gebaseerde webserver en deze te gebruiken in Bonjour-geschikte webclients om toegang te krijgen tot de server. Ik gebruik deze strategie zelfs voor servers met een vast IP-adres, omdat het de toegang tot webservers vereenvoudigt voor niet-technische gebruikers die niet bekend zijn met IP-adressering. Figuur 3 laat zien hoe ik via een webbrowser toegang krijg tot mijn tuinverlichtingscontroller met behulp van de DNS toegewezen naam of het vaste IP-adres.
of het vaste IP-adres <192.168.1.140>.
Listing 6 illustreert de noodzakelijke codering voor het benoemen van de MCU in het WLAN, waarbij MDNS het object is dat mDNS-functies faciliteert voor het activeren en onderhouden van de MCU-netwerknaam.
Listing 6: Een netwerknaam toewijzen aan de MCU voor eenvoudige toegankelijkheid in Bonjour-compatibele webclients.
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
ESP8266WebServer server3(80); // mDNS activation server
String newHostname = "garden1"; // my garden controller network name
// shall be automatically appended with .local
// to form a legitimate domain name
void setup() {
// . . . . .
if (!MDNS.begin(newHostname)) {
Serial.println("Error setting up MDNS responder!");
while (1) {
delay(1000);
}
}
Serial.println("mDNS responder started");
// Start the server
server3.begin();
Serial.println("TCP server started for mDNS activation!");
// Add service to MDNS-SD
MDNS.addService("http", "tcp", 80);
. . . . . . . . .
} // setup() end
void loop() {
MDNS.update();
// . . . . .
} // loop() end
IP-adresconflict van MCU in LAN
Wanneer een IoT-netwerk groter wordt met een vaste IP-toewijzing, moet men rekening houden met de mogelijkheid dat per ongeluk dezelfde IP wordt toegewezen aan meerdere controllers. In zo'n geval ontstaan IP-conflicten in het netwerk, waardoor conflicterende apparaten ontoegankelijk worden via het netwerk (zie Figuur 4). Natuurlijk kan een gecentraliseerd beheer van netwerkbronnen een dergelijk geval voorkomen, maar dit is niet altijd de realiteit.
veroorzaken, niet alleen voor embedded devices maar ook voor de computerfaciliteiten van gebruikers.
Bij het opnieuw opstarten initieert de MCU een dynamisch IP-verzoek en pingt het beoogde vaste IP-adres om er zeker van te zijn dat het niet is toegewezen aan een apparaat op het LAN. Daarna initieert de MCU de beoogde vaste IP-verbinding met het LAN in het setup() gedeelte. In Listing 7, ping ik de beoogde local_IP van de MCU die is gedeclareerd in Listing 1 voordat deze wordt toegewezen aan de MCU. Voordat dit codesegment wordt geactiveerd, moet de MCU-verbinding maken met het LAN met behulp van een tijdelijk door DHCP toegewezen IP-adres. Als de ping mislukt, dan kan ik er zeker van zijn dat het beoogde vaste IP-adres veilig is om mee te werken; anders moet de MCU elke minuut opnieuw opstarten om de conflictresolutie opnieuw te controleren.
Listing 7: Pingen van IP conflicterende netwerkbronnen.
#include <ESP8266WiFi.h>
#include <ESP8266Ping.h>
IPAddress local_IP(192, 168, 1, 140); // MCU fixed IP address
IPAddress subnet(255, 255, 255, 0); // Subnet Mask
IPAddress gateway(192, 168, 1, 1); // Default Gateway
IPAddress primaryDNS(8, 8, 4, 4);
IPAddress secondaryDNS(8, 8, 8, 8);
const char *ssid = "YOUR_SSID";
const char *password = "YOUR_PASSWORD ";
void setup() {
// . . . . .
WiFi.mode(WIFI_STA);
// Connect to Wi-Fi network with SSID and password using DHCP assigned IP
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
} // while end
// check IP conflicts, restart MCU if conflicts exist
if (Ping.ping(local_IP)) {
Serial.println("IP conflict detected!!");
Delay(60000); // wait for a minute then reboot
ESP.restart();
} else {
Serial.println("No IP conflict detected!!");
// Configure static wlan credentials
if (!WiFi.config(local_IP, gateway, subnet, primaryDNS)) {
Serial.println("STA Failed to configure");
} else {
Serial.println("Static IP Configuration Succeeded!");
// Connect to Wi-Fi network with SSID and password
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
} // while end
} // else end
} // else end
// . . . . .
} // setup() end
Energiebesparing van sensoren en MCU
Stroomverbruik wordt een probleem voor embedded systemen die op batterijen werken. Als sensoren en actuatoren worden aangestuurd, kan de batterij leeglopen, zelfs als de MCU in de slaapstand staat. De stappenmotor verbruikt inherent stroom, zelfs in inactieve toestand. Gepubliceerde projecten gebruiken een eenvoudige schakeling bestaande uit twee transistors en drie weerstanden voor het regelen van de 5,0 VDC die de belasting van stroom voorziet met behulp van een GPIO-pin van de MCU.
Dezelfde benadering kan worden toegepast op MCU-gestuurde sensoren die periodiek of door de gebruiker worden aangesproken. Ik heb een schakeling ontworpen om de massaverbinding van sensoren te onderbreken met behulp van een NPN-transistor die tot 1,5 A kan verdragen. De basis van de BD139 transistor wordt rechtstreeks aangestuurd vanuit de GPIO van een MCU (zie Figuur 5).
Als GPIO (D6 in de figuur) hoog wordt, gaat de transistor in verzadiging. Omdat de gemeenschappelijke massalijn van de sensoren is verbonden met de collector van de transistor, kan zo de voeding van de sensoren ingeschakeld worden. Als de GPIO laag wordt, is de transistor uit en de voeding van de sensoren wordt onderbroken.
De lezer kan de energiebesparing nog verder doorvoeren door de uptime van de MCU te verlagen als de bovengenoemde motivaties ook van toepassing zijn op de MCU. Een MCU heeft gewoonlijk verschillende slaapmodi.
Vragen of opmerkingen?
Heeft u vragen of opmerkingen over dit artikel? Stuur een e-mail naar de auteur op drgamallabib@yahoo.co.uk, of neem contact op met Elektor via editor@elektor.com.