Recreating a 7 segment display with Adafruit GFX & SSD1306 OLED

Git Repository –
7-seg_adagfx_ssd1306.git

I had a project come up recently where I needed to display some numbers that looked like an old 7-segment display. The only problem, I was using a 128×64 OLED driven by a SSD1306 driver. Luckily this display is a lot more capable than the vacuum fluorescent and 7-segment displays of the past, we should be able to overcome this dilemma with some simple code.

First things first before we can write any code, we first have to understand the basic operation of a 7-segment display. Lets start by looking at why they call it a 7-segment display.

It’s pretty obvious when you look at the diagram above where the name came from, any digit 0-9 can be shown by activating the right combination of 7 segments. That’s it, pretty simple right! Now how can we do this pragmatically?

One easy solution is to use something called a truth table, to store our truth table we will need to create a multi-dimensional array. Multi-dimensional arrays may sound scary if you have never used one, however if you are familiar with basic arrays already then you can think of them simply as an array of arrays.

Arduino Reference – Arrays

We will define our array as a const integer since it will never change, we will need 10 unique arrays to store 0-9 values with 7 positions in each array, one for each segment.

// define segment truth table for each digit
static const int digit_array[10][7] = {
  {1, 1, 1, 1, 1, 1, 0},  // 0
  {0, 1, 1, 0, 0, 0, 0},  // 1
  {1, 1, 0, 1, 1, 0, 1},  // 2
  {1, 1, 1, 1, 0, 0, 1},  // 3
  {0, 1, 1, 0, 0, 1, 1},  // 4
  {1, 0, 1, 1, 0, 1, 1},  // 5
  {1, 0, 1, 1, 1, 1, 1},  // 6
  {1, 1, 1, 0, 0, 0, 0},  // 7
  {1, 1, 1, 1, 1, 1, 1},  // 8
  {1, 1, 1, 0, 0, 1, 1}   // 9
};



Lets take a look at the function render_digit() from the example code.

Parameters: render_digit(x_pos, y_pos, digit, color)

The default starting position is x0,y0 which will render in the top left corner of the screen, to add an offset use X_pos/Y_pos. We use a for loop to run through our truth table and check which segments need to be active for the currently selected (digit). We then use a switch case to draw a rectangle to display any active segments. Note this will only display a single digit if called, to display multiple digits check the display_digits() function!

void render_digit(uint8_t pos_x, uint8_t pos_y,
                  uint8_t digit, uint8_t color) {
  // loop through 7 segments
  for (uint8_t i = 0; i < 7; i++) {
    bool seg_on = digit_array[digit][i];
    // if seg_on is true draw segment
    if (seg_on) {
      switch (i) {
        case 0:
          display.fillRoundRect(2 + pos_x, 0 + pos_y, 9, 3, 2, color); // SEG a
          break;
        case 1:
          display.fillRoundRect(10 + pos_x, 2 + pos_y, 3, 9, 2, color); // SEG b
          break;
        case 2:
          display.fillRoundRect(10 + pos_x, 12 + pos_y, 3, 9, 2, color); // SEG c
          break;
        case 3:
          display.fillRoundRect(2 + pos_x, 20 + pos_y, 9, 3, 2, color); // SEG d
          break;
        case 4:
          display.fillRoundRect(0 + pos_x, 12 + pos_y, 3, 9, 2, color); // SEG e
          break;
        case 5:
          display.fillRoundRect(0 + pos_x, 2 + pos_y, 3, 9, 2, color); // SEG f
          break;
        case 6:
          display.fillRoundRect(2 + pos_x, 10 + pos_y, 9, 3, 2, color); // SEG g
          break;
      }
      seg_on = false;
    }
  }
}

So now that we have a grasp on rendering a single digit lets try rendering multiple! We will use the modulo operation to pull each digit from an integer and place each into an array. Then we render that digit in the appropriate location based on the starting position and the character spacing defined.

void render_digits(uint8_t pos_x, uint8_t pos_y,
                   uint8_t spacing_x, uint16_t digit, uint8_t color) {
  uint8_t digit_array[] = {digit / 100 % 10, digit / 10 % 10, digit % 10};
  if (digit > 99) {
    render_digit(pos_x, pos_y, digit_array[0], color);
  }
  if (digit > 9) {
    render_digit(pos_x + spacing_x, pos_y, digit_array[1], color);
  }
  render_digit(pos_x + (spacing_x * 2), pos_y, digit_array[2], color);
}


That’s it, pretty simple right? Fun fact this code could be modified and used to drive a real 7 segment display too, all that would be needed is to configure 7 GPIO pins and toggle them in the render_digit() switch case.

Arduino code!

Adafruit Libraries:
Git-hub Adafruit-GFX Library
Git-hub Adafruit-SSD1306 Library

// EXAMPLE 7 SEGMENT DISPLAY RENDERING FOR ADAFRUIT_GFX & SSD1306
// Copyright 2018 T&A Laboratories LLC
// writen by: Aaron Williams on 9/7/2018
//
// Do not distribute without permission of the author
// !!!!!!!

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define OLED_RESET 11
Adafruit_SSD1306 display(OLED_RESET);

// define segment truth table for each digit
static const int digit_array[10][7] = {
  {1, 1, 1, 1, 1, 1, 0},  // 0
  {0, 1, 1, 0, 0, 0, 0},  // 1
  {1, 1, 0, 1, 1, 0, 1},  // 2
  {1, 1, 1, 1, 0, 0, 1},  // 3
  {0, 1, 1, 0, 0, 1, 1},  // 4
  {1, 0, 1, 1, 0, 1, 1},  // 5
  {1, 0, 1, 1, 1, 1, 1},  // 6
  {1, 1, 1, 0, 0, 0, 0},  // 7
  {1, 1, 1, 1, 1, 1, 1},  // 8
  {1, 1, 1, 0, 0, 1, 1}   // 9
};

void setup()   {
  Serial.begin(9600);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3D (for the 128x64)
  display.clearDisplay();   // Clear the buffer.
  // func params: render_digits(pox_x, pos_y, spacing, color)
  render_digits(0, 0, 18, 999, WHITE);
  display.display();
  delay(2000);
}

void loop() {
  // count up to 999
  for (int cnt = 0; cnt < 1000; cnt++) {
    display.clearDisplay();   // Clear the buffer.
    render_digits(0, 0, 18, cnt, WHITE);
    display.display();
  }
}

// RENDER DIGITS:
// use this funtion to print digits
// valid range range: 0-999
void render_digits(uint8_t pos_x, uint8_t pos_y,
                   uint8_t spacing_x, uint16_t digit, uint8_t color) {
  uint8_t digit_array[] = {digit / 100 % 10, digit / 10 % 10, digit % 10};
  if (digit > 99) {
    render_digit(pos_x, pos_y, digit_array[0], color);
  }
  if (digit > 9) {
    render_digit(pos_x + spacing_x, pos_y, digit_array[1], color);
  }
  render_digit(pos_x + (spacing_x * 2), pos_y, digit_array[2], color);
}

// RENDER DIGIT
// don't use this unless you only need a single digit
// use render_digits() func above instead
void render_digit(uint8_t pos_x, uint8_t pos_y,
                  uint8_t digit, uint8_t color) {
  // loop through 7 segments
  for (uint8_t i = 0; i < 7; i++) {
    bool seg_on = digit_array[digit][i];
    // if seg_on is true draw segment
    if (seg_on) {
      switch (i) {
        case 0:
          display.fillRoundRect(2 + pos_x, 0 + pos_y, 9, 3, 2, color); // SEG a
          break;
        case 1:
          display.fillRoundRect(10 + pos_x, 2 + pos_y, 3, 9, 2, color); // SEG b
          break;
        case 2:
          display.fillRoundRect(10 + pos_x, 12 + pos_y, 3, 9, 2, color); // SEG c
          break;
        case 3:
          display.fillRoundRect(2 + pos_x, 20 + pos_y, 9, 3, 2, color); // SEG d
          break;
        case 4:
          display.fillRoundRect(0 + pos_x, 12 + pos_y, 3, 9, 2, color); // SEG e
          break;
        case 5:
          display.fillRoundRect(0 + pos_x, 2 + pos_y, 3, 9, 2, color); // SEG f
          break;
        case 6:
          display.fillRoundRect(2 + pos_x, 10 + pos_y, 9, 3, 2, color); // SEG g
          break;
      }
      seg_on = false;
    }
  }
}