I have revised my Python drivers for the Holtek HT16K33 display controller, combining Micropython and Circuitpython versions into one and adding support for Adafruit’s Bi-colour 8×8 Matrix in the process.
The HT16K33 is a great I2C-connected component. I’ve been using it for years, usually with one of Adafruit’s I2C backpack boards because they do all the hard work — the electronics: resistors and such — for you. I have a lot of LED matrix and segment displays in various projects, pretty much all driven by the HT16K33. The rest use the Solomon SSD1306, but that’s a subject for another post.
The HT16K33 enables a variety of small LED displays which can be used for simple numerical readouts, or for more graphical, icon-driven visuals. You can present a surprising amount of information on a single 8 x 8 matrix, especially with scrolling text. Need a wider readout? There are 16 x 8 units for certain MCU boards, or you can assemble multiple 8 x 8 matrices into a row: drive all of them over the same I2C bus and set their unique I2C addresses accordingly. That’s what I did with my Matrix Clock project, but Weather is an effective weather forecast readout that uses just a single 8 x x display.
I originally wrote drivers for a variety of HT16K33-based displays which were using in Electric Imp projects like those mentioned above, and last year a did quick and dirty ports to Micropython and Circuitpython. They worked, but they were rough around the edges. Time then to put them into some sort of order and, ideally, into code capable of supporting either version of embedded Python.
I also wanted to separate out generic HT16K33 code from classes written for certain display types.
The result is a base driver class,
HT16K33, and various display-specific sub-classes which currently cover various four-digit, seven-segment LED backpacks, plus bi-colour 8 x 8 matrix displays, large and small 8 x 8 monochrome matrices, and 16 x 8 Featherwing Matrix boards. The parent class is loaded by each child class so that there’s a single
import for both parts.
There are some interesting quirks in how these products employ the HT16K33. The controller has a block of memory at address
0x00 which it uses as a display buffer. This memory governs what pins are asserted to drive a connected display. For example, the 0.56-inch four-digit, seven-segment displays use this space simply: digits one through four are controlled by a single byte at addresses
0x08, respectively. Address
0x04 is used to drive the display’s central colon symbol.
The 1.2-inch four-digit, seven-segment display works the same way, but the dots that comprise its two colon symbols alongside each row are lit according to bits in the value at address
0x02, the colon specifier, rather than bits in the digit values.
The 8 x 8 monochrome matrix also works with a byte per column of pixels, one bit per pixel, but this time it’s the byte at the odd address (
0x05 etc.) that determines the lit pixels. Curiously, the row 0 is actually row 7 of the source byte, so the driver has to do some bit manipulation to put each column’s bits in the correct order.
The 16 x 8 Featherwing matrix, which places two small 8 x 8 units side by side, puts its columns at addresses
0x1F (16-31) with the first eight columns at the even addresses and the second eight columns at the odd addresses. For example, the bytes for columns 0 and 1 are at stored at addresses 16 and 18; address 17 holds the byte for column 8, address 19 the byte for column 9, and so on. But there’s no need to move bit 0 to bit 7.
The 8 x 8 bi-colour matrix organises each column of pixels into two bytes. Row values map to bits so two bits determine a given pixel’s colour: 1 and 0 for green, 0 and 1 for red, and 1 and 1 for orange. The 16 bytes required for the eight columns run from address
Of course, all this means that driver writing is a much more interesting challenge than if all the displays worked exactly the same way.
You can get the code to use in your own project, or just to help you write your own driver software, on my GitHub repo.