Double Dabble for a Compact Clock

I like clocks, and the digital variety make fun little projects. Here’s an example: a tiny LED timepiece based on a trio of Adafruit Feather parts.

Compact time-teller

Feather is a nicely compact form-factor with a consistent pinout for digital IO, SPI, UART and I²C across the range. Feather add-on boards, called ‘FeatherWings’, use the same pinout. The upshot: you can solder two lines of female header onto one board and male header on the other, and the two boards fit together into small, neat package — as you can see in the pic above.

I originally started out with a Feather Huzzah ESP 8266 as the microcontroller board because I wanted WiFi so the clock can connect and get the current time, and because I wanted to try MicroPython. This part’s flaws are a paucity of memory and a woeful real-time clock (RTC), prompting some hacking to get the LED driver code to fit. I also had to make regular time checks to adjust for the on-board crystal’s terrible drift.

That all done, I found during testing that the clock would regularly freeze. Leave it running overnight and I’d see a very wrong time the following morning.

This prompted me to upgrade to the ESP32-based Feather Huzzah 32, which worked a treat: no memory issues and no problems running for extended periods of time — which, given this a clock and you want a clock to be always available to tell you the time when you need it to, was the most basic requirement.

Feathered nest: ESP32 and ESP8266 primary boards, and segment and matrix display add-ons

Flashing either Feather with MicroPython is straightforward. If you’d like to the try this clock project yourself, check out these instructions for installing MicroPython.

Unlike CircuitPython, MicroPython doesn’t make the device’s Flash accessible as a USB drive, so you need to use a tool called ampy to copy across code. I’ve included a bash script, install.sh, which uses ampy and prompts you for your WiFi details. It adds them to the code being copied to the Feather, but not to the code in your cloned repo. In the source code, look for the @SSID and @PASS placeholders.

You can install ampy with:

pip3 install --user adafruit-ampy

Depending on your platform, you may need to use sudo instead:

sudo pip3 install adafruit-ampy

ampy and install.sh need to know the device file for the connected Feather. On a Mac, it’s going to be among the list output when you run ls /dev/cu*. Linux will likely list it among /dev/tty*. Either way, you see something like /dev/cu.usbserial-01AB8E0B, and you’ll need to add that as an argument for install.sh:

install.sh /dev/cu.usbserial-01AB8E0B

To save entering it all the time, paste the device name into a file called device. install.sh will try to read this file if no device is included on the command line. To create the file:

  1. Run touch device.
  2. Run nano device.
  3. Paste in the device file name.
  4. Hit ctrlx and save.

I started out creating the clock to use a standard four-digit, seven-segment LED display. That led me to revise my HT16K33 driver, which is now available in a form that supports multiple display types — check out its GitHub repo. One of the supported display types is a 16 x 8 matrix LED Featherwing, which I bought to help me test the driver, but can now be used with the clock too: just swap the segment display for the matrix, and press M when asked by the install script.

Display control is as easy as BCD

The code uses a neat trick to display the time’s digits: it makes use of Binary Code Decimal (BCD), a way of treating an integer as a bit sequence as decimal digits rather than binary. Here’s an example: the binary value 10001001 is actually 137, but in BCD it’s 89. You treat the first and last groups of four bits (‘nibbles’) separately, thus 1000 is 8 and 1001 is 9, hence 89.

Back in the day, my Dragon 32’s Motorola 6809E CPU had a specific instruction, DAA, for converting an 8-bit integer between 0 and 99 into its BCD form. Today, I have to make use of whatever language I happen to be using. With Python, I usually do something like:

bcd = int(str(value), 16)
display.set_number((bcd & 0xF000) >> 12, 0)

but this time I wanted to see if it can be done without the complexity of converting the original value to a string and back, via a hexadecimal representation. This led me to to ‘Double Dabble’ algorithm, which works solely on integers. A glance at Wikipedia will give you all the details, but in short you do keep multiplying the original value by 2 (‘double’) for as many bits as you’re working with, and handle carries for decimal (‘dabble’):

def bcd(bin_value):
    # We need eight shifts for an 8-bit number
    for i in range(0, 8):
        # Multiply by 2
        bin_value = bin_value << 1 

        # Final op is always a shift, so break out when done
        if i == 7: break 
        
        # If the BCD units value (bits 8-11)  is 5 or more, add 3
        if (bin_value & 0xF00) > 0x4FF: bin_value += 0x300

        # If the BCD tens value (bits 12-15) is 5 or more, add 3
        if (bin_value & 0xF000) > 0x4FFF: bin_value += 0x3000
    return (bin_value >> 8) & 0xFF

For a clock, eight bits is sufficient — you never have a higher value than 59 — so that’s two digits each for hour and minute readouts. The multiplication by 2 shifts the bits from positions 0 through 7 to 8 through 15, with bits 8 through 11 representing the unit digit, and 12 through 15 the tens. At the end, the code shifts those bits back to 0 through 7 and masks off anything higher up, just in case.

So if the time is 13:47, the clock code sends 13 to bcd() and gets back 19: 00010011, or 1 (0001) and 3 (0011). Extract each digit value and use them to tell the LED driver what digit to display.

This is the matrix display version, but each digit is still selected using BCD

It’s a quick way of converting an integer value into digit parts and it works for much larger numbers that the small, two-digit variety needed here.

Settings and Preferences

You can specify preferences for the clock by copying across a file called prefs.json using ampy:

ampy --port put prefs.json

and then reboot or power-cycle the clock.

The file should contain the following keys:

{ "mode": true,    // True for 24-hour clock; false for 12-hour clock
   "colon": true,  // Show a colon between hour and minutes
                   // (segment only)
   "flash": true,  // Flash the colon if it's showing (segment only)
   "bright": 12,   // Display brightness between 1 and 15
   "on": true,     // Clock display is turned on (true) or off (false)
   "bst": true }   // Auto-check for British Summer Time

These are the default values, too.

What I really want to do is have the ESP32 serve up a web page which can be used to apply settings dynamically. That’s what my impClock project does, though it has the advantage of the Electric Imp Platform’s agents — per-device micro-servers in the cloud, which are just what you need for easy remote access to an IoT device’s settings. Full disclosure: I work there, but seriously, it does make device management much easier than the ES32 + Python does.

Start me up

Whatever display you choose, the clock will display “Sync” at power-up while it connects to WiFi and then synchronises its RTC with a Network Time Protocol (NTP) server (in the function get_time()). A pixel flashes at the bottom right of the LED to show you something’s happening! That same pixel is used to signal AM (unlit) or PM (lit) when the clock’s in 12-hour mode.

With the matrix display, the top right pixel is lit when the clock has become disconnected from the Internet for some reason. The decimal point after the first digit has the same role on the segment LED version.

The segment version

You can find all the application and installation code you need in this repo.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s