Design a Digital Clock on Raspberry Pi Sense HAT

Design a Digital Clock on Raspberry Pi Sense HAT

The Story

Recently, I broke my digital clock bought almost 5 years ago. It is a simple and portable clock, which I think is well designed. But after this happened, I felt I can't live without a digital clock putting on my desk. As a programmer, I prefer programming my own digital clock, instead of purchasing one with at least S$20.
digital clock

Hardware

There is an 8x8 LED matrix on the Sense HAT board. I used it to display the current time.

Totally 64 pixels are very limited to show the current hour and minute. Thus, it is impossible to show the numbers as a seven-segment display (SSD), like my broken clock.

Design

Normally, we use 4 digits to show the current time, from 00:00 to 12:59. With an 8x8 display, it is intuitive that we can use 4x4 pixels to display one digit.

Alt Text http://whipptron.deviantart.com/art/4x4-Pixel-alphabet-plus-numbers-313579218

Then I coded a simple script to check the results:

Alt Text

Can you tell the time from the photo above? It shows 22:45. Super ugly, right?

Since most of the time I don't need to know the exact time, I may change my design. The hour in digits is a must, but minutes can be represented by others. Then my next design is perfect for an 8x8 LED matrix:
Current hour is displayed in the central 6x6 pixels, and the surrounding pixels are used to display minutes like an analog clock.

Alt Text

The GIF shows the time from 09:10 to 10:15. It is the exact clock face I want.

The following image shows the design I used to encode numbers from 0 to 12.

The full clock face is like the following image:
Clock Face

Row 1 and 8 are for minutes only. For row 2 to 7, each row starts with one minute-pixel, then 6 hour-pixels, and ends with another minute-pixel.

Coding

1. Setup

Turn on the low_light option, so that the LED light is not dazzling. I also covered the LED matrix with a piece of white paper to block the glare, because I use it as a digital clock on the desk, not an advertisement LED board after all.

There are three ports on the bottom of the Raspberry Pi board, so in order to make it stand on the desk, the board is actually put up side down. Therefore, I rotated the LED display 180 degrees.

sense.low_light = True  
sense.rotation = 180  
2. Encoding

Each number is encoded into a 6x6 matrix. In Python, it is represented as a 2D array. For instance, number five is encoded as follows:

five=[[0,1,1,1,1,0],[0,1,0,0,0,0],[0,1,1,1,0,0],[0,0,0,0,1,0],[0,1,0,0,1,0],[0,0,1,1,0,0]]  

1 is used to represent an ON pixel, and 0 is used as an OFF pixel.

3. Logic

There are 60 minutes in one hour. However, there are only 28 pixels to show the current minute. As a result, there are 29 statuses, which are from all pixels are off to a full circle is on. So the unit of each pixel is 60/29.

The first row is divided into two halves, the second half is filled first, then the first half, i.e. from [0,0,0,0,0,0,0,0] to [0,0,0,0,1,1,1,1], and then from [0,0,0,0,1,1,1,1] to [1,1,1,1,1,1,1,1].

For row 2 - 7, the first minute-pixel is checked, then append the hour row, and lastly, check the ending minute-pixel.

The last row is similar to the first row, but start from [0,0,0,0,0,0,0,0], then turn on the pixels from right to left, and all the way to [1,1,1,1,1,1,1,1].

Finally, update the clock every 10 seconds. Besides, a KeyboardInterrupt is used to close the program.

try:  
    while True:
        now = datetime.datetime.now()
        update_clock(now.hour, now.minute*1.0)
        time.sleep(10)
except KeyboardInterrupt:  
    print 'Closed.'
    sense.clear()

Demo

Source Code

from sense_hat import SenseHat  
import datetime  
import time

sense = SenseHat()  
sense.low_light = True  
sense.rotation = 180

# unit: 60/29
u = 60.0/29

zero = [[0,0,1,1,0,0],[0,1,0,0,1,0],[0,1,0,0,1,0],[0,1,0,0,1,0],[0,1,0,0,1,0],[0,0,1,1,0,0]]  
one=[[0,0,0,1,0,0],[0,0,1,1,0,0],[0,0,0,1,0,0],[0,0,0,1,0,0],[0,0,0,1,0,0],[0,0,1,1,1,0]]  
two=[[0,0,1,1,0,0],[0,1,0,0,1,0],[0,0,0,0,1,0],[0,0,0,1,0,0],[0,0,1,0,0,0],[0,1,1,1,1,0]]  
three=[[0,0,1,1,0,0],[0,1,0,0,1,0],[0,0,0,1,0,0],[0,0,0,0,1,0],[0,1,0,0,1,0],[0,0,1,1,0,0]]  
four=[[0,0,0,0,1,0],[0,0,0,1,1,0],[0,0,1,0,1,0],[0,1,1,1,1,0],[0,0,0,0,1,0],[0,0,0,0,1,0]]  
five=[[0,1,1,1,1,0],[0,1,0,0,0,0],[0,1,1,1,0,0],[0,0,0,0,1,0],[0,1,0,0,1,0],[0,0,1,1,0,0]]  
six=[[0,0,1,1,0,0],[0,1,0,0,0,0],[0,1,1,1,0,0],[0,1,0,0,1,0],[0,1,0,0,1,0],[0,0,1,1,0,0]]  
seven=[[0,1,1,1,1,0],[0,0,0,0,1,0],[0,0,0,1,0,0],[0,0,0,1,0,0],[0,0,1,0,0,0],[0,0,1,0,0,0]]  
eight=[[0,0,1,1,0,0],[0,1,0,0,1,0],[0,0,1,1,0,0],[0,1,0,0,1,0],[0,1,0,0,1,0],[0,0,1,1,0,0]]  
nine=[[0,0,1,1,0,0],[0,1,0,0,1,0],[0,1,0,0,1,0],[0,0,1,1,1,0],[0,0,0,0,1,0],[0,0,1,1,0,0]]  
ten=[[0,1,0,0,1,0],[1,1,0,1,0,1],[0,1,0,1,0,1],[0,1,0,1,0,1],[0,1,0,1,0,1],[0,1,0,0,1,0]]  
eleven=[[0,1,0,0,1,0],[1,1,0,1,1,0],[0,1,0,0,1,0],[0,1,0,0,1,0],[0,1,0,0,1,0],[0,1,0,0,1,0]]  
twelve=[[0,1,0,0,1,0],[1,1,0,1,0,1],[0,1,0,0,0,1],[0,1,0,0,1,0],[0,1,0,1,0,0],[0,1,0,1,1,1]]

nums = [zero, one, two, three, four, five, six, seven, eight, nine, ten, eleven, twelve]

# hour background color
z = [0,0,0]

# hour number color
w = [255,255,255]

# minute background color
e = [0,0,0]

# minute dot color
x = [0,150,0]

def row_one(m):  
    result = [0,0,0,0,0,0,0,0]
    if m > u*4 and m < u*25:
        result = [0,0,0,0,1,1,1,1]
    else:
        if m < u:
            result = [0,0,0,0,0,0,0,0]
        elif m < u*2:
            result = [0,0,0,0,1,0,0,0]
        elif m < u*3:
            result = [0,0,0,0,1,1,0,0]
        elif m < u*4:
            result = [0,0,0,0,1,1,1,0]
        elif m < u*25:
            result = [0,0,0,0,1,1,1,1]
        elif m < u*26:
            result = [1,0,0,0,1,1,1,1]
        elif m < u*27:
            result = [1,1,0,0,1,1,1,1]
        elif m < u*28:
            result = [1,1,1,0,1,1,1,1]
        else:
            result = [1,1,1,1,1,1,1,1]
    return result

def row_eight(m):  
    result = [1,1,1,1,1,1,1,1]
    if m < u*18:
        if m < u*11:
            result = [0,0,0,0,0,0,0,0]
        elif m < u*12:
            result = [0,0,0,0,0,0,0,1]
        elif m < u*13:
            result = [0,0,0,0,0,0,1,1]
        elif m < u*14:
            result = [0,0,0,0,0,1,1,1]
        elif m < u*15:
            result = [0,0,0,0,1,1,1,1]
        elif m < u*16:
            result = [0,0,0,1,1,1,1,1]
        elif m < u*17:
            result = [0,0,1,1,1,1,1,1]
        else:
            result = [0,1,1,1,1,1,1,1]
    return result

def convert_to_color(arr, background, color):  
    return [background if j == 0 else color for j in arr]

def update_clock(hh, mm):  
    if hh > 12:
        hh = hh - 12

    img = []

    # row 1
    row1 = row_one(mm)
    img.extend(convert_to_color(row1, e, x))

    # row 2-7
    for i in range(6):
        if mm > (u*(24-i)):
            img.append(x)
        else:
            img.append(e)

        # hour number
        img.extend(convert_to_color(nums[hh][i], z, w))

        if mm > (u*(5+i)):
            img.append(x)
        else:
            img.append(e)

    #row 8
    row8 = row_eight(mm)
    img.extend(convert_to_color(row8, e, x))

    sense.set_pixels(img)

try:  
    while True:
        now = datetime.datetime.now()
        update_clock(now.hour, now.minute*1.0)
        time.sleep(10)
except KeyboardInterrupt:  
    print 'Closed.'
    sense.clear()

More: IoT