Binary Clock

No, I couldn’t think of a better name.

It’s a pretty standard binary clock. I got a cheap, plastic binary clock from Thinkgeek decades ago. It works, of course, but is kind of ugly. I enjoy the simplicity (and opacity) of the design and realized I wanted something that leaned into that.

Which led to me designing and building this clock. I really love the 1950s industrial look of this. We’re bringing advanced technology into the home.

That’s a Clock?!

For the uninitiated, here’s how it works.

Each light represents a binary digit. Like with regular numbers, the most significant digit is on the left and the least significant is on the right. The top row represents the hour, the bottom row represents the minutes.

I don’t like how blinky/active a binary clock showing seconds is so I didn’t bother representing it.

In this example image, the hour row has 8, 2, and 1 lit. Adding those together you get 11. The minute row has 2 lit. Thus the time is 11:02. Yes, it’s a little tedious at first, but you quickly start to recognize the patterns and eventually can read it as quickly as a regular clock.

With a clock using 24-hour mode, you only need 5, not 6, lights to represent the hour, but for symmetry and to make it easier to reprogram the clock to a different orientation, I made both rows have 6 lights. If I wanted to rotate the clock to be vertically oriented, I just need to swap some pin assignments around to make it work in that orientation.

What’s Outside?

The case is made from scrap bamboo board and some scrap aluminum. The aluminum was cut with a hacksaw and the holes were positioned with a printed paper template then drilled out with a hand-drill. I got close enough. The aluminum panel is held captive by a dado in the bamboo. The bamboo box is just a basic miter box, glued together. The miters were a little off because my table saw’s stops were off. I only realized this after I got a digital angle finder after I had made my cuts. I card scraped and sanded the bamboo and then finished with some bee’s paste wax.

The lights themselves are panel mount LEDs bought directly from china. They’re rated for 5-6 volts, but driving them at 3.3V keeps them at a good brightness level and doesn’t draw an excessive amount of current–if all of them are lit we’re right at the maximum current rating (60mA) for the MCU.

What’s Inside?

The micro-controller is the massively overkill Itsybitsy-m4. You really don’t need a 120MHz ARM processor to track the time and control 12 LEDs and 2 buttons. A dinky little MSP430 would’ve work much better and costs about $1, but I really like programming my MCUs using Go and the Itsybitsy that I had on-hand let me do that.

There’s a little .1” perf board that I soldered my connections to and a couple of DIP buttons to. The wiring was done with some very fine enameled coil wire. The MCU is on headers so can swap it out if I blow it up without having to remake the whole board.

It’s powered off of USB.

What’s The Code?

Most of the low-level stuff gets taken care of for you when using TinyGo so the code for the clock is dead simple. Since the USB serial is mapped to UART0 and UART0 is mapped to STDIN and STDOUT, it’s pretty easy to get data in and out of the micro controller. If I’m powering the clock off a laptop, it’d be nice to be able to set the time by just writing the time to the serial port. I made a quick attempt to do that, but it didn’t work, so that bit is commented out for now.

I’d also like to have different display modes and maybe animations–all controllable via USB serial. But that will be much further down the road.

package main

import (
	"machine"
	"time"
)

// timeOffset is added to the time.Now() time to determine wall-clock time
// pressing the minute button increases timeOffset by 1 minute. Pressing the
// hour button increases timeOffset by 1 hour
var timeOffset time.Duration

var HourLEDs = [6]machine.Pin{
	machine.D9,
	machine.D10,
	machine.D11,
	machine.D12,
	machine.D13,
	machine.D7, // off-by-one in meat space is even more annoying
}

var MinuteLEDs = [6]machine.Pin{
	machine.D0,
	machine.D1,
	machine.D3, // oops soldered something wrong
	machine.D2, // *shrug*
	machine.D4,
	machine.D5,
}

var ticker = make(chan struct{})

const (
	hourBtn   = machine.A0
	minuteBtn = machine.A1
)

func init() {
	//setup 12 LEDs
	// There's the same number of Hour LEDs as Minute, so we just do both
	// but this could cause problems if anything changes so... don't change anything
	for i := range HourLEDs {
		HourLEDs[i].Configure(machine.PinConfig{Mode: machine.PinOutput})
		MinuteLEDs[i].Configure(machine.PinConfig{Mode: machine.PinOutput})
	}

	//setup 2 buttons
	hourBtn.Configure(machine.PinConfig{Mode: machine.PinInputPulldown})
	minuteBtn.Configure(machine.PinConfig{Mode: machine.PinInputPulldown})

	// write to ticker chan to force display to update after
	// every button press
	var lastPress time.Time
	hourBtn.SetInterrupt(machine.PinRising, func(p machine.Pin) {
		if time.Since(lastPress) > 250*time.Millisecond {
			lastPress = time.Now()
			timeOffset += time.Hour
			go func() { ticker <- struct{}{} }()
		}
	})

	minuteBtn.SetInterrupt(machine.PinRising, func(p machine.Pin) {
		if time.Since(lastPress) > 250*time.Millisecond {
			lastPress = time.Now()
			timeOffset += time.Minute
			timeOffset.Truncate(time.Hour)
			go func() { ticker <- struct{}{} }()
		}
	})
}

func main() {
	go func() {
		for {
			time.Sleep(time.Second)
			ticker <- struct{}{}
		}
	}()

	/*
		go func() {
			for {
				var input string
				fmt.Scanln(&input)
				t, err := time.Parse(time.Kitchen, input)
				if err != nil {
					continue
				}
				timeOffset = time.Since(t)
			}
		}()
	*/

	for {
		//update display
		h, m, _ := time.Now().Add(timeOffset).Clock()

		for i := range HourLEDs {
			if (h>>i)&1 == 1 {
				HourLEDs[i].Set(true)
			} else {
				HourLEDs[i].Set(false)
			}
			if (m>>i)&1 == 1 {
				MinuteLEDs[i].Set(true)
			} else {
				MinuteLEDs[i].Set(false)
			}
		}

		<-ticker
	}
}

FIN