BBC micro:bit
Speaking Clock

Introduction

Just when you think that there aren't any more clocks that you want to make with the micro:bit...

This project has a micro:bit reading the time from an RTC. A second micro:bit is attached to a speaker. Press a button on the second micro:bit and it requests the time from the first one, then tells you the time in words.

micro:bit circuit

In the photograph, on the left, you can see the micro:bit that does the speaking. It is attached to a 4tronix Power:Bit which is supplying battery power. The Power:Bit duplicates the edge connector. That is fed into a Pimoroni Noise:bit, which has the speaker and a lovely design. On the right of the photograph, I have a micro:bit going into a 4tronix inline edge connector. This is another neat product that duplicates the edge connector. I've mounted the micro:bit on a Bit:2:Pi, which I am just using for power. Connected to the inline breakout is a Hobbytronics DS1338 RTC breakout. The accessories are overkill here and mainly to provide convenient power and audio out. The Noise:bit looks so good, you just have to use it.

The basic project requires one micro:bit to be connected to a speaker. The other micro:bit needs to have a connection to an RTC. I chose to use 2 micro:bits for this because the Noise:bit uses the micro:bit as a power source and because the speech is easier to make out when you can be quite close to the speaker (so, portable is best).

Programming - RTC

The micro:bit with the RTC needs to read the time and send it by radio to the other one. It listens for a radio signal (a 't' being received on channel 10) and then reads the time, makes some adjustments and sends it by radio to the micro:bit with the speaker.

from microbit import *
import radio

chnl = 10
radio.config(channel=chnl)
radio.on()

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'\x00')
    buf = i2c.read(0x68, 7)
    s = bcd2bin(buf[0] & 0x7F)
    m = bcd2bin(buf[1])
    h = bcd2bin(buf[2])
    w = bcd2bin(buf[3])
    dd = bcd2bin(buf[4])
    mm = bcd2bin(buf[5])
    yy = bcd2bin(buf[6]) + 2000
    return [s,m,h,w,dd,mm,yy]

def set_time(s,m,h,w,dd,mm,yy):
    tm = bytes([0x00] + [bin2bcd(i) for i in [s,m,h,w,dd,mm,yy-2000]])
    i2c.write(0x68,tm)
    

while True:
    s = radio.receive()
    if s is not None:
        if s=="t":
            t = get_time()
            hh = t[2]
            hh = hh % 12
            if hh==0: hh=12
            h = str(hh)
            m = str(t[1])
            tstr = h + " " + m
            radio.send(tstr)               
    sleep(50)
    

Programming - Speaking

The receiving micro:bit responds to a button press by sending the time request signal. When it receives a time signal, it works out how to say the time and then says so.

from microbit import *
import radio
import speech

chnl = 10
radio.config(channel=chnl)
radio.on()

nums_dict = {0: ('Zero', 'Ten'),
                    1: ('One', 'Eleven'),
                    2: ('Two', 'Twelve', 'Twenty'),
                    3: ('Three', 'thirteen', 'Thirty'),
                    4: ('Four', 'Fourteen', 'Forty'),
                    5: ('Five', 'Fifteen', 'Fifty'),
                    6: ('Six', 'Sixteen', 'Sixty'),
                    7: ('Seven', 'Seventeen', 'Seventy'),
                    8: ('Eight', 'Eighteen', 'Eighty'),
                    9: ('Nine', 'Nineteen', 'Ninety')}
                    
def numwords(str_num):
    if len(str_num) == 1:
        ones = int(str_num[0]) 
        tens = 0             
    else:
        tens = int(str_num[0])
        ones = int(str_num[1])
    if tens == 0 or tens == 1:
        return nums_dict[ones][tens]
    else:
        word = nums_dict[tens][2]
        if ones>0:
            word += ' ' + nums_dict[ones][0]
        return word

while True:
    if button_a.was_pressed():
        radio.send("t")
    s = radio.receive()
    if s is not None:
        t = s.split()
        h = numwords(t[0])
        m = numwords(t[1])
        if t[1]=="0":
            m = "oh clock"
        elif int(t[1])<10:
            m = "oh " + m
        tstr = h + " " + m
        speech.say(tstr)
    sleep(50)   

Review & Next Steps

It's reasonably easy to understand the time spoken through the speaker on the Noise:bit. It's always easier to understand what the micro:bit is saying when you have a rough idea of the kinds of words you should be hearing. If you play speech from the micro:bit to someone who doesn't already know what it is going to say, they have can have a hard time decoding it. If you tell them what it has said and play the same noise back, they will hear the words quite clearly. When you are expecting to hear some numbers, you tend to make them out quite well.

Using say isn't bad, but you can get better performance by working out the phonemes and using pronounce. A future version of this clock should probably do that.

I am old enough to remember a time when people were able to cope with the time being rounded to the nearest 5 minutes. A future version of this project should probably tell me the time and date the way I would prefer.

Plugging the micro:bit directly into the Noise:bit makes the speaker into a mouth for whatever graphic shown on the matrix. The portable speaking part of the project would do better with a different power solution.