Driving Seven Segment Displays

Many projects require the ability to display values. Until now, we have used the Serial library to display information on the computer connected to the Arduino. Particularly, in the Sensing Temperature and Humidity post, we used the Serial library to display temperature and humidity on the IDE’s Serial Monitor window. In this post, we will use a four digit seven-segment display to show numbers directly from the Arduino micro-controller.

The featured image at the beginning of this blog post was published by ©Raimond Spekking / CC BY-SA 4.0 (via Wikimedia Commons).

The Seven Segment Display

The seven-segment display is an electronic device that uses seven LED segments organized in the shape of a number eight. The LEDs are lit in different patterns to form numerals 0 to 9. An LED in the form of a dot is sometimes added at the bottom right of the seven segments to serve as a decimal point. The picture at the left, above, depicts a single digit seven-segment LED display. Each of its segments is identified by a letter as shown in the center diagram. The diagram at the right depicts the electrical connections within the seven-segment LED display. Some seven-segment displays have a common cathode, as shown, others have a common anode. For the remainder of the post, we will use common cathode seven-segment displays.

To use the seven-segment display, we connect the common cathode to ground and apply a voltage to each segment through a current limiting resistor. A voltage is applied to the current limiting resistor attached to the segment that needs to be lit while the segments that are to remain extinguished are connected to ground. The following diagram shows how the number four, ‘4’, can be formed by connecting segments ‘b’, ‘c’, ‘f’, and ‘g’ to a voltage and segments ‘a’, ‘d’, ‘e’ and the decimal point, ‘dp’, to ground.

Each segment requires its own current limiting resistor to ensure that regardless of the number of segments lit, current through each LED segment is the same, guaranteeing that each LED segment’s intensity remains constant. If a current limiting resistor was to be connected to the common cathode, a constant amount of current would be distributed between all lit segments, making display intensity diminish with the number of segments lit. Displaying a ‘1’ using two segments would be twice as bright as a ‘4’, using four segments, and three times brighter than a ‘9’ using six segments. Eight digital output pins are required to control and light each of the seven segments and decimal point. This is more than half of the available digital pins on the Arduino Uno which has fourteen digital pins externally available.

To drive more than one seven-segment digit, we use a method called multiplexing. When multiplexing, we activate one digit at a time and apply a voltage to each of the seven segments and decimal point. The digits are activated in sequence, fast enough for the eye not to notice that digits are not all lit at the same time, and only for a brief instant. To activate a digit, we ground its common cathode while the other common cathodes remain unconnected. To achieve this on the Arduino, the digital pin connected to the common cathode of the digit to activate is set to OUTPUT and a LOW value applied to it. This lights the digit’s segments whose digital pins are set to HIGH as the common cathode digital pin acts as a ground. For all other digits not being displayed, we let their common cathodes “float”, as if they were disconnected, by setting their digital pin modes to INPUT. In input mode, digital pins do not provide current, or very little, and act as if the pin is not connected.

The Four Digit Seven-Segment Display

With the multiplexing technique, eight digital pins are required for all of the segments and decimal point, and one digital pin per digit to display. To display four digits, twelve digital pins are required. Seven-segment displays come in a variety of packaging and sizes. For this project, I used a 0.36″ (9.2 mm) common-cathode 4 digit seven-segment display. For all of this to work, electrical requirements must be met. The electrical characteristics of each of the LEDs making up the seven-segment display are similar to single LEDs as discussed in my Arduino’s Blink post. Of importance are the absolute maximum forward current, IF, and the forward voltage, VF. For this project, I used the four digit, common-cathode, seven-segment display 3461AS from XLITX. For each segment, the absolute maximum forward current is 30 mA and the forward voltage is typically 1.8 V. According to the specification, the relative luminosity increases linearly from 0 mA up to 20 mA. The following diagram shows the internal structure of the 3461AS four digit seven-segment display and its pinout.

Each Arduino digital pin can source or sink 40 mA. In order to drive each of the four digits, we will use 8 digital output pins and connect them to the seven segments and decimal point anodes through a current limiting resistor, and we will use 4 digital output pins, each connected to the common cathode of each digit. As described previously, to light a segment of one of the digits, we apply a HIGH, or 5 V, to the segment’s anode and apply a LOW, 0 V to the digit common cathode of the segment to be lit.

Up to eight segments can be lit at once all drawing the same amount of current. We must limit the current through each segment to no more than 5 mA in order not to exceed the 40 mA an Arduino digital pin can sink. If each segment drops 1.8 V, current limiting resistors will each drop 3.2 V. According to Ohm’s law, R = V / I and to draw a maximum of 5 mA, the current limiting resistor must be at least 3.2 V / 0.005 A or 640 Ω. We will use 1 KΩ current limiting resistors, limiting each segment’s current to 3.2 V / 1000 Ω, or 3.2 mA, and limiting the total current sunk by the digital pin connected to the common cathode to 25.6 mA. The following diagram depicts the electrical circuitry to connect a four digit seven-segment display to an Arduino.

The Arduino’s digital pins 2 through 9 drive segments ‘a’ through ‘g’ and the decimal point through eight 1 KΩ current limiting resistors. The common cathodes of digits one through four are connected to the Arduino’s digital pins 10 through 13 respectively.

Breadboarding

The following picture depicts how to connect the different parts using a solderless breadboard, jumper wires, a seven-segment four digit display, eight 1 KΩ resistors, and an Arduino Uno micro-controller.

Displaying a Floating-Point Number

To demonstrate how to drive the four digit seven-segment display, I wrote a program that increments a floating-point number from −99.9 to 999.9 in 0.1 increments, five times a second and displays it on the four digit seven-segment display. Each digit is displayed in turn for 1.25 ms every 5 ms, thus updating at 200 Hz with a duty-cycle of 25%. The program can be found on Github. To run the program, download file FourDigitSevenSegmentDisplay.ino and load it in the Arduino IDE. Compile and load the program onto the Arduino microcontroller and watch the floating-point value increment on the four digit seven-segment display. In the following sections, I will explain how to convert each digit to their seven-segment counterpart, and how to manage the conversion of a floating-point number into a sequence of digits. First, we define the floating-point display format, the way we want the floating-point number to be displayed on the seven-segment four digit display.

We set the number of decimal places to one. With four digits, the smallest number that can be displayed is −99.9 and the largest number is 999.9. When numbers are between −9.9 and −0.1 or between 10.0 and 99.9, only 3 digits are required and the leftmost digit is left blank. When numbers are between 0.0 and 9.9, only 2 digits are required and the two leftmost digits are left blank. The following diagram shows how to display all of these use cases.

The Demonstration Program

The program starts with the usual header followed by different constants and definitions used throughout the program. First, we define the digital pins used to drive segments ‘a’ through ‘g’ (SEGMENT_A through SEGMENT_G) and the decimal point (SEGMENT_DP). Then, we define the digital pins used to activate digits (DIGIT_1 through DIGIT_4). Note that the program assumes that the digital pin numbers of the pins driving segments ‘a’ through ‘g’ are consecutive and in incrementing order. This also holds for the digital pin numbers of the pins activating digits 1 through 4. The number of digits is defined in constant numberOfDigits. Constant digitTimeOn contains the number of microseconds each digit remains lit while constant timeBetweenIncrements contains the amount of time in seconds between each increment. Constant microsecondsInASecond contains the number of microseconds in a second.

/*
  Four digit seven-segment display demonstration
  Program that displays a floating-point counter on a four-digit
  seven-segment display. It is associated with the Four Digit
  Seven Segment Display blog post at https://lagacemichel.com
  MIT License
  Copyright (c) 2021, Michel Lagace
*/

// Digital output pins to turn on or off each segment of the selected digit
#define SEGMENT_A 2
#define SEGMENT_B 3
#define SEGMENT_C 4
#define SEGMENT_D 5
#define SEGMENT_E 6
#define SEGMENT_F 7
#define SEGMENT_G 8
#define SEGMENT_DP 9

// Digital output pins to select each of the four digits
#define DIGIT_1 10
#define DIGIT_2 11
#define DIGIT_3 12
#define DIGIT_4 13
const int numberOfDigits = 4;

// Number of microseconds to leave digit on to achieve 200 Hz
const int digitTimeOn = 1250;

// Interval in seconds between counter increments - 0.2 sec for 5 counts/sec
const float timeBetweenIncrements = 0.2;
const float microsecondsInASecond = 1000000.0;

// Number of digits after decimal points and floating-point constants
const int scaleOfNumber = 1; // number of digits after decimal point
const int scalingFactor = pow(10.0, scaleOfNumber);
const float increment = 1.0 / scalingFactor;
const float minimumValue = -pow(10.0, numberOfDigits - 1) / scalingFactor + increment;
const float maximumValue = pow(10.0, numberOfDigits) / scalingFactor - increment;

// Counters to control value displayed on seven-segment display
float displayCounter = minimumValue;
long iterations = 0;

// Segment encodings for digits 0, 1, 2, 3, 4, 5, 6, 7, 8, and 9 and for the
// minus // sign '-'. Each bit represents a segment in the following order:
// n/a, a, b, c, d, e, f, g. The most significant bit is not used. Segments
// are identified as follows
//          a
//      +-------+
//      |       |
//     f|       |b
//      |   g   |
//      +-------+
//      |       |
//     e|       |c
//      |       |
//      +-------+  . dp
//          d
const int segmentPatterns[] = {
  0b1111110, // 0: a, b, c, d, e, f
  0b0110000, // 1: b, c
  0b1101101, // 2: a, b, d, e, g
  0b1111001, // 3: a, b, c, d, g
  0b0110011, // 4: b, c, f, g
  0b1011011, // 5: a, c, d, f, g
  0b1011111, // 6: a, c, d, e, f, g
  0b1110000, // 7: a, b, c
  0b1111111, // 8: a, b, c, d, e, f, g
  0b1111011, // 9: a, b, c, d, f, g
};

Constant scaleOfNumber states the number of decimal places to display; constant scalingFactor contains the power of ten corresponding to the number of decimal places; constant increment is the floating-point value to add to the displayed floating-point value at every increment time; constant minimumValue is the minimum floating-point value than can be displayed; constant maximumValue is the maximum floating-point value that can be displayed before returning to minimumValue. Global variable displayCounter is the floating-point value displayed on the seven-segment display. It is initialized to the minimum floating-point value to display, minimumValue. Global variable iterations counts the number of loop() iterations between displayCounter increments.

Lastly, segmentPatterns, is an array of constant integers that contain seven-segment encodings for numbers zero to nine as a pattern of lit and extinguished segments on each of the seven-segment display digits. Each encoding uses the least significant seven bits of each constant integer to represent the state of each of the seven segments, from ‘a’ to ‘g’, left to right. The most significant bit, the left-most one, represents segment ‘a’. The bit to its right represents segment ‘b’ and so on until the least significant bit which represents segment ‘g’. Each encoding is a series of bits where a ‘1’ represents a lit segment and a ‘0’ an extinguished one. For instance, digit ‘four’ is formed by lighting segments ‘b’, ‘c’, ‘f’ and ‘g’. So, the second, third, sixth, and seventh bits from the left are set to ‘1’ and all other bits remain ‘0’. The encoding for ‘four’ is thus 0b0110011.

Extinguishing All Digits

After a reset and between each displayed digit, all digits are extinguished. This is done by floating all digit digital pins attached to the common cathodes. Floating of digital pins is achieved by setting their modes to INPUT.

// Extinguish all digits in the four digit seven-segment display
void extinguishDigits()
{
  // Float all common cathodes by setting all digit pins as INPUT
  for (int currentDigit = DIGIT_1; currentDigit <= DIGIT_4; currentDigit++)
  {
    pinMode(currentDigit, INPUT);
  }
}

Displaying a Minus Sign

To display a minus sign on one of the digits we need to light segment ‘g’. First, we extinguish all digits using function extinguishDigits(); then we make digital pins associated to segments ‘a’ through ‘f’ and the decimal point LOW; we make the digital pin associated to segment ‘g’ HIGH; and we finally activate the specified digit by setting the digit’s pin mode to OUTPUT and making the pin LOW.

// Displays a minus sign on the specified digit.
void displayMinusSign(int digit)
{
  // Extinguish all digits
  extinguishDigits();

  // Turn off all digit segments
  for (int currentSegment = SEGMENT_F; currentSegment >= SEGMENT_A; currentSegment--)
  {
    digitalWrite(currentSegment, LOW);
  }

  // Turn off decimal point segment
  digitalWrite(SEGMENT_DP,LOW);

  // Turn on g segment to display minus sign ('-')
  digitalWrite(SEGMENT_G, HIGH);

  // Activate requested digit
  int selectedDigitPin = digit + DIGIT_1 - 1;
  pinMode(selectedDigitPin, OUTPUT);
  digitalWrite(selectedDigitPin, LOW);
}

Displaying a Digit

To display a digit, we light and extinguish the appropriate segments according to the pattern in the segmentPatterns array corresponding to the digit to be displayed. First, we extinguish all digits using function extinguishDigits(); then we get the pattern and put it into variable segments. If the value to display is not between 0 and 9, all segments are extinguished.

We then loop through the segments in reverse order, starting with segment ‘g’. At each iteration of the loop, we use the bitwise AND operator ‘&’ to extract the least significant bit of the pattern. The bitwise AND operator performs a logical AND operation on each pair of corresponding bits between two values. If both bits are ‘1’, the resulting bit is ‘1’, otherwise, the resulting bit is ‘0’. In the code, the operation ‘segments & 0b0000001‘, results in 0b0000001, or ‘1’, if the least significant bit of segments is ‘1’, and 0b0000000, or ‘0’, if the least significant bit of segments is ‘0’. On the first iteration of the loop, the least significant bit of segments corresponds to the state of segment ‘g’. Segment ‘g’ is set to HIGH or LOW according to the value of segments‘ least significant bit. We then shift the value of segments right by one bit, making the least significant bit correspond to segment ‘f’. The loop then repeats and turns on or off each segment in turn, in reverse order, for segments ‘f’, ‘e’, ‘d’, ‘c’, ‘b’, and ‘a’.

// Display a value, between 0 and 9 on the specified digit of the display
// and display the decimal point if required.
void displayDigit(int digit, int value, bool decimalPoint)
{
  // Extinguish all digits
  extinguishDigits();

  // Prepare the segments to light
  int segments = 0;
  if ((value >= 0) && (value <= 9))
  {
    segments = segmentPatterns[value];
  }

  // turn on or off each segment
  for (int currentSegment = SEGMENT_G; currentSegment >= SEGMENT_A; currentSegment--)
  {
    digitalWrite(currentSegment, segments & 0b00000001);
    segments = segments >> 1;
  }

  // Set the decimal point
  digitalWrite(SEGMENT_DP, decimalPoint);

  // Select requested digit
  int selectedDigitPin = digit + DIGIT_1 - 1;
  pinMode(selectedDigitPin, OUTPUT);
  digitalWrite(selectedDigitPin, LOW);
}

The decimal point is then set according to the value of the Boolean argument decimalPoint. We finally activate the specified digit by setting the digit’s pin mode to OUTPUT and making the pin LOW.

Displaying a Floating-Point Number

In order to display the number, we extract each digit from right to left, first from the fractional part, then from the integer part. If the number is in the displayable range, we make the number positive, remembering the sign, we then extract the integer and fractional parts of the number as two integers, integerPart and fractionalPart. We first display the fractional part by iterating through the number of decimal places, dividing the number by 10, displaying the remainder, obtained using the modulo operator ‘%‘, and continuing with the result of the division. We thus iterate through all fractional part digits from right to left. Each fractional part digit stays lit for digitTimeOn microseconds.

We then enter a loop to display the integer part. The first integer part digit is always displayed, even if it is zero, along with the decimal point following it. As we did for the fractional part, at each iteration of the loop, we divide the integer part by 10, display the remainder and continue with the result of the division. The loop ends when the remaining integer part, remainingValue, reaches zero. each integer part digit is lit for digitTimeOn microseconds.

// Display the number passed as a parameter on the four digit
// seven-segment display.
void displayNumber(float number)
{
  // Process digits from right to left
  int currentDigit = numberOfDigits;

  // Only numbers smaller than the maximum value can be displayed
  if ((number >= minimumValue) && (number <= maximumValue))
  {
    // Scale the number and extract decimal and integer portions of number
    bool negative = false;
    if (number < 0.0)
    {
      negative = true;
      number = -number;
    }
    int integerPart = number;
    int fractionalPart = (number - integerPart) * scalingFactor;

    // Display fractional part
    int remainingValue = fractionalPart;
    for (int i = 0; i < scaleOfNumber; i++)
    {
      int digit = remainingValue % 10;
      remainingValue = remainingValue / 10;
      displayDigit(currentDigit--, digit, false);
      delayMicroseconds(digitTimeOn);
    }

    // Display integer part
    remainingValue = integerPart;
    bool decimalPoint = true;
    do
    {
      int digit = remainingValue % 10;
      remainingValue = remainingValue / 10;
      displayDigit(currentDigit--, digit, decimalPoint);
      decimalPoint = false;
      delayMicroseconds(digitTimeOn);
    } while (remainingValue > 0);

    // Display minus sign if number is negative
    if (negative)
    {
      displayMinusSign(currentDigit--);
      delayMicroseconds(digitTimeOn);
    }
  }

  // Extinguish all digits ensuring that all digits are displayed the same amount of time
  // and wait a bit if not all digits were lit, ensuring constant intensity for all numbers
  // currentDigit actually contains the number of digits left.
  extinguishDigits();
  delayMicroseconds(digitTimeOn * currentDigit);
}

If the number is negative, we display a minus sign on the next digit to the left. The minus sign is displayed for digitTimeOn microseconds. All digits are then extinguished and a delay, in microseconds, corresponding to the number of blank digits times the value of digitTimeOn is introduced to ensure that digits are always turned on the same amount of time regardless on the number of digits displayed. This guarantees that all digits of the four digit seven-segment display have always the same intensity.

Setting up the Arduino

During the setup() portion of the Arduino program, we float all digit selection pins by setting the pin mode to INPUT using the extinguishDigits() call. We then set the mode of the digital pins driving the segments ‘a’ through ‘g’ and the decimal point to OUTPUT. Global variables displayCounter and iterations are reset to their default values.

// Set all digital pins used for digit selection as input to extinguish all digits
// and set display segments as outputs.
void setup() {

  // Float all digit selection digital pins as input
  extinguishDigits();
  
  // Set all segment digital pins as output and turn them off
  for (int currentSegment = SEGMENT_G; currentSegment >= SEGMENT_A; currentSegment--)
  {
    pinMode(currentSegment, OUTPUT);
  }

  // Set decimal point digital pin as output and turn it off
  pinMode(SEGMENT_DP, OUTPUT);

  // Reset counters
  displayCounter = minimumValue;
  iterations = 0;
}

The Main Loop

In the main loop() we increment the iteration counter and then check if the number of iterations adds up to the time to wait between increments, timeBetweenIncrements. If so, we reset the number of iterations, increment DisplayCounter and if it exceeds maximumValue, it is set to minimumValue. DisplayCounter is then displayed on the four digit seven-segment display through a call to displayNumber().

// Continuously display the counter and increment it at the specified interval.
void loop() {
  // Increment number of iterations until the interval between increments has been reached
  // Each iteration takes digitTimeOn*numberOfDigits microseconds
  iterations++;
  if ((iterations * digitTimeOn * numberOfDigits / microsecondsInASecond) > timeBetweenIncrements)
  {
    // Reset loop counter and increment display counter
    iterations = 0;
    displayCounter += increment;

    // Reset display counter if it has reached the maximum value
    if (displayCounter > maximumValue)
    {
      displayCounter = minimumValue;
    }
  }

  // Display floating-point counter value
  displayNumber(displayCounter);
}

What Next

Of course, displaying a counter is not the best use for a four digit seven-segment display. It did, however, allow us to test all use cases and ensure that the functions and program were functioning properly. Instead of showing a counter, one could display temperature, light intensity, voltage, current, or any value of interest. We have just gained the capability of displaying values without being connected to a computer.

Published by

Michel Lagacé

More than 40 years working in the high technology sector, I now share tips and tricks on software and electronics. I also love to cook and to write in my spare time.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s