How to build an RP2040-based connected clock-Part 2
2022-02-16 | By Maker.io Staff
License: General Public License Arduino
The previous article in this series discussed how to assemble the hardware portion of the RP2040-based connected clock project. It also introduced the schematic diagram of the project and outlined the assembly process. This article explains the software part of this project in great detail. It also introduces the network time protocol that the Arduino uses to synchronize its local clock with an accurate external clock source.
An Overview of Network Time Protocol (NTP)
Network Time Protocol (NTP) is one of the oldest protocols still commonly in use today. It provides computers with a way to synchronize their internal local clocks with an accurate global clock source. Many people use NTP without knowing it, as most consumer operating systems, such as Windows, use the network time protocol to obtain the current time when needed.
NTP operates on a distributed set of computers arranged in so-called stratum levels. Stratum zero contains the most accurate available time, and it serves as the source for all other devices. Level zero comprises high-precision timing devices, such as atomic clocks. The computers on stratum level one is physically connected to the high-precision clocks. Computers on lower stratum levels are typically connected via logical connections, for example, over the Internet. Devices on lower stratum levels request the time from one or multiple computers located directly one level above them. The clock signal’s accuracy decreases with every additional stratum level, as every computer in the chain adds a bit of a delay. At one point, your Arduino may query the current time from the global NTP pool. The pool automatically picks the best server based on your location and returns the time.
How to Use the Network Time Protocol (NTP) on an Arduino
Thanks to the NTPClient library, you don’t have to know the nitty-gritty details of NTP to obtain the current time in your timezone using an Arduino. To begin, install the NTPClient library using the Arduino IDE’s built-in library manager. Feel free to check out this guide if you don’t know how to install libraries.
Use the Arduino IDE’s built-in library manager to install the three libraries.
Note that I used the classic Arduino IDE for this project. You can check out this guide for updated instructions that work in version 2.0 of the Arduino IDE. Either way, make sure that you also install the “Adafruit LED Backpack” and “Adafruit GFX” libraries, as we’ll need them later.
Once you’ve installed the libraries, you can import them into your Arduino sketch:
#include <WiFiNINA.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include "Adafruit_LEDBackpack.h"
The first three libraries let your Arduino establish a connection to your local Wi-Fi network and query the current time. The last three libraries help the Arduino send data to the 14-segment display.
Next, I defined a few variables:
const long utcOffsetWinter = 3600; // Offset from UTC in seconds (3600 seconds = 1h) -- UTC+1 (Central European Winter Time)
const long utcOffsetSummer = 7200; // Offset from UTC in seconds (7200 seconds = 2h) -- UTC+2 (Central European Summer Time)
unsigned long lastDisplayUpdate = 0UL;
unsigned long lastNTPrequest = 0UL;
unsigned h = 12;
unsigned m = 52;
unsigned s = 0;
// Define an NTP Client object
WiFiUDP udpSocket;
NTPClient ntpClient(udpSocket, "pool.ntp.org", utcOffsetWinter);
The first two numbers describe the offset from UTC in your local time zone. In my case, I used the central European time, which is UTC+1 in winter. Next, I created three variables that will hold the time returned by the NTP. For now, I added some dummy values. Lastly, I defined WiFiUDP and NTPClient objects. I supplied the NTPClient with the NTP address and the offset from UTC. I used the global NTP pool that automatically picks the best server for your location.
Next, I instructed the Arduino to establish a connection to a Wi-Fi network and to start the NTP client in the setup()-method:
void setup()
{
/* Display setup code omitted – see below */
WiFi.begin(SSID, PASS);
while (WiFi.status() != WL_CONNECTED)
{ }
ntpClient.begin();
}
I added a while loop that doesn’t let the program continue until the development board connects to a Wi-Fi network. Once it opens a connection, the Arduino can query the current time whenever it wants. However, I decided to get the current time once at startup and then every thirty minutes in the loop()-method:
void loop()
{
unsigned long currentMillis = millis();
// Adjust the Arduino's internal time every 30 minutes
if(lastNTPrequest == 0 || currentMillis - lastNTPrequest > NTP_REQUEST_FREQUENCY)
{
ntpClient.update();
h = ntpClient.getHours();
m = ntpClient.getMinutes();
s = ntpClient.getSeconds();
lastNTPrequest = currentMillis;
}
if(currentMillis - lastDisplayUpdate > DISPLAY_UPDATE_FREQUENCY)
{
/* Display update code omitted – see below */
}
}
As you can see, the update()-method checks how much time has elapsed since the last NTP query. If there was no request yet or it happened more than thirty minutes ago, the Arduino uses the ntpClient.update()-function to obtain the current time. Then, the program sets the h, m, and s variables defined above.
Update the Internal Clock and the 14-segment Display
The 14-segment display requires two additional variables:
#define DISPLAY_ADDRESS 0x70
Adafruit_AlphaNum4 disp = Adafruit_AlphaNum4();
The disp object represents the 14-segment display, and the Arduino can use it to communicate with the physical display. But before the program can do that, you need to initialize the display in the setup()-function:
disp.begin(DISPLAY_ADDRESS);
disp.setBrightness(16);
disp.clear();
/* Display output omitted */
disp.writeDisplay();
As mentioned above, the Arduino only requests the current time every thirty minutes. In between that period, the Arduino keeps track of the time itself. For that purpose, there is another if-block in the loop method of the firmware project:
void loop()
{
unsigned long currentMillis = millis();
/* NTP update code omitted – see above */
if(currentMillis - lastDisplayUpdate > DISPLAY_UPDATE_FREQUENCY)
{
s = s + 1;
if(s >= 60)
{
m = m + 1;
s = 0;
if(m >= 60)
{
h = (h + 1) % 24;
m = 0;
}
}
displayTime();
secondIndicatorOn = !secondIndicatorOn;
lastDisplayUpdate = currentMillis;
}
}
This code might look like a lot, but it only advances the internal time once every second. As mentioned above, the Arduino stores three variables that represent the current seconds, minutes, and hour. In the loop method, the Arduino increments “s” once every second. If the variable reaches a value of 60, the Arduino resets the seconds to zero and advances the minutes. If “m” is 60, the Arduino resets the variable to zero and increments the hours.
Lastly, the Arduino calls a helper function that updates the display to show the current time:
void displayTime(void)
{
disp.clear();
int dh = h;
if(TWELVE_HOUR_FORMAT)
{
if(h == 0)
dh = 12;
else if(h > 12)
dh = h - 12;
}
bool pmIndicatorOn = (h >= 12);
int hourFirstDigit = dh / 10;
int hourSecondDigit = dh - (hourFirstDigit * 10);
int minuteFirstDigit = m / 10;
int minuteSecondDigit = m - (minuteFirstDigit * 10);
disp.writeDigitAscii(0, '0' + hourFirstDigit);
disp.writeDigitAscii(1, '0' + hourSecondDigit, secondIndicatorOn);
disp.writeDigitAscii(2, '0' + minuteFirstDigit);
disp.writeDigitAscii(3, '0' + minuteSecondDigit, pmIndicatorOn);
disp.writeDisplay();
}
This helper function first clears the display. Then, the Arduino checks whether the user wants the time to be displayed in the 12-hour time format. Note that the Arduino gets the time from the NTP server in 24-hour format, so it needs to convert it first if someone wants to use the 12-hour display style. Once it finishes the conversion, the Arduino splits the hours and minutes into individual digits. That’s necessary because the software can only transmit a single character to the display at once. Once it has sent all the character information to the display, the Arduino calls the writeDisplay function. Doing so makes the 14-segment screen show the current time.
Besides the current time, the clock also displays a light that flashes once every second, as well as an AM/PM indicator that stays on once the time advances past noon and turns off at midnight.
As the last step, upload the code to the Arduino RP2040 Connect. Note that you have to install the board support files first. You can follow this guide if you don’t know how to get started with the RP2040 Connect.
Download the Firmware Code
You can download the firmware code here.
Summary
The Network Time Protocol is one of the oldest computer protocols in use today. It allows devices to synchronize their internal clock with a reliable external clock source. The Arduino uses a simple-to-use library to communicate with the global NTP pool. It queries the time once at startup and then every thirty minutes. In the meantime, the Arduino keeps track of time by updating three internal variables every second. Then, the Arduino uses the Adafruit LED backpack library to send the current time to the display.
Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.
Visit TechForum