This project took more time and money than I’d planned. It was meant to be a quick one that I could get done in a few weeks, without a lot of complex code or new things to learn.
The idea is a just to make a clock to display UNIX time. For those that don’t know, this is simply a count of the number of seconds since 00:00 on 1st January 1970. As I write this post, we’re on 1383053948. Lots of computer systems use this system to internally store time. The time currently needs ten digits to display, and that will do until Sat, 20 Nov 2286 17:46:39, which should be long enough.
So, I was just going to do a clock that had ten seven-segment displays and counted up in seconds. I decided to go with:
- An Arduino Pro Mini board for the controller
- A USB micro connector + breakout board for power
- Two buttons to set the date/time
- An RTC chip to store the time
- A display board of my own design
I thought it would just be this (simple, right?):
Every one of these things except the buttons would cause delays and hassle. Let’s see why.
The normal way to drive multiple seven-segment displays to multiplex them; that is to only turn on one display at a time, and rely on our rubbish, slow, biological eyes to make it seem as if all of them are on at once.
This approach has the benefit that you only need one seven-segment driver and as many power transistors as there are displays. Also, as only one display is on at a time, the power consumption is factor of N lower than keeping all the displays on, where N is the number of displays.
The disadvantage is that something (normally your microcontroller) has to keep the displays switching over in concert with the data. If that task gets interrupted by other processing, your display might start doing weird things.
Given these trade-offs, the obvious choice for me was to multiplex the displays. The microcontroller would not be under heavy processing load, the power consumption would be reduced, and I would only need a single 7-segment driver IC and 10 transistors.
Instead, I chose a different approach. I wanted to design a generic seven-segment display (1″ high), so that several of them could be chained together to form a large multi-digit display. I designed a board that was the width and height as the display, so they can just be butted up against one another and soldered together.
Each board has a TLC5916 8-channel constant-current LED driver, enough for the seven segments and the decimal point. The board is designed for common-anode displays. The decimal point in 1″ displays typically only has one LED (the segments have two), but I decided to cheerfully ignore this, especially as for this application there wouldn’t be any decimal points on.
The TLC5916 is essentially an 8-bit shift register and 8-bit latch, with a constant current driver attached to each latch output. The board I designed has:
- A data input (either from a microcontroller or a previous board)
- A data output (from the TLC5916 for driving another board)
- Clock, enable and latch lines that are passed straight through the board, so each TLC5916 shares the same line.
- 5V power for the TLC5916 and the display.
Here are the boards, fresh from OSHPark:
This was my first mistake: the boards are the wrong size. The footprint I used for the seven-segment display was too small. So I had to get them made again. But the circuit itself was fine, so these boards will find a home in a future project.
Lesson 1: Check your boards carefully. (I should already have learnt this lesson)
After designing the boards, I decided that the best colour for the display would be blue. A UNIX clock should be in blue. It’s a gut instinct. So I went searching for 1″ high blue seven-segment displays and found some in the USA.
There were two problems with this.
Firstly, the pin spacing was slightly different on these displays, which meant I had to force them into the boards with pliers, pray I didn’t break them or the boards.
Secondly, the displays run at an average LED voltage of 6V. The boards only have a 5V supply for the common anode, shared with the TLC5916.
So, I had to modify each board to have both 7 Vand 5V supplies by:
- Making the anode supply 7V.
- Cutting the 5V track to the TLC5916.
- Soldering a 5V regulator upside-down onto the decoupling capacitor for the TLC5916.
- Soldering hook-up wire from the regulator input to the common anode.
I then bought a cheap DC-DC converter and used it to produce a 7V anode voltage from my 5V supply.
Lesson 2: Specify all components before committing to board fabrication.
Lesson 3: Where components might potentially have different voltages, consider designing for contingency and including jumpers/switches etc.
I think that this type of project lends itself to see-through casings, so you can look at my shoddy soldering and gluing. So I went with a clear perspex design.
I designed a box in Inkscape, starting with a template from http://boxmaker.rahulbotics.com/, and modifying to suit. I added cutouts for power and time-setting buttons, as well as for each display. I also rounded off the box corners to make the face profile look a little nicer.
The enclosure design was simple enough, but the laser cutting did not go well, and I ended up staying at the hackspace into the early hours of the morning with a scalpel removing bits that didn’t quite cut through.
Lesson 4: Check laser cutter alignment and settings at the extremes of the cutting area.
Timing and Clock
The clock would probably be unplugged and plugged in again regularly. It’s silly to have to set the time every time this happens, so I got a Chronodot RTC board, which is really just a breakout for a DS3231 RTC chip and backup battery.
This chip stores the date and time (plus alarm settings and control) in 12 8-bit registers, read/writeable over i²c.
I’m quietly building myself an embedded software library, with generic components where possible, feeding into architecture-specifics where necessary. To talk to the DS3231, I therefore needed to write myself an i²c library for the AVR.
All the libraries that already exist for i²c on an AVR use “busy loops” to wait for reception or transmission of a byte. This is absolutely valid software design. The i²c bus is pretty fast, and most applications on an AVR microcontroller will not miss the processor cycles used up in these loops. It also makes control flow simple and code size small.
However, all my other modules for AVR have been interrupt driven, so I rolled by own interrupt-driven state machines for i²c comms. This was entirely unnecessary given the excellent libraries that already exist out there. But then the point of making/hacking is (to one extent or another) doing it yourself. But it did take ages to get all the bugs out. It would have been far quicker if I’d have purchased a simple logic analyser.
Lesson 5: Debugging hardware is less expensive than debugging time.
Having got the i²c comms working, I then implemented a quick pre-build make script to auto-generate a header file containing current time in UNIX format as a define.
By including this header file, I can compare the time from the DS3231 to the compile time. The startup logic is simple:
Setting the Time
Two tactile switches are used to allow the user to set a new time. Ideally the clock would never need changing, since the DS3231 is pretty accurate. But there are two instance where it will:
- During development, when I might write all sorts of stuff to the RTC.
- When leap-seconds are added to the time. If you don’t know about leap-seconds, go read about them. Horology, and the various systems we use to measure time, is pretty fascinating.
One button selects a digit, and one buttons increments that selected digit.
To read the buttons, I wrote a simple library that:
- Debounces button inputs with a variable time.
- Will call a per-button callback function on button down, button up, and at a varying rate while held down.
The library is entirely architecture independent, and relies on regular updates from a button reading task or interrupt. This was the only bit of the project that just worked.
Lesson 6: Simple software libraries are good for the soul.
All my projects have an interrupt driven state machine at their core, and this was no exception. The logic for this one is very simple, and acts just to prevent the clock from updating while the buttons are being used:
Because I wanted this to be a quick and simple project, I didn’t bother designing a PCB. I thought it would be easier to just wire together pre-built boards. I was wrong.
There are four separate boards in the design (counting all the display boards as one), plus two buttons, plus the little DC-DC converter I had to buy. That’s a lot of point-to-point wiring. It looks ugly, takes up space and has many potential failure points.
Lesson 7: Sometimes rolling your own IS the right way to go.
It all worked out OK in the end. I had a few frustrating nights where I couldn’t figure out what was going wrong with my time conversions, which turned out to be a 16-bit truncation of a 32-bit number. I’d tested this particular library on my PC, where it worked fine. On the AVR, not so much.
Lesson 8: PC test rigs are great. Test driven development is great. But remember to account for differences between your 32/64-bit PC and your tiny 8-bit microcontroller.
Here are some photos of the finished article: