This is only a preview of the September 2024 issue of Practical Electronics. You can view 0 of the 80 pages in the full issue. Articles in this series:
Articles in this series:
Articles in this series:
Articles in this series:
Articles in this series:
Articles in this series:
Articles in this series:
|
Teach-In 2024
Learn electronics with
the ESP32 by Mike Tooley
Part 7 – Telling the Time
I
n the last part of our Teach-In series, we ex-
plored some of the ESP32’s inbuilt features that make wireless communication a reality. That started with a simple
wireless Access Point to send environmental data that can be
displayed on a phone, tablet, laptop or desktop PC.
This instalment continues that theme by showing how an
ESP32 can connect to the Internet and use a network time
protocol (NTP) server to obtain the current time with very
high accuracy.
Our Practical Project features the construction of a highly
accurate digital clock with a low-cost 16 × 2 LCD panel display,
while our Coding Workshop will take your coding skills to a
new level by introducing arrays and structures.
The learning objectives for the seventh part of our series are:
n Know how to connect to an NTP time server
n Know how to format and display NTP data
n Know how to use C++ arrays and structures
The Network Time Protocol (NTP)
The Network Time Protocol is a set of rules for synchronising computers and microcontrollers using data derived from
a standard network time reference. This allows you to obtain
the time and date with very high precision and without any
additional hardware.
NTP servers (see Table 7.1) must operate independently of
their locale, providing worldwide access to UTC (Universal
Coordinated Time).
Like GMT (Greenwich Mean Time), UTC is the same all over
the world and needs to be adjusted for the local time zone and
daylight saving, where appropriate.
To use NTP with an ESP32 (or similar microcontroller),
we usually configure the ESP32 as a client that connects to
an NTP server via the UDP (User Datagram Protocol) on port
123. Once connected, the client sends a request packet to the
Table 7.1 – Internet time servers
Region
Host name
Asia
asia.pool.ntp.org
Europe
europe.pool.ntp.org
North America
north-america.pool.ntp.org
Oceania
oceania.pool.ntp.org
South America
south-america.pool.ntp.org
Worldwide
pool.ntp.org
70
About Teach-In
Our latest Teach-In series is about using the popular ESP32
module as a basis for learning electronics and coding. We
will be making no assumptions about your coding ability
or your previous experience of electronics. If you know one
but not the other, you have come to the right place. On
the other hand, if you happen to be a complete newbie
there’s no need to worry because the series will take a
progressive hands-on approach. There will be plenty of
time to build up your knowledge and plenty of opportunity
to test things out along the way.
We’ve not included too much basic theory because this
can be easily found elsewhere, including several of our
previous Teach-In series, see:
https://bit.ly/pe-ti
https://bit.ly/pe-ti-bundle
Earch month, there’ll be projects and challenges to help you
check and develop your understanding of the topics covered.
server, which responds with a timestamp packet containing
data that’s parsed to extract the required information.
Using the public Internet, NTP usually maintains time to
within just a few milliseconds, but when used in the more
controlled environment of a LAN (Local Area Network), the
accuracy can be improved to within 1ms.
Unlike protocols like TCP, UDP does not rely on handshaking and does not keep track of what’s sent. It thus avoids
the repeat transmissions and overheads associated with error
checking, and is the preferred method for time-critical applications like NTP.
Time zones
To compensate for different time zones, it is necessary to
adjust the UTC offset. This is calculated by multiplying the
GMT zone by 3600 (the number of seconds for each hour difference in GMT).
This is quite easy to do, as illustrated by the time zone and
daylight saving offsets for various cities in Table 7.2.
For example, the GMT offset for Paris is gmtOffset_sec =
3600 while that for Singapore is gmtOffset_sec = 28800.
In our code, each of these time zone offsets would be defined
as longs using lines like these two examples:
const long gmtOffset_sec = 3600; // Paris
const long gmtOffset_sec = 28800; // Singapore
Practical Electronics | September | 2024
Gotcha!
Because UTC and GMT share the same current time, they are
often confused. The essential difference between the two is
that UTC is a time standard while GMT is a time zone that
just happens to have zero offset from UTC.
Daylight saving time (DST)
First proposed by Benjamin Franklin in Paris in 1784,
daylight saving is the practice of putting clocks backwards
in winter and forwards in summer to make best use of the
available daylight. Today, many countries observe this
practice, so it must be accommodated when obtaining local
times from UTC.
To obtain the time adjusted for daylight saving, we use a
further one-hour offset which is applied during the applicable
months.
Since we are only dealing with an offset of 3600 seconds, we
can use an integer value with a line of the form:
const int daylightOffset_sec = 3600; // DST offset
Having determined the required time zone and DST offsets,
we are ready to configure the application using the values that
we have defined. This can be achieved using the timeconfigTime() function, as follows:
timeconfigTime(gmtOffset_sec,
daylightOffset_sec, ntpServer);
Check it out!
The time has come to check out your ESP32’s ability to obtain
and display NTP data, so let’s connect to the default (worldwide) NTP time server, retrieve the current time and display it
using the Serial Monitor. That might sound complicated, but
it’s actually quite easy!
We need to include two required libraries, <WiFi.h> and
“time.h”, using:
#include <WiFi.h>
#include "time.h"
The first of these two libraries manages the ESP32’s Wi-Fi
interface while the second facilitates the exchange of data between the ESP32 (as a client) and the NTP time server.
Next, we will need to define various network parameters
including the SSID (see last month), Wi-Fi password, NTP time
server and the required offsets for GMT (time zone) and DST.
You may have a different offset, but for London (UK), this is
achieved using code like:
// Network parameters
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 0;
const int daylightOffset_sec = 3600;
The getLocalTime() function will send a request packet to
the NTP server, parse the time stamp packet received, and
store the date and time information extracted from it in a time
structure called timeinfo.
There will be more information about the use of C++ structures is in the Coding Workshop below, under the heading
Coding Workshop on page 72.
The complete code listing appears in Listing 7.1, while
Fig.7.1 is a screen grab showing how the data appears in the
ESP32’s Serial Monitor.
Practical Electronics | September | 2024
Table 7.2 – Time zones and DST offsets for various cities
City & country
Time zone
Offset
DST?
Adelaide, Australia
GMT+9½
9.5 × 3600 = 34200
Yes
Athens, Greece
GMT+2
2 × 3600 = 7200
Yes
Brisbane, Australia
GMT+10
10 × 3600 = 36000
No
Helsinki, Finland
GMT+2
2 × 3600 = 7200
Yes
London, England
GMT+0
0 × 3600 = 3600
Yes
New York, USA
GMT−5
−5 × 3600 = −18000
Yes
Paris, France
GMT+1
1 × 3600 = 3600
Yes
Singapore
GMT+8
8 × 3600 = 28800
No
Listing 7.1 – Fetch and display NTP time data using the
ESP32’s Serial Monitor
// Include the required library files
#include <WiFi.h>
#include "time.h"
// Network parameters
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";
const char* ntpServer = "pool.ntp.org";
const long gmtOff_sec = 0;
// Change if necessary
const int daylightOff_sec = 3600; // Change if nec.
void setup() {
Serial.begin(115200);
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
// Initialise and get the time
configTime(gmtOff_sec, daylightOff_sec, ntpServer);
printLocalTime();
// Disconnect WiFi
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
}
void loop() {
delay(1000);
printLocalTime();
}
void printLocalTime() {
struct tm timeinfo;
// Warn if time is not found
if (!getLocalTime(&timeinfo)) {
Serial.println("Time not found!");
Serial.println("Is the router OK?");
return;
}
// Display the current time
Serial.println("* CURRENT NTP TIME *");
Serial.println(&timeinfo, "%H:%M:%S");
Serial.println(&timeinfo, "%A %d");
Serial.println("");
}
Gotcha!
The default NTP time server at pool.ntp.org automatically
selects a time server that is nearby, but you can specify a
different time server selected from the list in Table 7.1. You
may also need to change one or both offsets as required for
your location.
71
Table 7.3 – Parsed values returned by getLocalTime()
Fig.7.1: The output of the ESP32 Serial Monitor running the NTP
sketch in Listing 7.1.
Working with time data
A wide variety of options can be used to extract the data that
you need from the timeinfo structure. This makes it delightfully
easy to manipulate and format the data that you need. Table
7.3 lists the available specifiers.
For example, %A is used to obtain just the day of the week
while %F will provide you with the data in long (YYYY-MMDD) format.
The code fragment in Listing 7.2 shows just a few of the
formatting option for date/time strings. The result of running
the code is shown in Fig.7.2.
Specifier
Value returned
Example
%A
Day of the week
Monday
%a
Day of the week (abbreviated)
Mon
%B
Month of the year
January
%b
Month of the year (abbreviated)
Jan
%D
Short format date (MM/DD/YY)
05/15/24
%d
Day of the month
12
%F
Long format date (YYYY-MM-DD)
2024-02-10
%H
Hour of the day (24-hour format)
21
%I
Hour of the day (12-hour format)
9
%j
Day of the year
211
%Y
Year
2024
%y
Last two digits of the year
24
%m
Month as a number
05
%M
Minute of the hour
59
%p
AM or PM
PM
%r
12-hour clock time
09:15:21 am
%R
24-hour time HH: MM
21:19
%S
Second of the minute
21
%T
Formatted time (HH: MM: SS)
09:15:21
Arrays provide a way of storing multiple values within a
single variable, avoiding the need for separate variable declarations.
Structures take this one stage further by allowing you to group
variables regardless of their type. Let’s see how they work.
String arrays
We briefly mentioned string arrays in Part 6 of this Teach-In
series, when we showed how strings are stored as an array of
alphanumeric characters.
Arrays can be extremely useful, particularly when storing
and manipulating the same data types.
Coding Workshop
To declare an array, you simply need to define the variable
This month’s Coding Workshop provides you with an introductype, specify the name for the array and the number of elements
tion to the arrays and structures available in C++.
that need to be stored. It is possible to have multi-dimensional
arrays, but we’ll stick to a single dimension here.
Note that the name of the array should be immediately folListing 7.2 – Some possible date/time string formats
lowed by square brackets containing the number of items in
the array. Here are two examples:
// Display current time in different formats
Serial.print("Formatted time: \t");
string colourChannel[5];
Serial.println(&timeinfo, "%T");
Serial.print("Time (24 hour format): \t");
Serial.println(&timeinfo, "%R");
This declares a colourChannel[] array that will hold six string
Serial.print("Time (12 hour format): \t");
Serial.println(&timeinfo, "%r");
Serial.print("Minutes and seconds: \t");
Serial.println(&timeinfo, "%M:%S" );
Serial.print("Month, day and year): \t");
Serial.println(&timeinfo, "%B %d %Y");
Serial.print("Date (long format): \t");
Serial.println(&timeinfo, "%F");
Serial.print("Date (short format): \t");
Serial.println(&timeinfo, "%D");
Serial.print("AM or PM: \t\t");
Serial.println(&timeinfo, "%p");
Serial.print("Day of the year: \t");
Serial.println(&timeinfo, "%j");
Serial.println("");
Fig.7.2: The result of running the code in Listing 7.2.
72
Practical Electronics | September | 2024
Gotcha!
Listing 7.3 – Example of using data structures with the ESP32
Array indexes start with 0 so that [0] is the first element, [1]
is the second element, and so on. It’s important to remember
this if you’re coding with arrays!
/* Using structures with the ESP32 */
variables. To place values into the array, we can arrange them
in the form of a comma-separated list enclosed by a pair of
curly braces:
string colourChannel[5] = {"Red", "Green", "Blue",
"Black", "White"};
// Declare our structure
struct sensors {
String name;
String type;
int address;
bool status;
int calibration;
long data;
} Sensor1, Sensor2, Sensor3;
void setup() {
Serial.begin(115200);
Each string in the list is enclosed in its own opening and
closing quotation marks. Without these, they won’t be interpreted as strings!
// Define the first sensor
Sensor1.name = "Lobby"; // Lobby sensor
Sensor1.type = "72X121";
Sensor1.address = 64;
Sensor1.status = false;
Sensor1.calibration = 255;
Sensor1.data = 0;
Numeric arrays
Numerical values are handled similarly. For example, the following code fragment creates an array of four longs (32-bit or
64-bit integers) designed for use in the RF synthesiser of an
FT8 HF transceiver:
// Define the second sensor
Sensor2.name = "Hall"; // Hall sensor
Sensor2.type = "72X121";
Sensor2.address = 64;
Sensor2.status = false;
Sensor2.calibration = 127;
Sensor2.data = 0;
long txFT8[4] = { 3573000, 7074000, 10136000,
14074000 };
Accessing array elements
Individual elements stored within an array are accessed using
an index number with a value from 0 to one less than the size
of the array.
This can be a little confusing, so we’ve provided some
examples in Tables 7.4 and 7.5 which respectively show
how the variables in our two previous array examples are
indexed.
Structures
Structures, like those that we met earlier using NTP, provide a neat and efficient way of grouping related variables.
However, unlike arrays, structures can contain a mixture of
many different data types. In contrast, all the elements in a
single array must be of the same type.
Each of the variables becomes a member of the structure.
To create a structure, use the struct keyword and then declare
each of its members, as shown in Listing 7.3.
In Listing 7.3, the sensors structure has seven members
of which two are strings (name and type), two are integers
(address and calibration), one is a long (data), and one is a
Boolean (status).
Each of the sensors (Sensor1, Sensor2 and Sensor3) shares
the same structure.
The variables that apply to the individual sensors are defined
and initialised in the code, starting with Sensor1 and ending
with Sensor3.
To show how the structure might be used, the loop included
in the listing has some sample data for display using the ESP32’s
Serial Monitor.
Table 7.4 – Indexing the colourChannel[] array
// Sensor name
// Sensor type
// Bus address
// Status flag
// Calibration factor
// Sensor data
// Three sensors
// Define the third sensor
Sensor3.name = "Office"; // Office sensor
Sensor3.type = "92Y200";
Sensor3.address = 64;
Sensor3.status = false;
Sensor3.calibration = 192;
Sensor3.data = 0;
}
void loop() {
// Code to fetch and process sensor data goes here
Sensor1.status = true;
// Sensor OK
Sensor2.status = false; // Faulty sensor detected
Sensor3.status = true;
// Sensor OK
// But for test purposes add some data here
Sensor1.data = 12928;
Sensor2.data = 0;
// No data returned
Sensor3.data = 22491;
// Check each sensor and if online display its data
Serial.println("Current sensor data: ");
if (Sensor1.status == true) {
Serial.println(Sensor1.name + "\t" + Sensor1.data);
} else {
Serial.println(Sensor1.name + "\tFault!");
}
if (Sensor2.status == true) {
Serial.println(Sensor2.name + "\t" + Sensor2.data);
} else {
Serial.println(Sensor2.name + "\tFault!");
}
if (Sensor3.status == true) {
Serial.println(Sensor3.name + "\t" + Sensor3.data);
} else {
Serial.println(Sensor3.name + "\tFault!");
}
Serial.println();
delay(5000); // Wait five seconds
}
Array index
Code reference
String stored
Table 7.5 – Indexing the txFT8[] array
0
colourChannel[0]
Red
Array index
Code reference
Numeric value stored
1
colourChannel[1]
Green
0
txFT8[0]
3573000
2
colourChannel[2]
Blue
1
txFT8[1]
7074000
3
colourChannel[3]
Black
2
txFT8[2]
10136000
4
colourChannel[4]
White
3
txFT8[3]
14074000
Practical Electronics | September | 2024
73
A Practical Project
Our Practical Project uses NTP to convert the ESP32 into a
network-synchronised clock with the current time displayed
on a low-cost 16 × 2 LCD panel.
The circuit to interface the ESP32 to the 16 × 2 LCD panel
is shown in Fig.7.3.
The LCD panel is fitted with an I2C interface (see Part 5) that
only requires four connections: GND, VCC, SDA (I2C data) and
SCL (I2C clock).
To centre the time on the LCD display, so that it looks nice,
we need to be able to address individual character cells, as
shown in Fig.7.4.
For example, after first initialising the LCD screen, to place
the letter ‘A’ in row 0, column 9 of the display we would use
the following code:
Fig.7.3: Connecting the
alphanumeric LCD screen
to the ESP32 only requires
four wires. Two are for the
bidirectional I2C serial bus
(SDA & SCL), while the
other two deliver power and
provide a common ground
for serial communications.
Teach-In Challenge
lcd.setCursor(9, 0); // Cursor to row 0, column 9 The functionality of this month’s Practical Project can easily
be extended by adding one or more relays to control external
lcd.print("A");
// Display the character
equipment. It can switch this equipment on or off based on the
Whereas, to display the letter B in row 1, column 5, we use: current time and/or date.
Our Teach-In Challenge involves adding a single relay that
lcd.setCursor(5, 1); // Cursor to row 1, column 5 can be used to control some external night security lighting
that’s to operate during the period 20:00 to 07:00. The relay
lcd.print("B");
// Display the character
(RL1) is similar to those that we met earlier in Part 5 of TeachOn each occasion, we’ve used lcd.setCursor to locate the In and only requires simple interface circuitry along the lines
of that shown in Figs.7.6 and Fig.7.7.
character cell before printing the character.
You will need to define the I/O pin for RL1 using a line of
The preferred layout for the date and time display is shown
the form:
in Fig.7.5.
The starting point for the hours, minutes and seconds string
is set to row 0, column 4, while the day and date appear below, // Define the I/O pin
const int LightingRelay = 23;
starting at row 1, column 4.
The prototype layout for the network-synchronised clock is
Thereafter, we will be able to control the lighting within a
shown in the photo below, while the code for the ESP32 NTP
conditional statement using the function call:
clock is shown in Listing 7.4.
74
Practical Electronics | September | 2024
Fig.7.5: How we have chosen to display the time
and date in the available space on the LCD.
Fig.7.4: Characters on the 16×2 LCD are identified by row and
column numbers.
digitalWrite(LightingRelay, HIGH)
... to turn the lighting ‘on’, and ...
digitalWrite(LightingRelay, LOW)
... to turn the lighting ‘off’. We will leave the conditional
statements to you.
Next month
Next month, we will be showing you how to add a low-cost
GPS module to your ESP32. This will allow you to use your
ESP32 with an inexpensive LCD panel to obtain and display
accurate time, location, altitude and speed data.
One important advantage of GPS time over NTP is that it wil
work just about anywhere and does not need Internet access.
Coding Workshop will provide you with some pointers to
help you develop code to be used with GPS/GNSS (Global
Navigation Satellite System) modules and our Practical Project
PE
will feature a simple GPS/GNSS location fixer.
Listing 7.4 – Code for the ESP32 NTP clock
/* Simple ESP32 clock using a 16 x 2 LCD display
and time data obtained from an NTP server. */
// Include the required library files
#include <WiFi.h>
#include "time.h"
#include <LiquidCrystal_I2C.h>
// Define the LCD parameters
int lcdColumns = 16;
int lcdRows = 2;
// Setup the LCD
LiquidCrystal_I2C lcd(0x27, lcdColumns, lcdRows);
// Network parameters
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 0;
const int daylightOffset_sec = 3600;
void setup() {
// Initialise the LCD
lcd.init();
// Turn the backlight on
lcd.backlight();
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
// Initialise and get the time
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
printLocalTime();
Fig.7.6: This circuit is the same
as Fig.7.3 except now we have
introduced a relay module
controlled by the ESP32.
// Disconnect WiFi
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
}
void loop() {
delay(1000);
printLocalTime();
}
void printLocalTime() {
struct tm timeinfo;
// Warn if time is not found
if (!getLocalTime(&timeinfo)) {
lcd.setCursor(0, 0); // First row
lcd.print("Time not found!");
lcd.setCursor(0, 1); // Second row
lcd.print("Check router?");
return;
}
// Send the time info to the LCD
lcd.setCursor(4, 0); // First row
lcd.print(&timeinfo, "%H:%M:%S");
lcd.setCursor(4, 1); // Second row
lcd.print(&timeinfo, "%A %d");
}
Fig.7.7: the relay module requires just three extra wires.
Practical Electronics | September | 2024
75
|