BBC micro:bit
Four Letter Clock

Introduction

This is a clock project making use of a range of parts that work quite nicely by themselves, but need a bit of work to combine. Having successfully got a few RTCs to work, I wanted to find a nice easy way for a user to set the time on the chip. Since I had a few spaces left on my Pimoroni pHAT Stack, I thought I'd add a Touch pHAT into the project and write the code to allow the user to set the time.

micro:bit circuit

The photograph shows the following pieces of kit,

  • micro:bit
  • 4tronix Bit:2:Pi
  • Pimoroni pHAT Stack
  • Adafruit PiRTC PCF8523 Real Time Clock
  • Pimoroni Four Letter pHAT
  • Pimoroni Touch pHAT

Circuit

See the separate pages on these accessories to see how to connect them. I doubt that I would be so keen to connect them without the pHAT Stack and Bit:2:Pi.

Programming

This is a pretty long program. I've flattened the code a fair bit to allow all of this to squeeze into the micro:bit. I have left the full font in place for the moment. The next feature on the clock is likely to end this luxury, along with the comments.

The main problem to face in this project is caused by the Touch pHAT. The LEDs are not connected in a way that allows us to take advantage of the LED tracking feature of the CAP1166 chip. In order to replicate that functionality, we have to turn on the LED when we read that a pad is pressed. For this to work, there can't be long delays between readings being taken from the chip.

The second Touch pHAT issue is how easy it is to trigger a press. When you are setting the time, you want to avoid accidental presses causing too much damage. I decided to use a 3 second press of one pad to enter the time setting mode and a similar 3 second press to confirm the new time.

Pads A and B are used to set the hours, C and D to set the minutes.

from microbit import *

# four letter pHAT
fbuf = bytearray([0]*16)

# font - each character is 2 bytes - index is ASCII - 32
font = [0x0,0x6,0x220,0x12ce,0x12ed,0xc24,0x235d,0x400,0x2400,0x900,
        0x3fc0,0x12c0,0x800,0xc0,0x0,0xc00,0xc3f,0x6,0xdb,0x8f,0xe6,
        0x2069,0xfd,0x7,0xff,0xef,0x1200,0xa00,0x2400,0xc8,0x900,
        0x1083,0x2bb,0xf7,0x128f,0x39,0x120f,0xf9,0x71,0xbd,0xf6,
        0x1200,0x1e,0x2470,0x38,0x536,0x2136,0x3f,0xf3,0x203f,0x20f3,
        0xed,0x1201,0x3e,0xc30,0x2836,0x2d00,0x1500,0xc09,0x39,0x2100,
        0xf,0xc03,0x8,0x100,0x1058,0x2078,0xd8,0x88e,0x858,0x71,0x48e,
        0x1070,0x1000,0xe,0x3600,0x30,0x10d4,0x1050,0xdc,0x170,0x486,
        0x50,0x2088,0x78,0x1c,0x2004,0x2814,0x28c0,0x200c,0x848,0x949,
        0x1200,0x2489,0x520]
        
# update flp
def flshow():
    data = bytearray([0]) + fbuf
    i2c.write(0x70,data)
# set a digit
def set_dig(v,p):
    vbin = font[ord(v)-32]
    fbuf[p*2]   = vbin & 0xFF
    fbuf[p*2+1] = (vbin >> 8) & 0xFF    
# print a 4 letter string
def set_str(s):
    for i,ch in enumerate(s):
        set_dig(ch,i)       
# display time
def show_time(h,m):    
    hrs = str(h)
    if h<10: hrs = "0"+hrs
    mins = str(m)
    if m<10: mins = "0"+mins
    set_str(hrs+mins)
    fbuf[3] |= (1 << 6)
    flshow()

# RTC functions        
def bcd2bin(value):
    return (value or 0) - 6 * ((value or 0) >> 4)

def bin2bcd(value):
    return (value or 0) + 6 * ((value or 0) // 10)
       
def get_time():
    i2c.write(0x68, b'\x03')
    buf = i2c.read(0x68, 7)
    m = bcd2bin(buf[1])
    h = bcd2bin(buf[2])
    return m,h       

# touch functions
def w_i2c(a,r,v):
    i2c.write(a, bytes([r,v]))

# get the touch input and light the correct LEDs
def get_tou():
    w_i2c(0x2c,0,0)
    i2c.write(0x2c,b'\x03')
    d = i2c.read(0x2c,1)[0]
    p = sum(1<<(5-i) for i in range(6) if d>>i&1)
    i2c.write(0x2c,b'\x74')
    l = i2c.read(0x2c,1)[0]
    if p!=l:
        w_i2c(0x2c,0x74,p)
    return [p >> i & 1 for i in range(5,-1,-1)]
    
# set time mode       
def set_tim():
    i2c.write(0x70,b'\x83') #blink
    mm,hh = get_time()    
    tset = True
    lastr = 0
    while tset:        
        a = get_tou()
        r = int("".join(str(i) for i in a), 2)        
        if lastr!=r:
            if a[1]:
                hh -= 1
            if a[2]:
                hh += 1
            if a[3]:
                mm -= 1
            if a[4]:
                mm += 1
        if hh>23: hh=0
        if hh<0: hh=23
        if mm>59: mm=0
        if mm<0: mm = 59
        # set time held for 3 seconds
        if a[5]:
            tst = running_time()
            while a[5]==1:
                a = get_tou()
                sleep(5)
                if running_time()-tst>3000:
                    tset = False                                               
        show_time(hh,mm)
        lastr = r
    #set the time
    i2c.write(0x68, b'\x03')
    buf = i2c.read(0x68, 7)   
    tm = [0x03] + [buf[0],bin2bcd(mm),bin2bcd(hh),buf[3],buf[4],buf[5],buf[6]]
    i2c.write(0x68,bytes(tm))        
    i2c.write(0x70,b'\x81') #no blink

# init flp
i2c.write(0x70,b'\x21')
i2c.write(0x70,b'\x81') #no blink
i2c.write(0x70,b'\xFF') #brightness full

# init touch
w_i2c(0x2C,0x72,0)
sleep(1)
w_i2c(0x2C,0x41,0x30)
sleep(1)
last = 0
while True:
    a = get_tou()    
    # check for user interaction
    if a[0]==1:
        tmr = running_time()
        # wait for release
        while a[0]==1:
            a = get_tou()
            sleep(5)
        # if it took more than 3s, set time    
        if running_time()-tmr>3000:
            set_tim()
    # update if more than a second has passed
    if running_time()-last>1000:
        mm,hh = get_time()
        show_time(hh,mm)
        last = running_time()
    sleep(5)

Review & Next Steps

The purpose of this project was really to add another accessory to the pHAT Stack and to get the micro:bit controlling everything without running out of memory. Although there is a fair bit of trimming to cut out features that aren't being used, there's still a lot of scope for cutting things down.

The font is a luxury. It could be cut down to the digits and upper case characters without a great loss of future functionality. That would save a decent amount of space and would not be too hard to encode. That would leave space for messages as well as the time display.

The code for the time setting could trimmed down. Alternatively, a Piano or Drum HAT could be used, where the LEDs do the tracking. All of the touch devices give you the sensor data as a byte, which I've turned into a list in this program. In single touch mode, the byte will equal a binary place value or 0 if nothing is touched. Checking for those numbers instead of turning the reading to a list will save some lines of code.

After a little trimming, adding an alarm feature would be good. It's easy to run a few jumpers to a buzzer from the Bit:2:Pi or the spare headers on the pHAT Stack. A change to the time setting process and the code could be reused. As well as setting the time of an alarm, a means to set the alarm and turn it off needs to be considered.

At this point, there would be space left for one more thing. Something shiny and blinky?