► Games

► Sound & Music

► Watches & Clocks


► Power Supplies

► Computers

► Graphics

► Thermometers

► Wearables

► Test Equipment

► Tutorials

► Libraries

► PCB-Based Projects

By processor

AVR ATtiny

► ATtiny10

► ATtiny2313

► ATtiny84

► ATtiny841

► ATtiny85

► ATtiny861

► ATtiny88

AVR ATmega

► ATmega328

► ATmega1284

AVR 0-series and 1-series

► ATmega4809

► ATtiny1604

► ATtiny1614

► ATtiny3216

► ATtiny3227

► ATtiny402

► ATtiny404

► ATtiny414

► ATtiny814

AVR DA/DB-series

► AVR128DA28

► AVR128DA32

► AVR128DA48

► AVR128DB28



► RP2040

► RA4M1

About me

  • About me
  • Twitter
  • Mastodon


RSS feed

Printing to a Serial LED Display

23rd March 2022

This article describes a simple routine to allow you to use a low-cost eight-digit LED module to display formatted data from a microcontroller project:


Displaying data on an eight-digit LED display module from an ATtiny85
using the routine described in this article.

The routine will work with almost any microcontroller; as a demonstration of the routine I've written a simple stopwatch program for the ATtiny85 or ATtiny402.


Seven-segment LED displays are a great way to provide clear, readable output from a project such as a digital clock, voltmeter, or frequency meter. They are also useful for providing debugging information when developing a project. The only downside is that there are a lot of pins to wire up, but you can avoid this by using a serial seven-segment display module that you connect to with a simple I2C or SPI interface.

To test the routine I chose a low-cost eight-digit seven-segment display module that is available from many vendors, including eBay, AliExpress [1], and Banggood:


Eight-digit LED display module based on a MAX7219.

They are based on the MAX7219 display driver [2] which is easy to control using SPI.

The MAX7219 is specified as operating from 4.0V to 5.5V, but it seems to work fine from 3.3V. For a brighter display when using it with a 3.3V board connect VCC on the MAX7219 to the +5V pin on the board.

Connecting the display

Connect the display using the appropriate SPI pins as follows:

CS Enable

ATtiny85 version

Here's how to connect the SPI serial display to an ATtiny85 (or ATtiny45/ATtiny25):


Connecting the eight-digit LED display module to an ATtiny85.

Note that because the ATtiny85 implements SPI using the USI, and it's acting as the SPI master, the DIN line on the display needs to connect to the DO line on the ATtiny85 (not MOSI). PB3 (digital I/O pin 3) is used as the chip select.

ATtiny402 version

Here's how to connect the SPI serial display to an ATtiny402 (or ATtiny202/ATtiny412/ATtiny212):


Connecting the eight-digit LED display module to an ATtiny404.

On the ATtiny402 the display connects to the MOSI and SCK lines. PA7 (digital I/O line 1) is used as the chip select, so you need to change the declaration at the start of the source to:

const int cs = 1;

Here's the prototype I used to try this out:


Displaying data on an eight-digit LED display module from an ATtiny402
using the routine described in this article.


The C function printf() is a print function that allows you to format several values using flexible format specifiers. Spence Konde has recently added support for printf() to his megaTinyCore and DxCore cores for the smaller AVR microcontrollers, and several other Arduino cores already include printf() support.

At first I couldn't see the benefit of printf() support in small microcontrollers, especially if like me you don't generally use the devices with serial output to the Serial Monitor. However, I recently realised that printf() gives an excellent way of displaying formatted output on a display, such as a serial LED display, using a routine like the one in this article.

The following table shows a selection of popular Arduino cores, and whether they support printf():

Arduino core Example printf()
Adafruit SAMD Boards Adafruit Feather M0 Yes
Arduino AVR Boards Arduino Uno No
Arduino Mbed OS RP2040 Boards Raspberry Pi Pico No
Arduino SAMD Boards Arduino Zero No
ATTinyCore ATtiny85 Yes
DxCore AVR128DA28 Yes
ESP32 Arduino Adafruit ESP32 Feather Yes
ESP8266 Boards Adafruit ESP8266 Feather Yes
megaTinyCore ATtiny402 Yes
Raspberry Pi RP2040 Boards Raspberry Pi Pico Yes
STM32 Boards Maple Mini Yes


An example of how you would use printf() is:

int E = 23;
Serial.printf("E = %d", E);

which will print:

E = 23

The following table illustrates some typical format strings for use in printf():

String Description
%d Print as a signed decimal integer
%u Print as an unsigned decimal integer
%x Print as an unsigned hexadecimal integer
%f Print as decimal floating point *
%2d Print integer in a width of 2 characters
%02d Print integer in a width of 2 characters left-padded with zeros

* By default in the DxCore, megaTinyCore, and STM32 Boards cores printf() doesn't include support for printing floating-point values, but a Board menu option allows you to enable this. Note that you will probably need a processor with over 4Kbytes of flash to use this option.

The demo program below gives an example of using some of these format strings.

Display interface

This display interface lets you print to the display using the standard print() or printf() I/O functions to print numbers, hexadecimal digits, and a few additional characters. It has the following features:

  • Characters printed to the display go to successive character positions. Characters beyond the end of the display are ignored.
  • A decimal point in a string isn't printed as a separate character, but lights the decimal point of the previous character, so the string: "3.1415926" will fit on the eight displays.
  • The minus and equals characters are supported, so you can label values; for example "A=2 B=-4".
  • A newline character, 10 or \n, resets the cursor to the start of the display.
  • You can clear the display by printing a clear character, 12 or \f, or by calling the clear() function.

Defining the segments

The array Segs[] is used to define the segment patterns for the 19 characters: 0 to 9, A to F, space, minus, and equals:

uint8_t Segs[19] = {
0x7e, 0x30, 0x6d, 0x79, 0x33, 0x5b, 0x5f, 0x70, 0x7f, 0x7b, // 0 to 9
0x77, 0x1f, 0x4e, 0x3d, 0x4f, 0x47,                         // A to F
0x00, 0x01, 0x09                                            // space, minus, equals

Defining the SerialDisplay class

First we define a class called SerialDisplay based on the Print class. This enables it to inherit the behaviour of the standard I/O functions such as print() to print strings and integers:

class SerialDisplay : public Print {
    void init();
    size_t write(uint8_t);
    void clear();
    void show(uint8_t, uint8_t);
    void cmd(uint8_t, uint8_t);
    uint8_t cur = 0;
    uint8_t last = 0;

Display-specific routines

The routine cmd() sends a command and data to the display as two bytes:

void SerialDisplay::cmd (uint8_t a, uint8_t d) {
  digitalWrite(cs, LOW);
  digitalWrite(cs, HIGH);

The routine show() displays a segment pattern on a specified display, numbered 0 to 7 from left to right:

void SerialDisplay::show (uint8_t d, uint8_t pat) {
 cmd(8-d, pat);

The routine init() sends the commands needed to initialise the display, and clear it:

void SerialDisplay::init () {
 cmd(0xF, 0);                                   // Test mode off
 cmd(0xB, 7);                                   // 8 digits
 cmd(0x9, 0x00);                                // Decode segments
 cmd(0xA, Brightness);
 cmd(0xC, 1);                                   // Enable display

To make the routines work with a different type of 8-digit display you should only have to replace these three routines with whatever is appropriate for the display.

Clearing the display

The routine clear() clears the display:

void SerialDisplay::clear () {
 for (int d=0; d<8; d++) show(d, Space);
 cur = 0;

Writing to the display

Finally, the routine write() is called by the Print functions to write a character to the display:

size_t SerialDisplay::write (uint8_t c) {
  if (c == '\n') {
    cur = 0;                                    // Return resets cursor
    return 1;
  } else if (c == ' ') {
    last = Segs[Space];
  } else if (c == '.') {
    last = last | DP;                           // Decimal point on previous display
    if (cur != 0) cur--;
  } else if (c == '-') {
    last = Segs[Minus];
  } else if (c == '=') {
    last = Segs[Equals];
  } else if (c >= '0' && c <= '9') {            // Digit
    last = Segs[c-'0'];
  } else if ((c|0x20)>='a' && (c|0x20)<='f') {  // Hex digit, upper or lower case
    last = Segs[(c|0x20) -'a'+10];
  } else if (c == 12) {                         // Clear display
    return 1;
  } else return 1;                              // Other characters don't print
  show(cur, last);
  if (cur < 7) cur++;
  return 1;

Stopwatch demo

The following demo program runs a stopwatch on the display, showing minutes, seconds, and hundredths of a second. A split number is also shown but not currently implemented.

First create an instance of the SerialDisplay class, called Display:

SerialDisplay Display;

Then here's the stopwatch demo, which uses printf() to format the data on the display:

void setup() {

void loop() {
   int split = 1;
   unsigned long now = (millis() - Start)/10;
   int mins = (now/6000) % 60;
   int secs = (now/100) % 60;
   int centis = now % 100;
   Display.printf("%d %02d.%02d.%02d\n", split, mins, secs, centis);
   while ((millis() - Start)/10 == now);

Here's the whole Serial Display library and demo program in a single file: Serial LED Display Program.


9th April 2022: Added ESP8266 and STM32 to the list of Arduino cores supporting printf().

  1. ^ MAX7219 8 Digit LED Display on AliExpress.
  2. ^ MAX7219 Datasheet on MaximIntegrated.

blog comments powered by Disqus