This is a clock designed to keep accurate time (independent of atomic or GPS), display local sunrise, sunset and solar noon, and also adjust itself for daylight savings time.
I wanted the clock to be easy to use and be flexible. The setting functions are menu-driven, you set each parameter one digit at a time (with live data validation) and you can abandon changes if you want. You can have 12 or 24-hour time. It uses a bright, legible VFD character display - you can choose even more readability with a 2x3 big-character mode.
Finally, VFDs are bright and readable, but sometimes you don't want them lighting up the room. So, you can set a schedule of when the display is bright, dim or off. You can turn the display on or off any time you want, as well.
I hope to go over key elements of the software and hardware design to help you in building a clock just like this one or to give you ideas for anything that needs menus, data validation, timekeeping and so on.
Step 1: Design Choices
As stated in the intro, I wanted the clock to be easy to use, accurate, simple but also flexible.
Easy to use:
- menu-driven setting options
- digit-by-digit setting (who wants to go up or down to set something like longitude?)
- data validation to keep the user from inputting impossible time, date, location etc...
- buttons match the way it is used, eg. you want to look at it while setting, so the left button is really the right button
- most common functions on their own button, eg. display on/off, big/small digits, etc...
- must keep time accurately without using radio time; DS3231 RTC is accurate to 2 ppm/year or approximately +/- 1 minute per year
- no buttons visible anywhere except the back
- allow adjustment of DST start and end
- allow 12/24 time
- support display brightness schedule
I suppose you can add to "Simple" that it uses the Arduino platform. There isn't actually an Arduino board inside, though you could use one; I used a Modern Device RBBB Arduino clone and a Wicked Device RBBB shield board. I chose the RBBB because it's cheap, flexible and sports a power jack, which I needed anyway and is a pain to do properly on protoboard. I chose the Wicked Device RBBB shield as it supports the RBBB and because it gave me sufficient protoboard space to mount a 2032 coin cell holder for the DS3231 backup power and have a header for the 14-pin cable to the VFD display.
Step 2: Hardware Choices
As mentioned before, I went with the Arduino platform - not so much for the Arduino-branded hardware but mostly because I've done a number of projects in it and because there's plenty of hardware flexibility. I use pre-made boards where it will save me time and grief, especially where custom circuitry is involved. Some good examples are wiring up the power jack (few have the right spacing for 1 mm pitch protoboards) and wiring the resonator (though I show how to do this on a breadboard in my project "Arduino-Powered Four Letter Word Generator").
One of the big reasons for going with the Wicked Device RBBB board was the 14-pin SO surface mount area. The DS3231 is a 16-pin SO part, but it turns out pins 8 & 9 are NC (not connected) so it was possible to shift the chip one leg to the right and mount it on the remaining 14 pads. Otherwise you have to buy and solder up a 16-pin SO to through-hole adapter, make your own PCB or buy something pre-made like a Chronodot. I needed some space to solder a 14-pin header for the VFD and the battery holder so the Wicked Device RBBB was the right choice. It was also the cheapest choice.
I chose the LCD-compatible VFD display because I wanted readability over all else. VFDs have some disadvantages, namely cost and power consumption. I got lucky and found an eBay seller with a lot of these at a good price. If I had to order directly at 1x quantity from Noritake I might reconsider. This project is wall-powered, so power consumption is not a problem. The VFD is a CU20029ECPB-W1J, if you're wondering.
Because we have a power-hungry VFD (~300ma @ 5v) I decided against a linear power regulator. I have tried it before with this VFD and it just gets too hot for a non-vented enclosure, even with a 7.5v power supply. A good 5v regulated switch-mode power adapter is cheap, reliable and keeps your project cool inside. The RBBB provides for a linear regulator but it is a simple matter of jumpering pins 1 & 3 where the regulator would go.
Finally, the case choice was a matter of finding one with a faceplate big enough to accommodate the VFD. It turned out that 1/8" plexiglass needed just a bit of sanding along the edges with coarse grit sandpaper to size it to fit the existing faceplate channel. The case is a Pro's Kit 203-115A. I got it from Alltronics.
Step 3: Software - Menu System
The first thing I developed with the menu and setting system. Creating the menus is easy - just increment or decrement an array index and then read the string out of flash using the PROGMEM directive. I suppose there were few enough strings you could just put it in RAM, but using FLASH when possible is a good habit.
The real work was the setting system. I wanted to be able to set each value character by character. Some of the numbers, like longitude and latitude are 5-6 digits (ignoring the decimal) - I did not want to use a press and change method, even with a jump increment on button hold. Speaking of buttons, this project uses Button.h, which I had initially thought handled debouncing but actually does not. A better choice would be Bounce.h. It was enough to toss in a small delay in the button polling loops, since the CPU itself isn't doing anything else - the RTC keeps time on its own.
So anyhow, the first problem of a by-character setting system is knowing where the cursor is allowed to go, This is handled by a two-dimension array which stores a 1 or 0 for each position on the VFD to indicate if the cursor is allowed there or not. If not, the cursor skips to the next available spot or stays on the last spot. I made liberal use of defined constants so I don't have to memorize which index value goes with with setting array.
The second problem of a by-character setting system is handling data validation. You would think it is as simple as re-assembling your digits back into the value and then doing a comparison against the max value, but to block the user from going to the next value you have to see what that value + 1 would do to the number as a whole. That's not bad for a couple of digits but it's a mess for 5-6. I found it was easier to have a two-dimensional array like the position array to hold the max allowable value per character. This allows you to check the same thing based on index every time the user tries to increment, and coupled with a check for the max value as a whole, this captures most bounds violations. It will still be possible for the user to sometimes set an illegal value. It was either that or a lot more code.
Along the same lines, I didn't care if the user tried to set something like February 30. You can add that sorts of bounds checking if you want. I'll admit I got "project fatigue" and left that sort of thing out.
Step 4: Software - Storing Values
A number of values in the clock need to be stored in EEPROM. Some, like the display schedule, are fine as bytes. Others, however, need a signed float. The easiest thing to do was store the float as an unsigned 16-bit word of two bytes. All you have to do is move the decimal point with a divide or multiply. I store the sign, where needed, in it's own byte.
I know there are AVR libraries to handle storing whatever to EEPROM. I think I am using less RAM doing it simply the way I do it.
Step 5: Software - Solar Calculations
This is where libraries are great. I used Timelord.h and formatted my data into the array it expected and get back all my solar and lunar data. Only one I had to do was solar noon, which I figured was halfway between sunrise and sunset. Maybe in some trigonometric sense it's incorrect, but it's close enough for me.
The bigger pain is DST conversion. It is not as simple as just adding or subtracting an hour, as you have to take into account what happens near midnight, and that depends on the month, whether or not it's a leap year, etc... Thankfully Timelord.h handles that too.
Lastly, the usual convention in the computer world for RTC (system) time is to store the time as UTC and then compute the time based on the timezone offset and DST conversion, if needed. I don't have a problem with that, but for non-technical people it might be a hinderance. I chose to store the the time in the RTC as standard local time. I think it's easier for most people to drop an hour if in DST when setting than think about what the time is in UTC. It would not be hard to change the clock to keep the time in UTC if that is what you wanted, as Timelord.h will handle that.
Step 6: Software - Talking to the DS3231
This one was easy. Just search the 'net for how to talk to a DS1370 - they're the same. I had to do a bit of digging on how to enable and set the SQW output pin. This pin is connected to an internal divider - you have a choice of four frequencies - 8192, 4096, 1024 and 1 Hz. I set this to 1 Hz and connect this to 13 to act like a button press to tell the CPU to update the display. It is important to note this is a 50% duty cycle signal, so you want to read the pin transitioning to low, not that it is low.
I've switched over to Bounce.h from Button.h. That's working well. Don't forget you not only have to set your button pins as inputs but then also set them HIGH. Otherwise the built-in pull-up resistors aren't activated, your pins will float and your buttons will act crazy. Button.h does this for you, Bounce.h does not.
Step 7: Software - Big Characters
I got the concept for doing the big numbers from another project online. It's clever - you create an array of your custom characters, including a leading space, and then you read them out by offsetting based on the number you want to display with a loop to iterate over the next four characters. You do this for both lines of the number you want. There you go, a 2x3 big number.
This VFD has the advantage of a tight spacing of the 5x8 character cells. The 2x3 characters look pretty solid. The disadvantage is the bottom line is wired as a cursor, so it's either all on or all off. Hence the funny look at the bottom of some characters where you'd expect them to be rounded off.
Very, very occasionally you will see the display 'glitch' as it re-writes. It may be possible to improve this using 8-bit mode. I don't think it warrants the use of 2x the pins here. For highly speed-critical apps, like a sound spectrum display, 8-bit would probably offer an improvement. Then again, the glitching might be a controller artifact you would never notice on an LCD as they are just not as fast display-wise as the VFD.
Step 8: Hardware - Component Mounting
The case I chose has a number of screw mount points inside the chassis. Unfortunately, none of them are really where I need them. I needed the VFD a certain distance back from the blue plexiglass and I needed the RBBB so the power jack would be flush with the back. The solution was to cut and bend a piece of sheet metal to act as an internal mounting surface. I tried my best to bend it without a real sheet metal brake. It came out sort of sloppy but you don't notice it at all with the cover on. You can buy a sheet metal brake relatively cheaply. It's probably a good investment, along with a little spot welder. Oh, add in a sheet metal cutter. Now you can make your own enclosures anytime.
As for the mounting hardware, it's expensive to buy in small quantities and cheap in large, like most hardware. I bought a grab bag online which had what I needed. Taking apart discarded consumer hardware is a good way to build up a collection of screws and standoffs.
Step 9: The Code
The code for the clock is at GitHub:
Version 1.3 used Button.h
Version 1.4 uses Bounce.h
Step 10: Concluision
Clocks are ubiquitous now. There's a clock on just about everything. Why do you need yet another clock?
Well, knowing the sunrise, sunset, noon and moon is helpful for a number of things. You can use it for determining when the fishing it best. You can figure out your Muslim prayer times. You can figure out other things for lunar-based calendars. If you pay attention to what solar noon looks like, you can get a sense of what time it is without a watch.
I am not sure the ATMEGA328 has enough RAM to do it, but it would sure be cool to calculate when a lunar or solar eclipse might be expected for your area.
I like the idea of my clock being accurate without the help of outside signals. One minute loss or gain per year is pretty accurate, at least for human activities.