Thursday, January 17, 2008

A Simple Binary Clock Project

In about April of 2004 I started this binary clock project which was inspired by the Think Geek binary clock. I put the project on hold a number of times and finally finished it around October of 20032004. When I started the project the TG clock was only available with red LEDs, and while it was definitely pretty cool, there were a number of things about it that bothered me. Obviously it needed blue LEDs, which are all the rage for the modern appliances. It also displays the time in 6 binary registers, one each for the 6 decimal digits of a digital clock. While this arrangement generates some pretty gnarly patterns, and is probably why it was chosen, it seemed very un-geekish to me. Lastly, and most importantly, since one of the guys I work with already had a red TG binary clock on his desk, if I was to have one, it couldn't be the same design, and it would have to somehow out-geek that other clock.

Clearly I would have to create my own binary clock from scratch to meet my requirements. As the only 'crossover geek' in the office (programming and hobby-level digital electronics), I could meet the primary goal of out-geeking the existing clock with my own AVR microcontroller based design.

Since presentation is important as well I choose to use a dead harddrive as the case for the clock. This presented some
interesting challenges, as there isn't really much room inside a hard drive. I considered using surface mount parts as I have
with other projects, but eventually decided against it. I don't like making PCBs for SMT parts as I don't have a well refined technique for etching boards, the fine features associated with SMT parts don't come out well and sometimes cause frustration and SMT parts don't fit well on the perf board I thought would look suitably homemade. While I could have a PCB made for the surface mount parts, it could end up looking more professional than I wanted. I wanted this thing be reasonably good looking, but I also wanted it to look home-built.

Rather than use 6 binary registers for a 6 digit display, I choose to go with two 6 bit registers, one each for seconds and minutes, and one 5 bit register for hours (I prefer the 24 hour time format, so support for that was required). This loses some cool points when it comes to the fascinating patterns that the Think Geek clock generates, but I think it is easier to read, more elegant, and, importantly, different.

I briefly considered keeping the HD platters on the motor and mounting some SMT LEDs so I could create a radial matrix display, but the difficulty of working with the moving parts required for that design added a level of commitment that I wasn't prepared to deal with (it took about 6 months to finally get it done as it was!). I still think it's a cool idea, and I've seen a number of such 'propeller' or 'persistence of vision' as they are called now, clocks. Perhaps I'll do something like it in the future. Since I made this clock 'persistence of vision' displays have become pretty common (see MAKE magazine), so doing it now shouldn't be too difficult.

I decided to use the Atmel AT90S2313 for this project. It is a 20 pin, 8 bit, 2kb flash-based RISC controller with built-in
UART. It's small, and has plenty of I/O for this task, and I've got a pile of them that aren't doing anything but taking up space and taunting me for not using them. The code is written in C, and compiled with the AVRGCC compiler. Since I do not use C I'm sure the quality of the code is abysmal. I referred to the K&R book quite often while writing the code you see below. It's been so long since I wrote it that I really don't recall what the heck I was thinking when I invented it, and most of the coding sessions where late-night Git'er Done sessions, so it is highly likely that there is some pretty whacked out code in there. Normally I'd take a lot more care in designing it, but by the time I got to the software I just wanted to get the darn thing finished.

The 3 registers are arranged around the platter in two horizontal and one vertical row. The seconds register is laid out horizontally at the bottom of the platter with the LSB on the right, minutes is vertically oriented on the right, LSB at the bottom, and hours is parallel to seconds at the top. Since the hours register is one bit shorter, the right-most position is not drilled out. I had actually intended to leave the left-most position undrilled, but I'm a goober and soldered the LEDs in wrong. After the difficulty I had wiring them up to the right angle headers with magnet wire (a horrible choice of material, I should have waited and picked up some more suitable wire at the local electronics supply store, but I was overeager to get it running. That decision cost me many hours of tedious wiring and troubleshooting), I wasn't inclined to change them.



I built three small boards to go on the back of the HDD case, onto which the LEDs are mounted, one board for each register. The boards contain 5 or 6 LEDs, an 6 pin single row 90 degree header, and a darlington PNP transistor and base resistor. The header pins are for power, enable and 5 or 6 LED ground wires. The PNP is controlled by the enable line, which is connected to the base via the resistor. When on it provides current to the LEDs. The ground lines are switched on the main board by a row of NPN transistors to turn on or off each LED.

I didn't have the Taig CNC milling machine at the time, so I used a hand drill and some files to make the openings in the back of the disk drive. The aluminum used for the case is very soft and cuts beautifully, if the mill had been available I would have considered doing some more interesting work on the exposed metal areas. I also would have etched something onto the platters I used for the face (I also would have had neatly aligned holes for the LEDs instead of the slightly wonky holes it has now).



The cables from the three boards run to a concentration point, also on the back of the drive. This wires together all the LED
ground wires and power, and brings the three column enable lines into the cable which runs through the existing access hole in the back of the clock to connect to the main board. I liberally applied hot glue to keep everything in place.




On the main board is a row of NPN transistors and their resistors, 3 buttons for setting the time, and the microcontroller itself, along with a few supporting parts. The NPN's are the other side of the control of the current to the LEDs, they work with the PNP's on the LED boards to control the current to each individual LED. There are other ways to control banked arrays of LEDs that can save IO lines, but you don't save much with this small number of LEDs, and I didn't have any shortage of IO lines.

To provide a nice look, I was pretty set against showing the tops of the LEDs. While the lenses are water clear, they will
always look like LEDs, which, while certainty geeky, isn't very pretty. I located some 1/4 inch clear lexan rod that I found
would carry the light very nicely, like an optical fiber. I cut it into pieces about 3/8ths of an inch long and frosted both
ends (chuck the rod into the drill and apply fine sandpaper) to diffuse the light from the LEDs. By mounting the bits of rod with about an 1/8th inch sticking out above the polished drive platter, I got a very nice modern look. The frosted end of the rod glows bright blue, but the sides are clear, with very little light leakage. The effect is of an intense blue disk hovering over the mirrored surface.

I dislike the very abrupt on-off nature of the Think Geek binary clock LEDs, the sudden changes catch my attention when the clock is at the edge of my field of view. It was apparent that this would not be acceptable for something that would be sitting on my desk all day. I used an ugly hack in the software to adjust the pulse width of the signal sent to each LED that was changing states. Each transition has a 1/4 second linear ramp to full on or off, which makes it look much more serene, and eliminates the snap of the TG clock.

Unfortunately, I've set the scan rate such that under just the right conditions I can see the flicker of the LEDs when they
are ramping on or off, but its very minor. Someone with very 'fast' vision might be more irritated by it. I can see and am annoyed by CRTs set for a 60Hz refresh rate, and the flicker of fluorescent lights and movie screens often bothers
me but not most others, so perhaps I'm just picky.

Overall the clock turned out pretty nice, although it has lots of room for improvement in all aspects. The software could be
much more elegant and extensible, the hardware has a number of dumb design elements, and the fit and finish could use plenty of work. In particular the hardware needs a microprocessor supervisor chip and battery backup, and it should be using a crystal that lends itself to more accurate timekeeping. The 4MHz crystal I used does the job acceptably, but choosing constant values for calculating the time in the software is dodgy. It also drifts with the temperature more than it ought to, a more stable crystal would be nice.

If I do another version I will include a Real Time Clock chip to handle the timekeeping, including battery backup. I can then
use the microcontroller to run a speaker for alarms and timers (1 hour 'lunch timer' and 5 minute tea timers would be handy, as well as an 'its 5pm, go home' notifier). I'd also like to include USB support to allow the clock to draw power and accurate time sync data from the computer.

The code is under the GPL, and the hardware is too trivial to be anything but public domain. Feel free to use the software under the terms of the GPL, and do whatever you wish with the hardware designs.

You can see video of the clock. The frame rate and shutter speed make the LEDs look like they pulsate a bit in the video. In person the fade is smooth and seamless.

Here is the code for the project:
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
#include <avr/delay.h>

#define F_CPU 4000000 /* 4Mhz */
#define UART_BAUD_RATE 9600 /* 9600 baud */

#define COLUMN_DWELL 5000

#define BTN_SET_TIME _BV(4)
#define BTN_SET_HOURS _BV(3)
#define BTN_SET_MINUTES _BV(2)

#define UART_BAUD_SELECT (F_CPU/(UART_BAUD_RATE*16l)-1)

typedef
unsigned char u08;
typedef
char s08;

typedef
unsigned short u16;
typedef
short s16;

/* uart globals */

static volatile
u08 *uart_data_ptr;
static volatile
u08 uart_counter;
static volatile
u08 received_byte;

static volatile
u08 seconds;
static volatile
u08 minutes;
static volatile
u08 hours;
static volatile
u08 old_seconds;

static volatile
u08 old_minutes;
static volatile
u08 old_hours;
static volatile
u16 fade_step;

static volatile
u08 btn_inc_hours; // hour set button pressed, inc hour value

static volatile u08 btn_inc_minutes; // min set button pressed, inc min value

enum
{ SECONDS, MINUTES, HOURS };

void
uart_send_byte( u08 val ){
uart_counter = 1;

UDR = val;

for
(;;){
if
(uart_counter == 0)
break
;
}
}


SIGNAL(SIG_UART_TRANS)
/* signal handler for uart txd ready interrupt */

{

uart_data_ptr++;

if
(--uart_counter)
UDR = *uart_data_ptr;
}


SIGNAL(SIG_UART_RECV){
/* signal handler for receive complete interrupt */

received_byte = UDR;
}


void
inc_time(u08 time_type){

switch
(time_type){
case
SECONDS:
if
(++seconds > 59) seconds = 0;
break
;

case MINUTES:
if
(++minutes > 59) minutes = 0;
break
;

case HOURS:
if
(++hours > 23) hours = 0;
break
;
}
}



SIGNAL(SIG_OUTPUT_COMPARE1A){
// Don't update the time if the set time button is down
if ((~PIND & BTN_SET_TIME) == BTN_SET_TIME) return;

// Store the old values for cross-fading
old_seconds = seconds;
old_minutes = minutes;
old_hours = hours;

// 4992 is close to COLUMN_DWELL and allows for 13 integral steps
fade_step = 4992;

// overflow each into the next
inc_time(SECONDS);
if
(!seconds) inc_time(MINUTES);
if
(!minutes && !seconds) inc_time(HOURS);
}



void
uart_send(u08 *buf, u08 size){
/* send buffer &lt;buf&gt; to uart */


if
(!uart_counter) {
/* write first byte to data buffer */

uart_data_ptr = buf;
uart_counter = size;
UDR = *buf;
}
}



// init system
void do_init( void ){
// variable initialization
seconds = 1;
minutes = 2;
hours = 4;

// Turn all the LEDs off
DDRB = 0xff; // PortB output
PORTB = 0xff; // outputs off
DDRD = _BV(5); // bit 5 on
PORTD = 0xdf;

// COM port can be useful for debugging
// Enable serial transmit and receive at 9600/8/1/n
UCR = _BV(RXCIE) | _BV(TXCIE) | _BV(RXEN) | _BV(TXEN); // enable RxD/TxD and ints
UBRR = (u08)UART_BAUD_SELECT; // Set baud rate

// Enable Timer/Counter 1 with a 256 prescale
TIMSK = _BV(OCIE1A); // enable TCNT0 overflow
OCR1 = 15623; // 15603 ticks with a 256 prescale on 4Mhz clock makes 1 second
TCCR1A = 0; // disable the output pin (PB3)
TCCR1B = _BV( CTC1 ) | _BV( CS12 ); // clear on compare match, 256 prescale

}

void display( u08 out_type, u08 value ){

// all the values are masked by 0x3F
u08 port_value = value | 0xC0;

// turn off all cols
PORTB = 0xC0;
PORTD = _BV(5);

// turn on the correct column
switch (out_type){
case
SECONDS:
port_value &= ~0x80;
break
;

case MINUTES:
port_value &= ~0x40;
break
;

case
HOURS: // Note, this would be cleaner if used portd for all cols
PORTD &= ~_BV(5); // hours is displayed via a different port
break;
}


// Display the value
PORTB = port_value;
}


void
display_fade( u08 out_type, u08 old_value, u08 value ){

if
(fade_step>312) {
display( out_type, old_value );
_delay_loop_2( fade_step );
}

display( out_type, value );
_delay_loop_2( COLUMN_DWELL-fade_step );
}


int
main(void)
{


do_init(); // initialze stuff
sei(); // enable interrupts
for (;;) { // loop forever
// combined state of three buttons used to set time
switch (~PIND & (BTN_SET_TIME | BTN_SET_HOURS | BTN_SET_MINUTES)){

// just timeset down
case (BTN_SET_TIME):
btn_inc_minutes = 0;
btn_inc_hours = 0;
break
;

// timeset and minutes down
case (BTN_SET_TIME | BTN_SET_MINUTES):
// immediate response to button press and auto inc on hold
if ((btn_inc_minutes == 0)|(++btn_inc_minutes == 20)){
inc_time(MINUTES);
btn_inc_minutes = 1; // don't reset to zero for auto inc
seconds = 0;
}

break
;

// timeset and hours down
case (BTN_SET_TIME | BTN_SET_HOURS):
// immediate response to button press and auto inc on hold
if ((btn_inc_hours == 0)|(++btn_inc_hours == 20)){
inc_time(HOURS);
btn_inc_hours = 1; // don't reset to zero for auto inc
seconds = 0;
}

break
;
}
// switch

// Display the time
display_fade( SECONDS, old_seconds, seconds );
display_fade( MINUTES, old_minutes, minutes );
display_fade( HOURS, old_hours, hours );
if
(fade_step > 312) fade_step-= 312;

}
// for
} // main

20 comments:

Anonymous said...

Hi from Brazil !!!
Beautiful project !!!
Can you put the hex compiled file to us, for download ?
See ya !!!

Dave said...

Howdy, thanks for checking out the project!

I believe I do have a hex file somewhere, I'll see if I can dig it up and post it.

Unknown said...

Hi Dave,

Nice and simple project :). Good work...just noticed that your code has missing full includes. (maybe a bug in the blogger.

#include
#include
#include
#include

Dave said...

Thanks, I got that fixed. I think I got some invalid tags there from the code->html software I used. Curiously, the invalid tags seemed to confuse either Blogger or Firefox into displaying the text for the < and > entities instead of rendering them. Weird. Works now though.

MikoL said...

Hi Dave!
I'm from Hungary!
I want to make your clock, but i need the .hex file, would you send me? My email: mikolaiadam@freemail.hu!

If i finished the clock i will send you some pictures!

Thanks!

Adam

Ron R said...

Hi Dave:

Bob and I are a couple of not-so-recently retired engineers, new to electronics, who were fascinated by your "Binary Hard Drive Clock". We're now overloading our aging neurons in an attempt to make one ourselves.

We've looked at your write-up and come up with a few questions that we hope you have time to answer.

1. What is the power supply? Did you simply use a 5 volt wall transformer, or did you build your own 5 volt regulated circuit?

2. In your discussion of the back boards you write that the boards are connected to a 6 pin single row 90 degree header. In both the schematic and the pictures there appear to be 8 lines going to the seconds and minutes and 7 lines going to the hours. Was the 6 a mistake?

3. When you speak of the "5 or 6 LED ground wires" are you referring to the collector outputs from the NPN transistors?

Thanks:
Ron from albuq

Dave said...

Hey guys! I'm glad you liked the project, be sure to take some pictures as you build yours, I'd love to see them.

The power supply for my clock is just a basic 7805 regulator connected to a random wall-wart power supply that I picked up at All Electronics for a few bucks. You can see the regulator and a gray filter cap near the upper right-hand corner of the picture of the back of the clock.

You're right, I didn't describe the header correctly, it is not 6 pins. Sorry about that! I used 10 pin female sockets and snappable male headers for those. I just snapped off as many pins as I needed, 8 for the hours and 9 for the minutes and seconds. In the picture of the back of the clock you can see that the hours bank, at the top of the image, has a 10 hole socket connected to 8 pins, with two of the holes empty.

I guess I didn't mention it in the article, but if I had it to do over again I wouldn't use the headers. Instead I'd use a big piece of perf board (or an etched PCB) for all three LED banks and wire it directly. This would be much faster and more compact than the silly headers.

In the schematic the 'LED ground wires' are the connections between the LED cathodes and the NPN collectors. In the clock I have them physically wired in a star pattern. Each LED cathode is individually wired to a common point and then a single connection runs from there back to the main board where the NPN transistors are located. You can see the collection point where they are wired together at the bottom right-hand side of the image of the back of the clock.

Hope that helps!

Dave said...

Incidentally, this would be a great project for an Arduino, they're easier to program and you can get the complete hardware for 20 or 30 bucks. Wish they'd had them when I started that project.

A Butterfly would also be a great platform for this clock. It already has a real-time clock and a battery, so it'll stay accurate and keep the time when the power's out, and the speaker can be used to play hourly chimes and whatnot. The LCD could be used to display the time in regular digital format for the binary-impaired (call it 'training mode') and for setting alarms or whatever.

Course, half the fun is making the hardware yourself :)

Dave said...

Hi - I'm new to electronics and I'm trying to understand how your clock works. What I can't understand is how you are able to control 17 diodes using 6 NPN and 3PNP transistors. For example, if I want to diplay 23 hours, my understanding is that I would turn on the PNP for the hours board and then turn on NPN gates 1,2,3 and 5 (counting from the right in you diagram). Wouldn't the minute LEDs (UM0, UM1, UM2 and UM4) also light up as soon as we turn on the PNP transistor for the minutes board? Sorry if this is a stupid question.

thanks

Dave said...

Hi Dave, it's a good question. The LEDs in the minute and second registers stay off because the PNP transistors that connect those LEDs to power are turned off when the PNP transistor for the hours register is turned on.

At any time only one PNP transistor will be turned on. If you were to slow the display down you'd see that it never turns on the hours, minutes and seconds at the same time. It switches between them very quickly so that it looks like they are on at the same time.

This takes advantage of a property of the human visual system called 'persistence of vision'.

Meter said...

This project project brings the best planing for any other project.I am also thinking to start a simple project on water level dip meters.

^^^ [e] ^^^ said...

Hi!

Great informative post!

Just a question, your schematic shows the PNP transistors to be 2N3906, and the description mentioned a Darlington pair. Did you use 2 of the 2N3906 to form the pair yourself?

Lastly, I can only get hold of a NPN Darlington transistor array, is it possible to use 3 of the NPN for the hours, minutes and seconds. and then use 6 PNP to control the individual LED?

I would have to switch/reverse the +5V, GND and the direction of the LEDs too right?

Thanks for your time!

Dave said...

For the darlington I used MPSA63. I think I had problems with a regular PNP so I switched to the darlington.

Yes, you could use 3 NPN's and 6 PNPs by turning the three registers of 6 into 6 registers of three. The code will be a bit different because instead of displaying all the seconds bits, then switching to the minutes bits, then to the hours bits, you'll be displaying the three values of the first bits, then the second bits, etc., up through the sixth bit.

The polarity should be the same, PNP's on the high side, NPNs on the low (and it's usually easiest if the NPNs go directly to ground, keep the resistors and LEDs on the top side of the NPN's, unless you know a thing or two about transistors).

^^^ [e] ^^^ said...

Got it, thanks!!

IvanBG777 said...

Hello, :)
i`m happy to find that blog!
i will try to make that clock 1-st
and after that i will try to make it smaller - a watch size :)
can you send me bin or hex file
from your project?
i will make pics of the project for you if you like :)
my e-mail is ivan_ivan85@abv.bg
thanks a lot :)

Dave said...

I do have some bin files for the clock, but I'm not entirely sure which ones are good and which are old development version (it's been a long time since I compiled them, and I think one of them is a version I read back from the AVR).

What I'll do is share the source files I have, and you can give the .bin files a try. I'm pretty sure one of the three is what is actually running on my clock right now.

The source file is there too in case it is necessary to go back to the source and compile it. Again though, it's been about 7 years since I wrote it, so I'm not sure what state I left it in.

https://www.sugarsync.com/share/c93czn80xyma2

Totally coincidentally, I'm taking the binary clock to the Omaha Maker Group meeting for show-n-tell tonight, it'll be fun to tell them that someone is looking into doing something more with it :)

Dave said...

And here is a DropBox link (evidently you have to have a SugarSync account to access shared SugarSync files).

http://dl.dropbox.com/u/3288491/BinClock.7z

IvanBG777 said...

Thanks a lot! :)
I will make pics in the process
of the project and after that i will start the "watch" project.
When i have some real progress i will share it. :)

Anonymous said...

Hi David,

Just found your superb binary clock. Congratulations, it's the best one I've seen so far... and I want to make one for myself too.

I just quickly read your post, but essentialy your design is great from both the visual aspect and because of the fact it uses a true binary notation.

If you don't mind, I would like to ask you a question: would it be possible to convert your code to the Arduino - read ATmega328?
I have a couple of these lying around and would be great to put one (or more ;-)) of them on use.

Do you think this is feasible?
Thanks,
Luís

Dave said...

If I were doing an Arduino version of the clock I'd probably just start over from scratch. Arduino takes care of a lot of all of the low-level stuff that makes up the bulk of the code for this one.

I would probably use the MsTimer2 library and my EventFuse library to handle tracking the time and doing the ramped intensity on the LEDs (there's an example in the EventFuse library that does a ramped intensity blink that would be easy to adapt), and maybe one of the button libraries.

It's a pretty straight-forward project, it would be neat to see it running on Arduino.