This project started on a whim -- years ago I'd done some work with GPS receivers and decoding their NMEA-0183 serial data stream, and recently I thought it might be fun to make a very accurate clock using the "Time" field contained within the NMEA-0183 message stream.
The GPS receivers I've used in the past sent a packet of NEMA-0183 messages from their serial port once a second. These messages contain information such as data and time, longitude and latitude of the receiver, and the satellites that it is receiving.
The GPS time field contains a very accurate representation of time, formated as HHMMSS.xxx, where HH is hours, MM is minutes, SS is seconds, and xxx represents fractions of a second. Hours is referenced to GMT.
I only wanted to display hours and minutes -- displaying seconds, too, would make the display too "busy", in my opinion. So although I receive a new time from the GPS satellite network every second, the display is only updated on a minute-by-minute basis.
On the front panel, in addition the four digit time display, I've mounted a photoresistor for measuring the ambient light level at the clock's front panel. This measurement allows the display brightness to change with a room's ambient light level, i.e. minimum brightness for a dark room to maximum brightness for a very light room (there are four steps of brightness).
I've included a software menu system that lets me, via the front panel's four digit numerical display and two switches on the receivers back panel, select and change the following clock settings:
- Standard-Time offset from UTC (e.g. set to -8 hours in California).
- Select 12 or 24 hour format.
- Select the Display Brightness Level (4 steps).
- Enable Display Brightness to change with ambient light level (or remain fixed).
- Enable the display's cursor (between the Hour and Minute fields) to "blink" (on for a second, off for a second), or remain ON constantly.
- View the number of satellites the GPS receiver is tracking (for debugging).
- View the ambient light level (for debugging).
Hardware:
GPS satellite data is received via a u-blox GY-NEO6M Rev2 GPS module using a NEO6M GPS receiver (purchased from Amazon -- interestingly, the seller disappeared several weeks later!).
This module sends a serial data-stream once a second to an Arduino Nano processor, which decodes the data-stream, strips out the time field, and displays it on a 4-digit seven-segment numerical display.
Although the Nano receives serial data from the NEO6M receiver, the Nano does not transmit data back to the NEO6M (there really is no need to do this during normal operation of the clock).
However, for changing the NEO6M receiver's parameters, such as its baud rate, I've included a 5-pin header onto which a CH340 USB adapter can be temporarily mounted (in this case, I use a SparkFun Serial Basic Breakout CH340G board):
The Nano's and the GPS receiver's Serial Ports operate at different logic levels -- the Nano's is 5V logic while the Receiver's is 3.3V logic. Given the voltage drop introduced by the Nano's 5V regulator, etc., the acceptable signaling-voltage margins can be on the edge. I did not feel comfortable with the narrowness of these margins, and so I decided it better to include bi-directional level shifters between the Nano's 5V and the Receiver's 3.3V I/O.
Such level shifters consist of two resistors and a single MOSFET transistor (e.g. BSS138) per channel. They can be purchased, pre-assembled on PCBs, from, say, Amazon:
There is a photoresistor, whose resistance changes with the level of ambient light, mounted on the front panel. The Nano reads an analog value of the voltage generated with this photo resistor, and the time display's "brightness" can be adjusted accordingly. I wasn't sure what value of resistance for normal ambient light I'd need, so I purchased a selection of 5 different types of photoresistors so that I could experiment:
The display is a TM1637 Module, which uses a 2-wire Clock/Data interface (there is an Arduino library of TM1637 functions). Unsure of what color to use, I purchased a pack of five modules, each with a different color:
In summary -- a very basic design consisting of three HW modules mounted on a thru-hole board (Nano, Level-Shifter, and GPS Receiver), and a separate Display module.
I brought the antenna connection out to the clock's back panel (via a uFL-male to SMA-female cable), rather than mounting within the project case one of the small antenna modules that came with the the NEO6M module. The inside of the project case (scavenged) had been sprayed with a conductive paint (I'm sure for EMC reasons), and I doubted an antenna mounted inside the resulting Faraday cage would have worked very well.
Below is the schematic.
Notes:1. There are two jumpers on the board, JP1 and JP2. For normal operation, these should be installed on header J1 per the schematic.
- JP1 connects the NEO6M's serial output to the Nano's serial input.
- JP2 connects the board's +5VDC to the NEO6M
- New Receiver's GND to J1.1.
- New Receiver's TXD (serial data out, 3.3V level) to J1.3.
- New Receiver's VCC (+5VDC) to J1.5.
There are two timing loops in the software.
The first loop is a 20 msec loop, and its primary task is switch debouncing and performing the resulting switch actions. If a parameter (e.g. 12/24 hour display formatting, GPS baud rate, etc.) is changed during this 20 msec loop, the Nano's EEPROM is updated with the changed value. These EEPROM values are used for system initialization upon power-up.
The second loop is a 1 second loop, and its tasks are:
- Receive the NMEA-0183 message stream, strip off the time field, and write the time to the front panel display.
- If displaying a menu item in lieu of time, update the menu's display with any changes to a menu's selection that occurred during the 20 msec loop.
- Blink (if enabled) the display's "colon" between the hour and minutes fields.
- Read ambient light level via the Nano's analog input A0.
Note that this code only receives data from the GPS receiver, it does not transmit data back to it. Therefore, to change any of the receiver's parameters, such as its baud rate (from its default of 9600 baud to, say, 38,400 baud), this change must be done using an external CH340 USB-to-Serial adapter and external software such as u-block's u-center (a very useful tool to verify if the receiver is working, if you are unsure of your Arduino code).
There are 9 possible menu items that can be displayed via the 4-digit front panel display. These menu items can be stepped through using the momentary-contact toggle switch, which I call the "MENU" switch. Within each menu item there are a number of values that can be selected between using the normally-open pushbutton, which I call the "NEXT" switch. The MENU items, and the range of possible values for each MENU item are shown, below:
- Menu Item 1:
- Displays current Time.
- The NEXT button does not function for this menu item.
- Menu Item 2:
- Allows setting of the local time zone's offset, in hours, from GMT. Note that the offset is per Standard Time, not Daylight Savings Time. E.g. it would be -8 for California, irrespective of the month.
- The NEXT button steps from 0 to -23.
- Menu Item 3:
- Allows setting of the "Brightness" value of the 4-digit display.
- The NEXT button steps through the 4 possible brightness values: 0 to 3.
- Menu Item 4:
- Selects displaying time in a 12 or a 24 hour format. "12" will be displayed if the 12-hour format is selected, and "24" will be displayed if the 24-hour format is selected.
- The NEXT button toggles between the 12 and 24 hour selections.
- Menu Item 5:
- Determines if the colon dividing the 2-digit hours field from the 2-digit minutes field will blink (1 second ON followed by 1 second OFF), or if it is ON continuously.
- The NEXT button toggles between the BLINK or ON states.
- Menu Item 6:
- Selects of one of five possible baud rates to use for the Nano's Software UART that communicates with the GPS Receiver.
- The NEXT button steps through the following baud rates:
- 9,600
- 19,200
- 38,400
- 57,600
- 115,200
- Menu Item 7:
- Displays the number of satellites tracked by the GPS receiver.
- The NEXT button does not function for this menu item.
- Menu Item 8:
- Enables the display brightness to change with ambient light levels, or keeps brightness at the level set in Menu item 3, irrespective of ambient light levels.
- The NEXT button toggles between display brightness tracking ambient light levels, or remaining fixed.
- Menu Item 9:
- Displays the Nano's ADC input value from the Ambient Light detector. Values range from 0 (dark) to 1023 (max light).
- The NEXT button does not function for this menu item.
The MENU switch steps through the items from 1 to 9, and then will cycle back to 1.
Similarly, the NEXT button steps through a menu item's possible values from first to last, and then cycles back to the first.
Note that my GPS Clock Arduino code can be found here, on GitHub: Arduino Clock Code.
Mechanical:
I used a small plastic box within which I mounted the electronics and the display. I'd purchased this box (which apparently had been a shipping product) sometime in the dim-past, because I thought I might someday be able to its case for a project.
For my new application of this case, I constructed both the front panel and the back panel from blank double-sided PCB stock.
The front panel simply has the time display and a small photoresistor, the latter is used to detect a room's ambient light level and with it, time display's brightness can be adjusted accordingly (i.e. max display brightness for a bright room, to minimum display brightness for a dark room).
The rear panel (see image below) has three switches, an SMA connector for the GPS antenna, and a hole to allow a mini-USB connector to be attached to the Nano within the box.
The three switches on the back panel are:
Leftmost switch: 3-position single pole toggle. This selects between the following items:
- UP: Display time as Daylight Savings time (add an hour to Standard Time).
- MID: Display time as Standard Time (no Daylight Savings Offset).
- DOWN: Display GMT time with 24-hour format.
This board, in turn, is mounted onto another piece of raw double-sided PCB stock (allowing me to attach it to the case's four mounting holes).
An overhead view of the final build:
Note that if using this type of Gel film, there is a protective layer and each side of the Gel film that will need to be peeled off.
Software Generation:
My wife suggested that I use ChatGPT to create the Arduino's code, so I thought I'd try it -- I was wondering what the experience would be like.
I created a free account on the ChatGPT website and decided I would start by prompting ChatGPT to create code that would read and debounce two buttons (my MENU and NEXT switches). Without any forehand knowledge on how best to use ChatGPT to write code, I plunged right in.
I quickly discovered that using the "Enter" key, in an attempt to format my request, immediately sends whatever I'd typed to ChatGPT, as you can see in the first line below. My next attempt you can see right below it -- a long paragraph written "stream-of-consciousness" fashion. But it worked!
I continued in the same vein, incrementally adding features and then testing them. I was amazed at how quickly I was generating code with ChatGPT as a partner (although perhaps I should rephrase that as being amazed at how quickly ChatGPT was generating code with me as a partner). I figured I'd have the complete sketch finished by the afternoon.
For example, when stepping through the menus, I usually defined the first two characters to be a shorthand ID for that menu item, e.g. 'br' would display for the "Brightness" menu. And I told ChatGPT which display segments of the first two seven-segment displays should be illuminated (e.g. segments E, F G, etc.).
The resultant code looked great, the characters were properly displayed on the 4-digit seven-segment display, and I continued on my way, working on a different part of the code.
Later I went back to test on the Nano some function that required I step through the menus, and I noticed that for the menus, where I had defined the first two digits to contain letters, were instead displaying gobbledygook instead of the letters.
Somewhere, during my other requests to add features to the code and unbeknownst to me, ChatGPT decided to replace the working "letters-defined-with-segments" code with Hex values that, to it, represented the same segments.
Except these Hex values did not represent those segments, as I show, below:
Aha! I looked at my code again -- ChatGPT had declared a variable within a Case statement (it actually had done this twice), as I show below.
I moved the declaration to the top of the sketch to make it a global variable, not local, and the problem went away.
This isn't a problem I would have had if I'd written the code, myself. I usually define most of my variables as globals and place them at the top of my code, and whenever I do define local variables, they are at the start of a function call (i.e. subroutine), rather than within the code, itself.
And because it's something I wouldn't do, I didn't realize that it shouldn't be done. Thank goodness for Google.
Anyway...by this time I decided that I was spending too much time fixing ChatGPT errors and I'd be better off completing the code myself. And so I did.
In summary...
I'd estimate that the coding effort was 90% ChatGPT and 10% me, and it took about a full day to create my first version of the code (Revision 1). I'm sure it would have taken me longer, if I'd written all of the code, myself.
In my opinion, ChatGPT is an incredibly useful aid for creating code -- I avoided all the usual typos I would normally generate (and have to find) if I was typing the code in.
But ChatGPT can create errors -- especially frustrating is its rewriting of already working code into buggy code.
Also -- there's a certain "I know it in the core of my being" sense that I get from writing my own code. If something isn't working properly, I know right where to start looking for the cause, because I'd thought about, and written, the code.
With ChatGPT's code I don't have the same sense of familiarity. Which means that coding errors, if they exist, could be more difficult to uncover.
Other Issues with the Clock:
Incorrect Time (Infrequent):
With my first version of code, I noticed that sometimes (very infrequently), there'd be an error in the displayed, for example, time appearing as 5:22 when it was actually a different time of day.
The GPS receiver's baud rate was set to 9600 baud. I hypothesized that NMEA 0183 data might be being dropped (corrupted) by the NanoVNA's software serial port because other software tasks might have had temporary priority in lieu of serial port reception and decoding.
So I decided to bump up the baud rate from 9600 to 38,400 baud and see if the errors still occurred.
That seems to have fixed the problem.
Only 4 levels of Display Brightness:
Documentation suggests (or states?) that the TM1637 displays have 8 steps of brightness (0 to 0x7). The displays that I purchased only seem to have 4 steps (0 to 0x3), and brightness seems to be unchanged from that of 0x3 if the brightness field is stepped from 0x3 to 0x7.
Standard Caveat:
As always, I might have made a mistake in my equations, assumptions, drawings, or interpretations. If you see anything you believe to be in error or if anything is confusing, please feel free to contact me or comment below.
And so I should add -- this information is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
No comments:
Post a Comment