Raspberry Pi Pico
4 x 7 Segment Display HT16K33
This page uses an Adafruit 0.56inch 7-segment display with an i2c backpack. The backpack contains an HT16K33 display controller. This is the same chip that is used to control the alphanumeric displays in lots of Raspberry Pi accessories. It is also used on a handful of other useful backpacks that Adafruit make. This display has a colon and a decimal place for each digit.
Without the backpack, there is a complex circuit required. The backpack also copes with the work of turning the correct LEDs on and off really quickly. With these displays, you can only light up the LEDs of a single digit at a time. To display more than one digit, the microcontroller has to switch between digits really quickly.
This display costs around £10 and is very easy to connect. It works at 3V with slightly less bright digits.

I made 4 connections for this circuit. It needs 3V power (to +), GND connected to the pin labelled -. The pin labelled D is the SDA pin and is connected to GP16. The pin labelled C is the SCL pin and is connected to 17.
The following is a library file that you can save to the Pico and keep separate from your main file. Save this file as ht16k33.py.
from machine import Pin, I2C
class backpack:
ADDRESS = 0x70
BLINK_CMD = 0x80
CMD_BRIGHTNESS = 0xE0
# Digits 0 - F
NUMS = [0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D,
0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71]
def __init__(self, d, c):
self.buffer = bytearray([0]*16)
self.i2c=I2C(0,sda=Pin(d), scl=Pin(c))
self.i2c.writeto(self.ADDRESS,b'\x21')
# 0 to 3
self.blink_rate(0)
# 0 to 15
self.set_brightness(15)
self.update_display()
def set_brightness(self,b):
self.i2c.writeto(self.ADDRESS,bytes([self.CMD_BRIGHTNESS | b]))
def blink_rate(self, b):
self.i2c.writeto(self.ADDRESS,bytes([self.BLINK_CMD | 1 | (b << 1)]))
def write_digit(self, position, digit, dot=False):
# skip the colon
offset = 0 if position < 2 else 1
pos = offset + position
self.buffer[pos*2] = self.NUMS[digit] & 0xFF
if dot:
self.buffer[pos*2] |= 0x80
def update_display(self):
data = bytearray([0]) + self.buffer
self.i2c.writeto(self.ADDRESS,data)
def print(self,value):
if value<0 or value>9999:
return
sdig = '{:04d}'.format(value)
dts = [int(x) for x in sdig]
for i,d in enumerate(dts):
self.write_digit(i,d)
def set_decimal(self, position, dot=True):
# skip the colon
offset = 0 if position < 2 else 1
pos = offset + position
if dot:
self.buffer[pos*2] |= 0x80
else:
self.buffer[pos*2] &= 0x7F
def clear(self):
self.buffer = bytearray([0]*16)
self.update_display()
def set_colon(self, colon=True):
if colon:
self.buffer[4] |= 0x02
else:
self.buffer[4] &= 0xFD
Here is some test code to save as your main.py file,
from ht16k33 import backpack
from time import sleep
# declare an instance
f = backpack(16,17)
# decimals on
for i in range(4):
f.set_decimal(i)
f.update_display()
sleep(0.1)
# decimals off
for i in range(4):
f.set_decimal(i, False)
f.update_display()
sleep(0.1)
# print something
f.print(1234)
f.update_display()
sleep(0.1)
# clear the display
f.clear()
sleep(0.1)
# blink the colon
for i in range(4):
f.set_colon()
f.update_display()
sleep(0.5)
f.set_colon(False)
f.update_display()
sleep(0.5)
# do some counting
for i in range(10000):
f.print(i)
f.update_display()
sleep(0.05)

