A GPS-synchronized digital clock based on the open-source Arduino hardware platform.
The COVID-19 lockdown rekindled a decades-long interest of mine in learning a bit more about electronics. Even though I was fascinated with the subject at a very early age, my interests gravitated towards software, and 40 years later, here I am clearing the cobwebs and delving into the study of electronics.
The best and most rewarding way to learn something new is to build something tangible and functional. This project is a culmination of my studies and hacking over a period of three months. It is a nontrival project in the sense that a variety of components are needed to build a digital clock that automatically sets the time and date based on GPS signals.
Bear with me if you happen to be a long-time Arduino hacker and developer, as this is my first serious project other than piecing things together with breadboards.
- Elegoo Uno R3 (1)
- Adafruit Ultimate GPS Logger Shield (1)
- Adafruit 0.56" 4-Digit 7-Segment Display with I2C Backpack (3)
- Adafruit Standard LCD 20x4 (1)
- Adafruit I2C LCD Backpack (1)
- Adafruit Rotary Encoder (1)
- ElectroCookie Large Solderable PCB (1)
- ElectroCookie Mini Solderable PCB (1)
Three 4-digit LED displays are used to show the local time in YYYY.MM.DD HH:MM format. The I2C backpacks are packaged unattached to the LED displays, which means they requiring soldering as part of the assembly process. The I2C backpacks are very convenient because they essentially need only two (2) pins on the Aurduino board for sending commands to the display. Furthermore, since I2C is a serial protocol that acts like a bus, multiple I2C-aware components can be attached to the same pins. However, they do require unique addresses, which will be discussed shortly.
These are the materials that ship with the 4-digit displays.
Solder the backpack to the display, but make sure it is not attached upside down. The dots are a convenient visual aid for aligning to the correct orientation. Solder four (4) of the pin headers to the top of the backpack. Repeat the same process for the remaining displays and backpacks.
This is a view of the assembled LED display.
Once the LED displays have been assembled, each needs to be assigned a unique I2C address. This is accomplished by soldering jumpers on the underside of the backpack. There are three (3) jumpers that act like bits, so as one can imagine, there are 8 unique addresses. Each backpack comes with all three jumpers open, which is equivalent to address 0x70
. The gpsconfig.h
file shows the address chosen for each LED display.
The LED displays are then arranged onto a larger PCB. The YYYY and MMDD displays are essentially touching each other while the HHMM has a small amount of space between its adjacent display. This was more of a design aesthetic than anything else.
This is a view of the LED displays attached to the PCB.
This is the wiring on the front side of the PCB before the LED displays were soldered. Wires from the power rail attach to pins on each display. And, the two pins representing the I2C bus are attached to each display in parallel.
Finally, this is the back of the PCB. Note that pin headers for power (+/-) and I2C were attached on both ends of the board. The reason for an additional set of headers is that they are being used to connect the LCD display, which is discussed later.
A single 20-column, 4-row LCD display is used to show GPS information and the currently selected timezone. The I2C backpack for the LCD was sold separately, but as mentioned above, is very convenient in reducing pin consumption on the Arduino board. The pins on the LED display simply connect to the LCD display.
This is a view of the front side of the LCD attached to the backpack.
And, this is a view of the back side of the LCD. Notice that I used the screw headers instead of pin headers. In retrospect, I would have soldered pin headers but did not want the hassle of removing the backpack and resoldering. Since the LCD display is operating on the same I2C pins, it requires an address that does not conflict with the LED displays (0x70
, 0x71
, 0x72
). If you look closely, the A0
and A1
jumpers on the lower left of the backpack are soldered together, making the address of the LCD 0x73
.
The rotary encoder used in this project rotates infinitely in both directions with a nice mechanical pulse as it moves around. It also has a push button action. The encoder is used to select the timezone in 30-minute increments. For a variety of reasons, I kept the hardware and software simple with respect to timezone. Indeed, there are web services that will convert latitude/longitude to timezone, but this clock was designed to be self-contained and not rely on the presence of a wifi endpoint. The timezone offset is selected by rotating the encoder in either direction, but the selection is not committed until the encoder is pressed. Once committed, the local time shown in the LED display is modified accordingly. If a timezone is not selected, local time is always equivalent to UTC. The timezone is also stored in EEPROM so it can be recovered if the power source is interrupted.
As seen in this photo, the rotary encoder is mounted on a small PCB. It could also be mounted to a face plate.
This is a view of the back side of the PCB. Similar to other components, pin headers are used for making connections to the encoder.
The GPS module conveniently mounts directly on top of the Arduino Uno board. A set of pin headers need to be soldered to the GPS board, but once completed, both PCBs slide together nicely.
This is a view of the GPS module mounted to the Arduino. Notice the additional pin headers I soldered to the GPS board. These are the pins needed to connect the LEDs, LCD and rotary encoder.
This is a side view of the GPS mounted to the Arduino. You can see the pin headers for power and those used for the I2C serial bus (A4
, A5
).
This is the other side of the GPS. The visible pin headers (9
, 10
, 11
) connect to the rotary encoder.
This is a view of all components connected together and arranged into a final product. The use of pin headers made it very convenient to delay the majority of decisions about the layout of components in a box or similar structure. However, during the final assembly process, I had to eliminate some of the headers since they were obstructing other components. Also, notice how the rotary encoder is stacked on top of the GPS module.
This is an operating view of the clock with realtime GPS information displayed on the LCD. Once a satellite fix has been established, the LCD shows the latitude and longitude in decimal degrees format. It also displays the number of satellites for which a fix has been established and the UTC time. The selected timezone is also shown, but this is governed by the rotary encoder, not a data point from the GPS module.
The Arduino CLI is used for both compilation of source code and uploading of the binary to the actual board. I found this CLI preferrable over the Arduino IDE because it allows the build and upload process to be fully described through the makefile. Simplicity of the development environment was essential. That said, if you prefer an IDE experience, I found the Arduino for Visual Studio Code extension to be much nicer than Arduino IDE.
Installs the Arduino core and dependent external libraries. This is an idempotent operation that first updates the library index and then ensures that all versioned dependencies are downloaded.
make install
Builds the sketch and its associated C++ files. The output directory of the build cannot be a subdirectory of the project, a restriction imposed by the Arduino compilation process, so it is placed under TMPDIR
. However, the final program artifacts are written to the project directory.
make build
Uploads the program to the Arduino board. Make sure PORT
is defined by the environment or provided as an argument to make
. PORT
is the serial port to which the Arduino board is attached. If undefined, it defaults to /dev/null
and will cause an upload attempt to fail.
make upload
make upload PORT=/dev/tty.usbmodem14401
export PORT=/dev/tty.usbmodem14401
make upload
Removes transient build files.
make clean
Performs an installation of libraries followed by a build and upload of the final program to the Arduino board.
make all
Copyright 2020 David Edwards
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.