Word Clock with minute resolution of time in words and linear display of seconds

I have just completed a Arduino NANO word clock with minute resolution of time in words and linear display of seconds.

Word Clock Page


A video showing the various modes is available here Word Clock on YouTube

There are also modes for digital clock, analogue clock, temperature & humidity, & also three games, Game of Life, Simon & Tetris.

The clock can be stand alone or run as a slave off a Master Clock if required.

In stand alone mode the clock runs off it's built in temperature compensated real time clock with an accuracy of ±2ppm from 0°C to +40°C

When running as a slave off a Master Clock time is synchronised on every 30seconds past the minute.

Summer/Winter time is also automatically adjusted by one of my Pragotron Master Clocks on the BSTin (D7 Nano) pin.

A manual switch can be added instead if quick summer/winter time change is required.

There is an option for PIR control so the clock automatically turns off when no one is in the room.

The Clock measures 500mm x 500mm weighs 12lb (5.5Kg) and is designed to be wall mounted.

There are touch pads in each corner to setup and control the clock.

A mini USB socket provides brightness information over the serial port and also allows software programming insitue.

This clock is based on the original Word Clock by Wouter Devinck.

No custom PCBs are used in this version of the clock just cheap easy to come by prebuilt modules a couple of small Veroboards.

Construction time around 40 hours.

That looks very nice. I've been thinking about building a word clock to add to my collection of NTP clocks. I already have a Nixie, A VFD, a large LED and an LCD all using more or less the same software base with a special module for handling the display variants.
I was interested to see how you made the screen with a custom designed sticker on glass. I see that some commercial ones are laser cut steel. I'd be happy if I found someone selling just the screen (for a reasonable price) so I could do the rest.
To make the clocks compensate for DST, you can use the time zone library.

Thanks 6v6gt. I have always wanted a Nixie clock there are some amazing designs out there. Any pics of your clocks?

You can probably get laser cutting online. You would need to change the font or bits will fall out!

Elegant!

6v6gt:
I'd be happy if I found someone selling just the screen (for a reasonable price) so I could do the rest.

I've built a word clock before, and if I ever build another one, I'll use ws2812b strips.
Not only will that make the wiring and circuitry much simpler but it also gives you individual led control including color.
But to make it really easy, the spacing of the letters needs to match the spacing on the ws2812b strips.
Which is typically 30/60/144 leds per meter. which 30 or 60 being the two choices depending on the size desired.

To make the clocks compensate for DST, you can use the time zone library.

Even better is to use an esp8266 which can do a query over the network.
You can get the current local date/time and timezone (if you want/need it) for your location from your IP address.
There are many ways to do it, for example: IP Address - Get users location and timezone details - Timezone API

A small esp8266 module would be all you need and you could configure it from a web browser.

--- bill

Hi bperrybap.
I have had a quick measure up and the 30 LED per meter strip would give you a clock around my size 500mm x 500mm. You would need approx an 8m length to get the 256 LEDs.

How many LEDs can you control at once? There would be around 768 LEDs as each LED contains a Red, Green and Blue LED.

Is there a white only version available as I am not a fan of multicoloured word clocks?

I presume if you wanted a huge 1000mm x 1000mm clock you could switch off every other LED and still use a 30 LED per meter strip?

Building the LED matrixes took the biggest chunk of time constructing my clock. Of the approx 40 hour build time I spent 13 1/2 hour constructing the Matrixes. Using LED strips would knock this down to an hour.

The leds are intelligent. Each LED is addressable through a serial series bus.
You tell it the intensity for each color and it maintains it until you change it.
Power can be huge if you turn them all on at maximum brightness.
I think there is a white only, there is also a version that has RGB + white.

I spent too many hours on building the matrix and the wiring for it and mine was a much smaller matrix than yours. I ended up using a HT16K33 16x8 controller as my matrix was only 8x13 and it was much easier than dealing with two 8x8 matrix chips.

--- bill

Hello,

Good Word. It's my new project. Thanks for all descriptions.

Just a followup comment about if you are thinking about using: https://timezoneapi.io
While I had them add a GNU/POSIX TZ string to the available information, to work with gnu time libraries
and it is very nice to have full auto timezone detection,
at this point in time my recommendation is don't use the site or at least don't depend on it.
The main reason is that they are now charging a minimum of $39/month - there is no free service anymore.
The second reason is that my guess is that these guys will go broke and the service will not be around in the future so I wouldn't want to depend on the service.
(I had a project that used it and now I can't get the daily sync information)

They change things because some people were abusing the free service by making way too many requests.
When timezonapi went to the no more free service, they changed the API to the server and broke 100% of the devices using it.
They should be looking at an API model like what weatherunderground uses which can offer both free and paid services.
They way have done things, they have to break things again in the future.

IMO, that isn't worth it given the cost and my perceived risk of changes to the service including going away.
If you are using a full time library like what comes with ESP8266,
you can use NTP and it handles timezones by being given a GNU/POSIX timezone string.

My project now gets a timezone string from the user and uses well known NTP servers when the WiFi information is configured.
While not as convenient as the full auto detect capability, this methodology is unlikely to break in the foreseen future.

--- bill

bperrybap:
. . .
My project now gets a timezone string from the user and uses well known NTP servers when the WiFi information is configured.
While not as convenient as the full auto detect capability, this methodology is unlikely to break in the foreseen future.
. . .

Can you post a link to a project which uses this "timezone string" method you have described?

I use a hacked version of Jchristensen's time zone library for my (numerous) NTP clocks, modified to allow dynamic updating of the time zones. The user either selects a timezone from a pull down menu or can enter the rules for his/her time zone.
The only published example is here, an NTP speaking clock, Arduino ESP8266 Speaking Clock - Exhibition / Gallery - Arduino Forum and page 13 of the document describes the user interface for setting the time zone.

I really haven't seen any projects that have examples that are using NTP, timezones and timezone strings correctly.
(I created an example that is attached)

I think much of the problem is that most people seem to be unaware of how unix solved how to handle time, timezone offsets and DST offsets decades ago as they are often trying to apply some other mindset of how to handle time and time zone offsets and end up doing things incorrectly or breaking existing unix/POSIX time APIs that have been in existence e and working for 30-40 years.
This is particularly true in the Arduino world.

While some libraries do use unix style time_t epoch values, they are not really using the time_t values correctly when managing timezones and timezone offsets.

What you really want is to use a POSIX compliant time library that has a complete implementation that includes timezone support with support for a POSIX/GNU timezone string: TZ Variable (The GNU C Library)

The latest AVR libC library does have many of the POSIX time calls and has some calls to support timezone offsets, but as I recall, the epoch that they chose to use is different than that standard unix epoch. This can create issues depending on what the timestamp needs may be.
https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html

IMO, I wouldn't use an AVR for something like this. I'd use the ESP.

The esp8266 library code comes with full NTP support and a POSIX compliant time library including support for GNU timezone strings. It is very complete.
There is no need to add or use any 3rd party library to handle time or timezone offsets including DST changes.
Unfortunately, there is not an example provided that shows how to use them. Also there are some serious bugs in the timezone offset code. I have reported it:Serious issues with timezone support in time functions · Issue #4637 · esp8266/Arduino · GitHub
This is another great example of how timezone offsets are not being handle correctly and the way they tried to do things, not only was unnecessary but also breaks many other things including other code that already properly handled timezone offsets.
I'm probably going to be the guy that provides the patches to fix them as I already know what the fixes are and they are not complicated.
I'll also be providing some new examples that will show how to properly use the time libraries with/without NTP and with/without and RTC.
I haven't gotten around to it yet.
Unfortunately, to fix the time zone offset code work correctly breaks existing code that is using the way the code currently works.
There is no easy solution for this that fixes the issue and preserves existing code and that is likely to be the big issue with respect to actually fixing this.
i.e. the ESP guys may not want to actually fix it do to it breaking existing code.

That said, there is a really easy way to avoid the broken timezone offset code.
There is no need to use that broken attempt at timezone offsets anyway, so not using it is the simple solution.
If you simply never use the timezone offset in configtime() by setting the offsets to 0 and use a proper timezone TZ string things will "just work" as they are supposed to.

Here is the magic to use NTP with a timezone string on the ESP8266.
The key is to configure the time code to use NTP, but DO NOT use the timezone offset support.
That offset code is totally broken.
You then set a timezone TZ string to be a gnu timezone string which indicates the timezone, offset, and when DST changes are. This is used by the gnu time code when doing local time conversions so all the "ctime" calls to things like ctime(), localtime(), asctime() etc... will work properly including with local time zone offsets and DST time changes.
i.e. all the time calls will "just work" as they should.

All that boils down to a few includes and a few lines of code.
includes:

#include <time.h>                       // time() ctime()
#include <sys/time.h>                   // struct timeval
#include <coredecls.h>                  // for optional settimeofday_cb()

Code you put in setup()

  settimeofday_cb(time_is_set_callback_function);  // optional:  callback function to call whenever time is set

configTime(0, 0, "pool.ntp.org");
  setenv("TZ", "CST+6CDT,M3.2.0/2,M11.1.0/2", 1);
  tzset();

I created a simple working example that is attached.
In a real project, I would recommend using the wifi manager access point to get the WiFi ssid & password as well as adding extra parameters to get the ntp site and TZ string.

--- bill

NTP-Clock-MinExample.ino (3.17 KB)

bperrybap:
I really haven't seen any projects that have examples that are using NTP, timezones and timezone strings correctly.
(I created an example that is attached)

. . .

Many thanks for that. I've played with it - it appears to work perfectly (at least for my time zone) - and I stripped it down to a minimum while understanding how it worked (attached) and it is really very simple.

It is clear that the concept of the configTime() function is wrong because the system time should be in UTC Unix Time (epoch1970) and the conversion from the time obtained from the NTP server in UTC (epoch1900) should be simply applying a constant. There should be no attempt to apply any local time conversions at that point and offering parameters there which could conflict with any TZ setting is a mess. Only at the point of display should any system time --> local time conversion be attempted.

Good would be to have a simple format library available with the better known time zones in posix format with a text description to make it easier to build a time zone selection menu.

I haven't looked at the underlying code here for fetching the NTP time but I spent some time developing a non blocking NTP fetch, so it was compatible with an audio application which could not tolerate any blocking (speaking clock). I hope this one also does not block while waiting for a response to its NTP request.

I also had to do something in that application to achieve better than one second accuracy. Since the NTP timestamp can come at any time during a second, and the system time granularity is one second, I had to develop a method of scheduling a system time update for the next full second.

What I don't see here is how to get the time from another source (say an RTC) for cases where the internet is not available.

Anyway, once again thanks and good luck with your proposed activity to tidy up the ESP8266 time functions.

/*
 * Example that shows how to use NTP with a TZ timezone string.
 *
 * Recommended use:
 * Bring up Serial monitor at 115200 *before* uploading
 * Upload code and observe results
 *
 * Code is realeased to the public domain
 * Orig: 2018-10-28  Bill Perry
 * Mod : 2018-10-29 6v6gt - reduce to minimum
 */

#include <ESP8266WiFi.h>
#include <time.h>                       // time() ctime()



////////////////////////////////////////////////////////
//
// WiFi setup
//

#define SSID            "xxxxxxx"
#define SSIDPWD         "xxxxxxx"

////////////////////////////////////////////////////////


#define PTM(w) \
 Serial.print(":" #w "="); \
 Serial.print(tm->tm_##w);

void printTm (const char* what, const tm* tm)
{
 Serial.print(what);
 PTM(isdst); PTM(yday); PTM(wday);
 PTM(year);  PTM(mon);  PTM(mday);
 PTM(hour);  PTM(min);  PTM(sec);
}



void setup() {
 Serial.begin(115200);
 Serial.println();

 // DO NOT attempt to use the timezone offsets. The code is broken.
 // if used, then localtime() and gmtime() won't work correctly.
 // set them both to zero and get real timezone & DST support by using TZ string
 // enable NTP by setting NTP server
 configTime(0, 0, "pool.ntp.org"); // up to 3 ntp servers can be specified

 // set up TZ string to use a POSIX/gnu TZ string for local timezone
 // TZ string information: https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html

 // setenv("TZ", "CST+6CDT,M3.2.0/2,M11.1.0/2", 1);
 setenv("TZ", "CET-1CEST,M3.5.0/2,M10.5.0/3", 1);  // Timezone setting CET

 tzset(); // save the TZ variable

 // turn on WIFI using SSID/SSIDPWD
 WiFi.mode(WIFI_STA);
 WiFi.begin(SSID, SSIDPWD);
 // don't wait, observe time changing when ntp timestamp is received
}


void loop()
{
 time_t tnow = time(nullptr);
 if ( tnow == 0 ) Serial.println( "time not set" ) ;

 printf("         ctime: %s", ctime(&tnow)); // print formated local time
 printf(" local asctime: %s", asctime(localtime(&tnow))); // print formated local time
 printf("gmtime asctime: %s", asctime(gmtime(&tnow))); // print formated gm time

 // print gmtime and localtime tm members
 printTm("      gmtime", gmtime(&tnow));
 Serial.println();
 printTm("   localtime", localtime(&tnow));

 Serial.println();
 Serial.println();
 delay( 100 ) ;
}

Hy,

I have one question :

Actually, I build Wordclock. and I begin to connect leds on Max7219. When we connect Led on Arduino, it's necessary to add resistor in serie with Led to extend life of led. Is it not necessary with max7219 or you consider it too complex to wire them on so little space?

Thanks.

Emmanuel

Hi the MAX7219 does not require a series resistor as it controls the current via the Rset resistor.

See details on my page here LED current

There is a table on my site showing the values for this resistor and the current I seg it produces.

Hope this helps.

6v6gt:
What I don't see here is how to get the time from another source (say an RTC) for cases where the internet is not available.

I suppose you could have the RTC keep UTC, and then do the math on that.

Supposing that you are using an RTC which keeps track of time in "human" form (seconds, minutes, hours, day of week, day of month, month, last two figures of year), converting between Unix time and "human" time is not as much trouble as one might think.

Seconds, minutes, and hours are left as an exercise for the reader.
As for days, well, there are 86400 seconds in 1 day, and so you just divide the "raw" count of seconds by 86400 to get a count of days. That count of days uses 1970-01-01 as its "zero" date. From that count, subtract 10957. That is because the functions I am about to give you use 2000-01-01 as their "zero" date, and from 1970-01-01 to 2000-01-01 it is 10957 days. Please read the comments within the functions, so as to be aware of input and output syntax.

uint16_t ymdToDays(uint8_t y, uint8_t m, uint8_t d) {
  // Converts date (year, month, day) to day number.
  // Input the year as a number from 0 to 99. (0 means AD 2000)
 
  // some basic sanity checks
  if (y>99) return 0xFFFF;
  if ((m<1)||(m>12)) return 0xFFFF;
  if ((d<1)||(d>31)) return 0xFFFF;
 
  y+=4; // so that the next line can't make it go negative
  if (m<=2) {y--; m+=12;} // treat Jan. and Feb. as part of preceding year
 
  uint16_t n=365*y; // whole years (not counting leap days)
  n+=(y>>2); // add in the leap days
  n+=(31*(uint16_t)m); // take care of months (assuming 31 days per month)
 
  // the next few lines compensate for short months
  if     (m>11) n-=4;
  else if (m>9) n-=3;
  else if (m>6) n-=2;
  else if (m>4) n-=1;
 
  n+=d; // take care of the day of the month
 
  return (n-1495); // make 2000-01-01(Sat) be day 0
}


uint32_t daysToYmd(uint16_t n) {
  // Converts day number to date.
  // Valid day numbers are 0 through 36524.
  //     0 means 2000-01-01(Sat)
  // 36524 means 2099-12-31(Thu)
  if (n>36524) return 0xFFFFFFFF;
 
  // Here we go!
  n+=1401; // move starting point to 1996-03-01
  uint8_t y=(n/1461)*4; // take care of 4-year intervals
  n%=1461; // max 3 years 365 days (not 4 years!) remaining
  if (n>=730) {n-=730; y+=2;} // 2 years
  if (n>=365) {n-=365; y++;}  // 1 year
  uint8_t m=3; // get started on the month
  // Note repeating pattern of month lengths:
  // 31 30 31 30 31  31 30 31 30 31  31 <30
  // Mr Ap My Je Jl  Au Se Oc No De  Ja Fe
  if (n>=306) {n-=306; y++; m=1;} // for Jan. and Feb.
  else if (n>=153) {n-=153; m=8;} // for Aug. thru Dec.
  if (n>=122) {n-=122; m+=4;} // here, 122 days mean 4 months
  else if (n>=61) {n-=61; m+=2;} // 61 days mean 2 months
  if (n>=31) {n-=31; m++;} // 31 days means exactly 1 month
  uint8_t d=n+1; // get the day of the month
  y-=4; // make y be 0 for AD 2000
 
  // Now we have the correct year, month, and day of the month.
  // (Note: y is 0 for AD 2000, ... 99 for AD 2099)
 
  // The following output is a "placeholder" for testing purposes.
  // It should probably be replaced with something more useful.
  return (((2000+y)*10000L)+(100L*m)+d);
}

Now you can convert between "human" UTC and Unix time without a problem. At least, for UTC dates from 2000-01-01 to 2099-12-31, there will be no problem.