Towards my GPS LED Light Clock

A few years ago, I made a CCFL light clock using an Arduino with a custom shield containing a transformer (to get a reliable 60Hz timebase) and a triac (for solid-state switching of the lamp). By having a simple 7-day alarm calendar (set at compile time), the clock seldom requires interaction except for the reading lamp function.

However, the design has two main problems: first, that the clock must be set at least twice a year due to DST (and the only way to set the clock is by plugging in a laptop, as there are no controls to set or display the time); second, that the triac subcircut sucks and generally behaves just a bit awkwardly, with the biggest problem being an occasional flash in the CCFL tube when a large load like the heat pump switches on. (the initial concept also included dimming an incandescent bulb with the triac, but that never worked reliably either). All in all this works reliably enough, but I've wanted to replace it with a better solution.

Problems and solutions

Problem 1: AC Switching. Solution: Switch LEDs instead. This makes simple PWM (as opposed to phase angle) dimming possible. I have a selection of white and colored 1W and 3W LEDs, which should get me in the neighborhood of the brightness of the 12W (?) CCFL I'm using.

Problem 2: Setting the time and conforming to DST rules. Solution: Use GPS and code future DST changes in the program flash. GPS reports (e.g., $GPZDA) include UTC time, and PPS output is a reliable timebase.

Timekeeping

The major new problem is that the clock now has to know about things like the difference between UTC time and local time, including how to look up a UTC time in the DST table and how to apply a time delta (in seconds or maybe minutes).

I've started on the code to do this. So far, I've invented a new time epoch (starting 00:00:00 UTC Jan 1 2000 but counting once per second like the Unix epoch)—a 32-bit "utime" allows counting 2³²-1 seconds (~136 years) into the epoch, well beyond the lifetime I can imagine for this device. I could have used the Unix epoch instead. In retrospect, using the Unix epoch but with unsigned time_t would give an epoch that extends to 2106, also beyond the lifetime I can imagine for the device.

I've created a time structure 'stm', which is like C 'struct tm' but with minimum-sized fields (e.g., stm_sec is a uint8_t); other fields that are not required (like tm_yday) are not in the structure at all.

A pair of functions, utime2stm and stm2utime, convert between the representations. The implementation of stm2utime is quite straightforward, while the implementation of utime2stm is less so. The weekday, hours, minutes, and seconds are easy; years, months and days are the tough part. The latter I implemented while studying Python's datetime module; it provides a function which uses the first day of year 1 as its epoch; because leap years are on a 400-year cycle, the year 2001 works just as well. So after accounting for the fact that I use year 2000 for the epoch, it's fairly easy to write the code for a micro. (Of course, it occurs to me after the fact that I never need to find the year, month, or mday from a utime, unless I add automatic handling of future holidays, in order to skip turning on the light on for days that I don't go to work—actually, this would be a nice feature!)

The most clever bit of stm2utime is how it determines the month for a given yday. It performs the calculation mon = ((yday + 50) >> 5) which a note in datetimemodule.c says is either the correct month or one month after the correct month. A simple comparison suffices to perform the correction. A different approach would be a search (binary or linear) through the table, but this is probably more efficient.

Having all these functions, I can read a UTC time from the GPS and convert it to seconds within the epoch. Every second, I increment the time. At at the top of a minute, I will add the timezone offset to the UTC time and convert it back into (local) time. If it's on a day that the alarm should go off, and the hour and minute match the alarm time, then turn on the light.

DST Adjustments

The next step is to get a list of all future DST adjustments. Well, I can't really do this, as future DST adjustments actually depend on the actions of lawmakers; the rules in the United States have changed twice in my lifetime, and occasionally there are localities that opt in to or out of DST altogether. I also contemplate including a single zone's DST rules, so if I move from one jurisdiction to another I'd also have to reprogram the clock. However, either of these kinds of events are considerably more rare than the twice-a-year DST changes. (the hard-coded alarm time and day-of-week rules make the clock of limited value as a travel alarm anyhow, so the fact that I can't take it with me to Denver and wake up at the right time isn't too big a problem)

The standard unix program 'zdump' knows how to determine all the DST changes and dumps information about when they occur. This can be transmuted into a program that prints a table of changes:

{UINT32_C(0), -21600}, // Sat Jan  1 00:00:00 2000 UTC = Fri Dec 31 18:00:00 1999 CST isdst=0
{UINT32_C(353318400), -18000}, // Sun Mar 13 08:00:00 2011 UTC = Sun Mar 13 03:00:00 2011 CDT isdst=1
{UINT32_C(373878000), -21600}, // Sun Nov  6 07:00:00 2011 UTC = Sun Nov  6 01:00:00 2011 CST isdst=0
…

Since time always progresses forward, it should be very simple to stay on the right table entry: whenever the next table entry's time is not in the future, advance to the next entry. At power-on only it might be necessary to step through multiple entries. If all the data is in seconds, then each entry is 8 bytes (a 16-bit type isn't big enough to store all world UTC offsets), and there are typically 2 entries per year, so 2kiB of flash is enough to store 128 years of rules, or from now to the end of the epoch. Plenty of space for that in flash on, but it's too much to store in RAM, meaning the code will be complicated by pgm_read_xxx calls.

So now I have the "algorithmic" parts of the software designed. I have the parts (arduino, gps, led, pwm led driver). Now all I need is the motivation to work on putting the parts together into a whole.

Files currently attached to this page:

stmtime.c6.2kB
z2a.c15.9kB



Entry first conceived on 22 June 2011, 20:19 UTC, last modified on 15 January 2012, 3:46 UTC
Website Copyright © 2004-2024 Jeff Epler