Adieu aux stéréotypes de codage de micrologiciels: Alternatives pratiques
sur

Le stéréotype usuel de codage de micrologiciel peut conduire à des perturbations opérationnelles, obligeant les développeurs à revenir sur leur programmation. Que se passe-t-il si votre microcontrôleur perd la connexion au réseau ou fait face à un conflit d’adresse IP ? Dans cet article, nous repensons les pratiques conventionnelles de codage et explorons les solutions efficaces pour augmenter la fiabilité et pour réduire les coûts des projets embarqués.
Many embedded projects I come across in the media provide brilliant solutions to developers but come short in answering critical questions. What action to take if the microcontroller (MCU) loses network connectivity during runtime? How to resolve IP conflicts in LAN? Is it mandatory to use RTC to maintain accurate timing? Does time adjustment mandates user intervention? Do we need to continuously power on sensors during MCU uptime?
We need to stay outside the box for a while and think it over when it comes to stereotype coding! In this article, I review some conventional, or stereotype to be accurate, coding of MCUs that lacks practicality and may require user intervention from time to time to resolve operational disrupts. Normally in such cases, push buttons and displays would be needed to monitor, reset, preset, or interrupt the MCU to take a corrective action. Facilitating user intervention may backfire when GPIOs are scarce to support such buttons and displays alongside interfacing sensors.
When working with Arduino IDE, you will always come across two distinct sections of the code, namely the setup() and the loop(). The former section is dedicated for initializing the MCU, and whatever peripherals and sensors wired to it. The latter section handles runtime activities that may involve reading sensors and taking actions. Dedicating sections as described would fool developers who would take it for granted that their embedded system would work flawlessly but would be surprised that operational disrupts are looming and are capable of forcing them back to the design table. I have been there, facing such a situation and being confronted with the aforementioned questions. However, I reached sensible solutions to overcome operational disrupts and to reduce the cost of running my projects by integrating some traditional setup() activities into the loop() section. In the following case studies, I shall elaborate on this strategy.
Limiting Wi-Fi Configuration to the setup() Section
When the AP reboots due to user intervention or power restore, the hooked MCU looses connection to the WLAN and consequently should reboot to run the setup() again in order to reinitialize Wi-Fi connectivity. So checking on the health or sanity of the Wi-Fi connectivity should be done on a regular basis within the loop() section and react to lost connection to the AP accordingly. Loosing connection to the internet, on the other hand, is another issue that requires detection for web-based applications even with valid WLAN connectivity. Such strategies are rarely addressed in published projects that seem to rely on human intervention availability.
Luckily, ESP8266 has a very interesting Wi-Fi library, the ESP8266WiFii> a>, providing a multitude of features and capabilities that are seldom found in practical projects. Random Nerd Tutorials reviewed wireless reconnection alternatives provided by that library. A combination of the following statements in the setup() section would do the magic:
WiFi.setAutoReconnect(true);
WiFi.persistent(true);
Alternatively, checking on the health of the wireless connection in the loop() section enables taking a recovery action, such as restarting the MCU. In Figure 1 is the code segment that we regularly use in the setup() section but can still use it in the loop() section with a slight modification.

Random Nerd Tutorials also provides a software interrupt alternative for the detection of lost connections and its recovery. For ESP8266, I take a different approach to check the sanity of network connectivity. I code the MCU to check access to its WiFi gateway regularly in the loop() section by pinging it using the function ping(gateway_IP) from ESP8266Ping library . This is referred to as “Ping for Life” which is used by network administrators to check connection status and keep it alive against timeouts. If the ping fails, I force the MCU to reboot and try connecting to an alternative AP. I also use pinging to check accessibility to the Internet but target a DNS server such as Google’s <8.8.4.4> server instead of the local WiFi gateway, using ping(primaryDNS_IP) function. If such regular ping fails, then the MCU would reboot to hook up to an AP with active Internet connectivity. I include the ESP8266Ping library in my sketch to achieve this strategy, as in the code segment in Listing 1.
Listing 1: Ping of Life implementation for WLAN gateway and a global 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();
}
Rebooting the ESP8266 MCU is performed by executing the ESP.restart() function. This function is inherently embedded in the ESP hardware platform installed in the Arduino IDE. In order to switch to a different AP upon restart, the user can check connectivity to alternative APs using the same ping approach and fix the wireless connection to the responsive AP in the setup() section. Listing 1 illustrates code segments that check local AP connectivity as well as Internet accessibility.
Using Real-Time Clock (RTC) Module to Maintain Time
An RTC module maintains its timer operation accurately despite the state of the associated MCU by having a backup battery to support its continuous operation. Whenever the MCU reboots, it fetches the current time from the RTC and accesses it regularly to update its time variables. Many MCUs have built-in timers capable of maintaining millisecond counts since the latest reboot. I like to use such a feature in my code to update the MCU time variables during its uptime. If the MCU has access to the Internet, then it makes sense to reach out to NTP servers to get the current time at reboot. I utilize both strategies to configure and run my ESP8266 modules, where in the setup() section I access an NTP server for fetching time as a start, and in the loop() section I use the millis() function to update that time to the current instant. I also access the NTP server regularly in the loop() section to re-adjust my software maintained clock and to compensate for any deflection that may occur. Such a strategy enables me to dispense the RTC integration in my projects and save GPIO allocations and power consumption.
In the following code snippet, I demonstrate how to access the NTP and maintain the local clock timer to confidently get an accurate real-time clock without an RTC. I set up two main routines that manipulate the clock: the getNtpTime() function for updating the clock based on NTP access (see Listing 2) and the update_time() function for processing the internal MCU timer (see Listing 3).
Listing 2: Update MCU clock while NTP syncing.
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 internal timer update of the clock.
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;
}
Within the loop() section, I call the first function once every hour to compensate for any clock tolerance, and I call the latter every minute in order to update the clock minutes and hours readings. For the sake of initializing the MCU clock upon reboot, I call the NTP function once in the setup() section and declare global variables necessary to display the processed current date and time (see Listing 4). This sequence of activities accomplishes RTC substitution with precision in mind. Sadly, I had to retire my stock of RTCs ever since this strategy paid off.
Listing 4: Sketch initialization with libraries and global variables for time processing.
// 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;
Dynamic IP Addressing of MCU-Based Web Servers
Whenever a web server is run on a dynamically-assigned IP address hardware, the user has to record the server’s IP address following each reboot and pass that address to his web clients. Such network setup requires a DHCP-enabled AP, as in Listing 5 without the WiFi.config() segment (Figure 2).

pool is specified alongside other internet accessibility settings.
However, maintaining LAN devices connectivity in such a way is a tedious job and thus requires a facelift. This is why I am inclined to fix the IP address of web servers in my personal LAN to make it easier to maintain web clients dispersed around my IoT network. Listing 5 shows an example in which I set up my garden lighting controller static network credentials.
Listing 5: Fixed WLAN network 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
Accessing a web server using its domain name rather than its IP address is another alternative way to solve the dilemma of server accessibility. So, for an MCU that is dynamically assigned its IP address, I use the mDNS library dedicated for ESP8266 to assign a domain name to the ESP-based web server and use it in Bonjour-enabled web clients to access the server. I use this strategy even for fixed IP servers, since it simplifies access to web servers for non-technical users who are not familiar with IP addressing. Figure 3 illustrates how I access my garden lighting controller from a web browser using either its DNS assigned name or its fixed IP address.

name or its fixed IP <192.168.1.140>.
Listing 6 illustrates the necessary coding for naming the MCU in the WLAN where MDNS is the object facilitating mDNS functions for activating and maintaining the MCU network name.
Listing 6: Assigning a network name for the MCU for easy accessibility in Bonjour-enabled web clients.
#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
MCU’s IP Address Conflict in LAN
When an IoT network grows bigger with fixed IP allocation in mind, one should consider the possibility of mistakenly allocating the same IP to multiple controllers. Such case produces IP conflicts in the network, leaving conflicting devices inaccessible via the network (see Figure 4). Certainly, a centralized management of network resources can avoid such case, but this is not always the ground truth.

error not only for embedded devices but also for user computing facilities.
On reboot, the MCU initiates a dynamic IP request and pings its intended fixed IP address to make sure it is not allocated to any device on the LAN. Thereafter, the MCU initiates the intended fixed IP connection to the LAN in the setup() section. In Listing 7, I ping the intended MCU’s local_IP declared in Listing 1 prior to assigning it to the MCU. Prior to activating this code segment, the MCU should connect to the LAN using a temporarily DHCP assigned IP address. If the ping fails, then I can be sure that the intended fixed IP is safe to work with; otherwise, the MCU should reboot to recheck conflict resolution every minute.
Listing 7: Pinging IP conflicting network resources.
#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
Saving Power of Sensors and MCU
Power consumption becomes an issue for embedded systems running on batteries. Driving sensors and actuators can drain a powering battery even with the MCU is in sleep mode. The stepper motor inherently consumes power even while in idle state. Published projects adopt a simple circuit composed of two transistors and three resistors for controlling the 5.0 VDC that powers the load using an MCU GPIO pin.
The same approach can be applied to MCU driven sensors that are accessed upon user intervention or periodically. I designed a simpler circuit to control either the driving DC power of the sensors or their connectivity to the ground line. Here, I present a circuit for cutting off sensors’ ground connection using a power NPN transistor that can sink up to 1.5 A. The Base of the BD139 transistor is driven directly from an MCU’s GPIO (see Figure 5).

A High setting of that GPIO (D6 in the figure) will produce a bias voltage, causing the transistor to enter the saturation state and to switch on its collector and emitter connectivity with negligible voltage drop. While the sensors’ common ground line is wired to the transistor’s collector, the powering of the sensors is complete. When the GPIO is set to Low, the transistor will be in the off state, breaking sensors’ powering path.
The reader may consider the power saving even further by reducing the MCU uptime if the aforementioned motivations apply to the MCU as well. An MCU have different levels of sleep modes.
Questions or Comments?
Do you have questions or comments about this article? Email the author at drgamallabib@yahoo.co.uk, or contact Elektor at editor@elektor.com.
Discussion (0 commentaire(s))