A HT16K33 Display Library for easy printing to LED Modules

The Holtek HT16K33 is a LED Display driver IC and can be used with I2C. Just two I2C wires enables you to control lot of LEDs . As long as you have I2C available - you can easily add this display driver to your Arduino project.

The Holtek HT16K33 LED Driver Chip

The HT16K33 LED Driver Chip can control up to 16 x 8 LEDs. There are maker-friendly modules available with 7segment LEDs, 14segment LEDs, dot matrix and even "blank" PCB als simple backpacks for your own project. As the HT16K33 communicates via I2C you only need two GPIOs. If 128 LEDs are not enough - up to 8 chips can be controlled by selecting the I2C address. Yes - up to 1024 LEDs or 64 alphanumeric digits (each digit with 16 LEDs) driven by just two Arduino GPIOs! The HT16K33 comes in various SMD variants, the largest one can choose one of 8 eight I2C addresses (0x70 - 0x77). You only have to short 3 pads like in a 3bit binary Code (1-2-4). For example to get address 0x73 you have to connect the pads 1 and 2. As with every other I2C device an I2C Scanner will help you to find the right address of your device (yes - I did it wrong on my first multi display also...).

The HT16K33 supports "brightness" and several frequencies for "blinking". But only for all digits of the IC, not individually per LED segment or digit.

If you do the comparison with a MAX7219: the LED driver chip HT16K33 can support twice the LEDs of a MAX7219, needs just the two I2C wires but you only can connect up to 8 ICs. The HT16K33 doesn't have a built in character set. You have to care about each character in your software, ideally use the Noiasca HT16K33 library.

Modifications of Noiasca HT16K33 library  - Why a new library?

In the Arduino world we are constantly using the so called "print" class. You will use it for Serial output (Serial.print), printing to LCDs, OLEDs or even for the output of a webserver. I added this "print" method to the Noiasca HT16K33 library. The new library offers easier output with print (and some other helpers). The idea is simple: I'm using "print" for output on Serial and I want to use it the same manner with the LED displays.

If 8 digits are not enough, you can combine modules to one large logical display. You can handel the large display "as one" and print out text like you would do it with Serial.

display.print(F("even a long text expanding over up to 8 modules"));

So - if you need to print lot of text (or large numbers) on LED displays or displays with more than one IC this library could make your life easier. And yes: you can save precious SRAM, keep all data in Flash Memory by using the F-Makro!

The modifications - What's the background?

One capability of C++ is, that a class can derive properties and characteristics from another class. This is called Inheritance. Exactly this method was used: the new library inherits the print method. To be precise: I needed to implemet the "write" method. This method writes one digit on the display. The library inherits the print class (which uses the write method) and now we are able to "print" integers, floats, C-strings (char arrays), (Arduino) Strings to the display like you are used to from other libraries.

As you might know from other libraries they offer some helper methods. The method

display.setCursor(newPosition);

is such an example, it will set the cursor to a defined position. Later on you will read about more  important methods.

7 segment vs 14 segment character set

As explained on my MAX7219 page, there are a some restrictions on 7 segment displays, and all this restrictions are still true for the HT16K33 library. This is a limitation of the LED display itself, not the driver chip. Obviously 14 segment displays can show much better characters.

Currently I haven't found any 16 segment modules. Therefore a dedicated 16 segment support is still missing. But as soon as someone points me to affordable HT16K33 modules with 16 segment LEDs, the implementation should be straight forward.

Speaking about character sets, the Noiasca HT16K33 library supports printable characters 0d32 to 0d127 from the ANSI character table. Starting with Version 1.1.0 you can chose from several character sets if you don't like mine. The differences are minor, but it's up to you, use what you like best.

Points or Dots

Printing points or dots needs some additional explanation. As printing to the display is done character by character we have to find a way how to "activate" the decimal point of a previous printed digit. This is simply done by storing the last printed character. Therefore we can "reprint" the previous digit with an activated dot.

This works quite well - up to the point if the previous character is a dot. This just "breaks" the print out. This means you can easily print a dot after another character - but not a dot after a dot.

So this is will not work:

...

instead print a blank before the next dot, like following:

. . .

This will be shown up correctly on the display - and if you carefully check your display it is exactly what you want: 7 segments must not be enlighten (all off), but the decimal point does. So it makes absolutely sense to print blanks between the dots.

By the way: if you want print floats, you don't have to care about the comma, it will be printed accordingly:

display.print(14.32);

If you only need one decimal - this function comes already with the print function:

float myFloat = 14.32;
display.print(myFloat, 1);  // will print 14.3 only

End of Line

If we come to the "last" digit of our device, we have to to decide what should happen with the next character. On default the library jumps to the "NEXT_DEVICE". If you have written the last position on the last device, the display will wrap around to the first device.

The Linefeed and Carriage Return

In this library, linefeed (LF, 0d10, 0x0A, \n) and carriage return (CR, 0d13, 0x0C, \r) will have no special effect. Both characters are below 0d32 and therefore classified as not printable. So even if I recommend the print, println will have no negative effect as the linefeed of the println will be omitted.

Alternatives

It's always a good advise to look for alternatives. So google for Arduino HT16K33 libraries and have a look on some alternatives.

Like for many other modules - Adafruit - is always a good starting point for an Arduino Library. My first HT16K33 HW module is an original Adafruit 0.54" backpack. Lot of ideas came from their library. Adafruit does an amazing job providing us with libraries, support them and buy hardware from them! If I haven't written my own library, Adafruits library would be my first choice.

Another alternative comes from Arduino forum user HKJ-lygte. He provides a generic LED display driver "library" and the HT16K33 is just one out of many supported ICs. I liked his idea to split the hardware definition of each LED segment from the character set / font table. But there are a lot of precompiler #defines and I prefer to use the Arduino "standard" I2C library "wire.h" for my projects. His homepage is worth a visit because he has a very comprehensive collection of available LED displays.

RAM and Flash Usage

I did some comparisions with other libraries. The print class will cost some extra RAM and flash. Nevertheless it will help you saving a lot more RAM and flash memory the more you will print to your display. Mostly you will use the Serial.print anyway in your sketch - so lot of code of the stream library needs already compiled to your sketch and the new HT16K33 library is just reusing this code also. If you do your comparisions - just don't give up after the first line. Try a more complex example with lot of text and see, how easy it becomes with the Noiasca HT16K33 Library.

The example "12 Sevenseg" is a port of the "Adafruit LED Backpack" sevenseg example. The codes should do (nearly) the same, but you see already some differences in flash and RAM resources:

  flash RAM
Adafruit 5934 Bytes 456 Bytes
noiasca 4540 Bytes 445 Bytes

Both compiled for Arduino UNO on 1.9.2019, today's figures could differ. Other sketches could bring up other results.

HT16K33 Library Examples

The HT16K33 library comes with lot of examples, here are some of them:

Example Purpose
01 Strandtest 7segment a quick start to check, if your wiring is correct
02 Strandtest 14segment a quick start to check, if your wiring is correct
11 Hello World 14segment lot of print variants, including sprintf and floats
12 sevenseg a port of an Adafruit example to compare memory usage
13 format number 7segment example how to print numbers right aligned without sprintf
20 Multidisplay demo of 3 different displays using 4 ICs in one sketch
21 Scrolltext scroll text over 16 digits (2 ICs)
23 clock 14segment ds3231 a simple clock example for the RTC DS3231

If you have any ideas or a specific usecase, just come up with the idea you want do get added!

Installation and usage of the Noiasca HT16K33 Library

Download the ZIP (at the end of this page) and unzip it to your libraries folder. It might be necessary that you restart your Arduino IDE before you can use the library.

There is an additional example sketch called 11_HelloWorld_14segment. It shows all available methods and some special things you should know about the library.

a) Include the new library into your sketch with

 #include <NoiascaHt16k33.h>

b) the library files

In general, there is no need to change anything in the .h/.cpp files of the library. All hardware relevant settings can be done in your sketch. Just use the proper constructer, the right parameters for the begin method or one of the setters for your display object. I don't like libraries when they force one to "manipulate" properties in any file. This is something I learned from Marco the author of the MD_MAX7219 library. Library files should be stable between all sketches they are included to. "Modifying" the library files might effect other sketches and therefore these modifications should be avoided.

There are only two exception to this rule:

  • If you want to use a different font for your display, you can chose another font in the file src/NoiascaHt16k33.h around row 85.
  • If you like to have debug messages from the library, this is can be activated - as in most libraries - in the .h header file.

c) see the examples

I've added some examples to make it easier to understand the functionality of the library. The first sketch should always be some "strandtest" to find out if your hardware works with the new software.

Important Methods

Here are some of the most important methods in the Noiasca HT16K33 library:

uint8_t begin(uint8_t i2c_addr, uint8_t numDevices = 1);

Defines the (start) I2C address of your display and the number of used devices. If your display consists of more than one device, the other addresses must be ascending (e.g. 0x70 0x71 0x72). If you only use one device, you can omit the optional numDevice parameter as it gets defaulted to 1 anyway if not set. The begin method returns 0 if the initialization was successfull.

void blinkRate(uint8_t b);

Takes the constants HT16K33_BLINK_OFF, HT16K33_BLINK_2HZ, HT16K33_BLINK_1HZ or HT16K33_BLINK_HALFHZ and sets the blink rate of the display. As limited by the hardware all digits of your display will blink.

void off(void);

Switch of the display.

void on(void);

Switch display on (after it was switched off).

void clear(void);

Clear the display (and the library displaybuffer if used in future).

bool isConnected(void);

Returns true if the display is connected. If you are using multiple ICs as one logical display - all I2C addresses will be checked.

void setBrightness(uint8_t b);

Sets the brightness of the display, takes a value from 0 (lowest) to 15 (brightest).

void setCursor(uint8_t newPosition);

Set the cursor for the next writing operation to the definied new position

void setDigits(uint8_t newDigits);

Set the number of digits per device. For usual the begin method sets the digits based on the used constructor. If you want to use some custom built displays you can limit the amout of used digits to your needs.

void writeLowLevel(uint8_t position, uint16_t bitmask);

Sends the provided bitmask directly to the given position.

The used methods are based on the Arduino LCD API 1.0 but only methods which make sense for a LED display are used.

Due to the inheritance of the print class all write, print and println variants are supported like you are used to from other Arduino libraries.

Supported Hardware

currently the library supports following hardware modules

Features Constructor
7 segment, 8 digit Noiasca_ht16k33_hw_7
7 segment, 4 digit, colon digit Noiasca_ht16k33_hw_7_4_c
14 segment, 8 digit Noiasca_ht16k33_hw_14
14 segment, 4 digit Noiasca_ht16k33_hw_14_4

Adafruit 0.56" 4-Digit 7-Segment Display

For the Adafruit 0.56" 4-Digit 7-Segment Display w/I2C Backpack use the Noiasca_ht16k33_hw_7_4_c constructor.

This display uses digit 0 and 1 on the left (hour), digit 2 is used for the colon (to blink in second rhythm) and digit 3 and 4 are used on the right side (minute). The library will care about this odd display layout: To show 0123 on the display, just send this command:

display.print("0123");

To activate the colon use:

display.showColon(true);

Adafruit Quad Alphanumeric Display (14-Segment)

For the Adafruit Quad Alphanumeric Display - 0.54" Digits w/ I2C Backpack use the Noiasca_ht16k33_hw_14_4 constructor. It just limits the display to 4 digits. As alternative, you can use the standard 8 digit constructor and limit the digits in your setup().

Noiasca_ht16k33_hw_14 display = Noiasca_ht16k33_hw_14()
setup()
{
 Wire.begin();
 display.begin(0x70);
 display.setDigits(4);

WtihK 14 Segment Display

The "HT16K33 AlphaNumeric 0.54" 8-Digit 14 Segment LED I2C Interface" displays from WtihK are simple to use:

Noiasca_ht16k33_hw_14 display = Noiasca_ht16k33_hw_14()
setup()
{
 Wire.begin();
 display.begin(0x70);

Caveat

So, are there any reasons not to use Noiasca HT16K33? The library will have no dedicated support for dot-matrix displays. For dot-Matrix I still use the MAX7219 and I have no plan to migrate to the HT16K33. Also writing of individual segments is not in the focus of Noiasca HT16K33, even if you can write a raw bitmask to a digit.

If you want to format numbers, use either sprintf or do it manually. You will find some ideas in the examples.

History / Version log

1.1.0 Release Candidate 2021-03-14 
2021-03-14 extract chartables in separate font files

1.0.2 Release Candidate 2020-06-07 
2020-06-05 on(), off()
2020-06-04 Wire.begin() must be called in the user sketch. A simple hook to catch on AVR platform is implemented
2020-09-19 fix for ESP8266

Migration from another Library to Noiasca HT16K33

To migrate from another library to Noiasca HT16K33 you have to check each "output". But this step is pretty easy: treat your display just like you would print to the serial monitor or to a LCD.

*) Disclosure: Some of the links above are affiliate links, meaning, at no additional cost to you I will earn a (little) comission if you click through and make a purchase. I only recommend products I own myself and I'm convinced they are useful for other makers.

History

First upload: 2019-10-15 | Version: 2021-03-14 (1.0.2)