BBC micro:bit
Four Letter pHAT

Introduction

The Four Letter pHAT is a Raspberry Pi breakout made by the company Pimoroni. It's a segemented alphanumeric display with 14 segements used to display characters. As a result, you can display digits, letters and many punctuation characters and symbols. The breakout uses an HT16K33 integrated cricuit to drive the LED segments. You control the display using i2c.

This is an image showing it connected to a micro:bit using a 4Tronix Bit:2:Pi, which makes the connections easier. The display looks much cooler than this in real life. The high speed refreshing means that the LEDs look less bright in photographs than they appear in real life. The display does look much brighter than this suggests and all segements do appear in the same brightness.

micro:bit circuit

The main benefit of this component is that you are controlling a shedload of LEDs with only a couple of pins that aren't general purpose anyway. As a display, you get to display words and symbols. Anything up to 4 letters at a time or scrolling on by.

Circuit

I did this on the Bit:2:Pi as you see in the image.

Pin Connections

ConnectionRaspberry Pimicro:bit
Power LED5V5V
Power IC3V33V
GroundGNDGND
DataPin 2Pin 20
ClockPin 3Pin 19

Programming

This is a long listing. Most of it is copied from the Adafruit Python library. Since the IC is used in a lot of places, the code is written with several displays in mind. This is a bodge of all of that lovely abstraction to get a quick result.

from microbit import i2c, sleep

class FourLetter:
    ADDRESS             = 0x70
    BLINK_CMD           = 0x80
    CMD_BRIGHTNESS      = 0xE0
    DIGITS = {
    ' ': 0b0000000000000000,
    '!': 0b0000000000000110,
    '"': 0b0000001000100000,
    '#': 0b0001001011001110,
    '$': 0b0001001011101101,
    '%': 0b0000110000100100,
    '&': 0b0010001101011101,
    '\'': 0b0000010000000000,
    '(': 0b0010010000000000,
    ')': 0b0000100100000000,
    '*': 0b0011111111000000,
    '+': 0b0001001011000000,
    ',': 0b0000100000000000,
    '-': 0b0000000011000000,
    '.': 0b0000000000000000,
    '/': 0b0000110000000000,
    '0': 0b0000110000111111,
    '1': 0b0000000000000110,
    '2': 0b0000000011011011,
    '3': 0b0000000010001111,
    '4': 0b0000000011100110,
    '5': 0b0010000001101001,
    '6': 0b0000000011111101,
    '7': 0b0000000000000111,
    '8': 0b0000000011111111,
    '9': 0b0000000011101111,
    ':': 0b0001001000000000,
    ';': 0b0000101000000000,
    '<': 0b0010010000000000,
    '=': 0b0000000011001000,
    '>': 0b0000100100000000,
    '?': 0b0001000010000011,
    '@': 0b0000001010111011,
    'A': 0b0000000011110111,
    'B': 0b0001001010001111,
    'C': 0b0000000000111001,
    'D': 0b0001001000001111,
    'E': 0b0000000011111001,
    'F': 0b0000000001110001,
    'G': 0b0000000010111101,
    'H': 0b0000000011110110,
    'I': 0b0001001000000000,
    'J': 0b0000000000011110,
    'K': 0b0010010001110000,
    'L': 0b0000000000111000,
    'M': 0b0000010100110110,
    'N': 0b0010000100110110,
    'O': 0b0000000000111111,
    'P': 0b0000000011110011,
    'Q': 0b0010000000111111,
    'R': 0b0010000011110011,
    'S': 0b0000000011101101,
    'T': 0b0001001000000001,
    'U': 0b0000000000111110,
    'V': 0b0000110000110000,
    'W': 0b0010100000110110,
    'X': 0b0010110100000000,
    'Y': 0b0001010100000000,
    'Z': 0b0000110000001001,
    'a': 0b0001000001011000,
    'b': 0b0010000001111000,
    'c': 0b0000000011011000,
    'd': 0b0000100010001110,
    'e': 0b0000100001011000,
    'f': 0b0000000001110001,
    'g': 0b0000010010001110,
    'h': 0b0001000001110000,
    'i': 0b0001000000000000,
    'j': 0b0000000000001110,
    'k': 0b0011011000000000,
    'l': 0b0000000000110000,
    'm': 0b0001000011010100,
    'n': 0b0001000001010000,
    'o': 0b0000000011011100,
    'p': 0b0000000101110000,
    'q': 0b0000010010000110,
    'r': 0b0000000001010000,
    's': 0b0010000010001000,
    't': 0b0000000001111000,
    'u': 0b0000000000011100,
    'v': 0b0010000000000100,
    'w': 0b0010100000010100,
    'x': 0b0010100011000000,
    'y': 0b0010000000001100,
    'z': 0b0000100001001000
    }
        
    def __init__(self):
        self.buffer = bytearray([0]*16)
        i2c.write(self.ADDRESS,b'\x21',repeat=False)
        self.blink_rate(0)
        self.set_brightness(15)
        self.update_display()
        
    def write_reg(self,reg,value):
        i2c.write(self.ADDRESS, bytes([reg,value]), repeat=False)
        
    def blink_rate(self, b):
        if b>3:
            b=0
        i2c.write(self.ADDRESS,bytes([self.BLINK_CMD | 1 | (b << 1)]), repeat=False)
        
    def set_brightness(self,b):
        if b>15:
            b=15
        i2c.write(self.ADDRESS,bytes([self.CMD_BRIGHTNESS | b]), repeat=False)    
    
    def update_display(self):
        data = bytearray([0]) + self.buffer
        i2c.write(self.ADDRESS,data,repeat=False)
        
    def clear(self):
        self.buffer = bytearray([0]*16)
        
    def raw_digit(self, value, position):
        self.buffer[position*2]   = value & 0xFF
        self.buffer[position*2+1] = (value >> 8) & 0xFF
        
    def set_digit(self, value, position):
        self.raw_digit(self.DIGITS.get(str(value), 0x00), position)
        
    def print_str(self, value, ralign=True):
        # Calculcate starting position of digits
        pos = (4-len(value)) if ralign else 0
        for i, ch in enumerate(value):
            self.set_digit(ch,i+pos)    
  
    def set_decimal(self, decimal, pos): 
        if pos < 0 or pos > 3:
            return
        # Set bit 14
        if decimal:
            self.buffer[pos*2+1] |= (1 << 6)
        else:
            self.buffer[pos*2+1] &= ~(1 << 6)  

flh = FourLetter()
flh.print_str("Byte")
flh.update_display()
sleep(5000)
# test decimals
for i in range(4):
    flh.set_decimal(True,i)
    flh.update_display()
    sleep(500)   
flh.clear()
# test numbers
for i in range(10000):
    flh.print_str(str(i),ralign=True)
    flh.update_display()
    sleep(50)
# some strings
words = ["   I", "HOPE", "THAT", "THIS", "WILL", "WORK", " NOW"]
for w in words:
    flh.print_str(w)
    flh.update_display()
    sleep(1000)
    
for w in words:
    flh.print_str(w.lower())
    flh.update_display()
    sleep(1000)       

The last part of this is a quick demonstration of the functions that I copied and adjusted. If you are using this code in a project, remove the characters from the DIGITS dictionary that you aren't using to save space.

Challenges

  1. 4 segments is a nice display to have when testing analog sensors. You get to see the readings more comfortably than having them scroll across the micro:bit.
  2. Scoreboard for a game. Use the left side for one team, right for another.
  3. Study the way the font is defined in binary. The on bits (1) are represent segements that are lit. Define your own character set.
  4. Following on from the previous. Rethink the display as though it is a single display. Design 4 characters that make an image when placed side by side - sitck man?
  5. Write a function to accept a string of characters of any length. Scroll the string across the display as you would on the micro:bit matrix, only 4 characters at a time.
  6. This should make a fine display for a stopwatch or with an RTC. The DS3231 has a default address that doesn't clash. Take advantage of the extra display capability to show the date when buttons are pressed.