connecting an arduino to a seven segment display

Connecting an Arduino to a 7 segment display

One simple way of displaying numerical data from an Arduino (or any microcontroller for that matter) is through the use of seven segment displays. But what are seven segment displays and how do you connect an Arduino to a seven segment display?

Seven segment displays

seven segment display
First, an explanation of what is a seven segment display, for those that do not know. Quite simply, a seven segment display is a numeric display made out of individual LED (or LCD) segments that in different configurations present different numerical digits. You will already be accustomed to seven segment displays as they are used in a multitude of digital devices from everyday life such as microwave ovens and alarm clocks.

sixteen segment display
Seven segment displays come in many shapes and sizes, different colors. Some even have more than seven segments used to display letters as well.

For our purposes, we’ll have a look at normal LED displays. Electronically speaking, there are two types: Common-anode and common-cathode. To understand what that means, maybe a simple schematic diagram will help make things clearer.
seven segment schematic representation

As you can see, the LEDs in such a display are connected in such a way that one side of each LED is connected to each other. That is the common connection. With a common anode display, the common connection will be connected to your positive voltage supply and each LED’s cathode is individually connected to ground to turn that segment on. Don’t forget about the current limiting resistors, though. It’s just vice versa for common cathode displays.

When connecting a display to your microcontroller or Arduino, it is important to know what the common connection is. This will determine whether you have to drive an output high or low to light up the relevant segment.

Multiplexing multiple displays

Displaying one digit might be all good, but you can only display values 0 until 9. not particularly useful for most projects. So you need to connect up more displays. This sounds like it will end up with a lot of connections. 7 connections post digit plus the common connection, and the another one of you want to light up the decimal point. That will use up a lot of outputs on your microcontroller. You might not even have enough pins for all these connections!

Luckily there is a better way to display more than one digit using only one additional microcontroller output for each extra digit. It’s called multiplexing. Essentially you will turn on each digit individually and in sequence. This will be done rapidly so that what is called persistence of vision will make it look like all the digits are on at the same time.

Connecting the displays to the Arduino

connecting arduino to seven segment
Breadboard layout
It’s a lot to wire but

  • The pins 22 – 29 is wired to the one end of the current limiting resistors for segments a – g
  • The pins 38 – 41 is wired to the common anode pin on each display
  • Each display’s segment pins is wired in parallel with the next display’s segment pins

This is what the wiring look like on my setup:

arduino seven segment wiring
An example of what the wiring could look like when done

These connections and subsequent code are only applicable to the Arduino Mega. If you have, for instance, an Uno don’t despair, later on I’ll, in a way, improve on this. It’s just useful to know the basics and the “hard” way of doing things before learning a better and easier way.

The Arduino Code

In the following snippet of code, I define a lookup table of patterns for each digit from 0 to 9. I’ve done so using binary numbers where a 0 means the segment is lit (my displays are common anode, so I need to pull the segment pin down to ground to light it up). The order of the bits also reflects the segment order as indicated in the drawing above (the right-most bit represents segment “a” and the leftmost bit represents the decimal point).
I’m using PROGMEM to store this lookup in flash memory instead of in the RAM. Don’t worry too much about this since in many cases the RAM used up isn’t of any significance; it’s just 10 bytes, basically.

const uint8_t sevensegment[10] PROGMEM = {
  0b11000000,
  0b11111001,
  0b10100100,
  0b10110000,
  0b10011001,
  0b10010010,
  0b10000010,
  0b11111000,
  0b10000000,
  0b10010000,
};

For my code I define a bunch of variables, some constant, that will be used in the main display algorithm.

  • The variable digits list the pins the display cathodes are connected to.
  • The variable digit will be used by the algorithm to indicate which digit is currently enabled to display.
  • decimalPoint will be used to position the decimal point on the display.
const unsigned char numDigits = 4;
const unsigned char digits[numDigits] = { 38, 39, 40, 41 };

unsigned char digit;
unsigned char decimalPoint = 3;

I will be using a direct write to the Arduino Mega 2560 (or rather ATmega2560) port A which is where I connected the segments to. So firstly I make all 8-bits outputs with the DDRA variable (or should I call it a register).

void setup() {
     ...
  DDRA = 0b11111111;
     ...
}

In the main loop() section of the program, I'll run the code that will continuously refresh the display by running through each seven segment display individually.

Here I increment the digit "counter" variable. It determines which display is currently on. The modulus operator (%) limits the value within the number of digits, (in our this instance the value is limited from 0 to 3).

digit = (digit + 1) % numDigits;

First I blank the currently showing display by writing out all 1's to PORTA.
I then have a for loop to switch off all the digits except the currently selected one.

PORTA = 0b11111111;
for (unsigned char i = 0; i < numDigits; i++) {
  if (i == digit) {
    digitalWrite(digits[i], HIGH);
  } else {
    digitalWrite(digits[i], LOW);
  }
}

The next code might seem a bit tricky, but I'll try to break it down.

  1. pattern will contain the binary pattern for the digit to be displayed.
  2. I first check if the value to be displayed is negative and if we are going to display the last digit. If that is the case, the highest digit will be replaced by a single dash indicating a negative value.
  3. The next bit of code might need to be pulled apart to make sense:
    • pgm_read_byte_near is a special function used to read values from flash memory. Remember when I used PROGMEM in the code above? This is to handle that. If you're not using PROGMEM I'll include a snippet just below this one to show how to assign the right value to the variable pattern.
    • sevensegment being our lookup table defined above. It is actually a pointer pointing to the very first byte, which would be the digit zero's pattern.
    • abs(value) ensures that negative values won't display incorrectly. So -1234 will become 1234.
    • pow(10, digit) will take 10 and raise it to the power of digit which is the number of the currently displayed digit. This "shifts" the number a certain number of places to the right through the division operator. int(...) is required to get the integer value because pow works with real values (that is floating point or double variables). For example if we're displaying the second digit of 1234 we'll now have the value 123.
    • The modulus operator (% 10) basically "cuts off" the parts of the number that's larger than 9, leaving only the single digit (3 in our example) that we want to display in that specific spot on our displays.
unsigned char pattern;
if ((value < 0) && (digit == numDigits - 1))   {
  pattern = 0b10111111;
} else {
  pattern = pgm_read_byte_near(sevensegment + (abs(value) / int(pow(10, digit)) % 10));
}

And here is how to reference the lookup table when not using the flash memory to store it.

 pattern = sevensegment[ abs(value) / int(pow(10, digit)) % 10 ];

That was the hardest part, now for the decimal point, and then to write out the pattern to PORTA which is where our segments are wired up.

if (digit == decimalPoint) {
  pattern &= 0b01111111;
}
PORTA = pattern;

The rest of the loop() { ... } function will do other stuff including changing what value should be that is displayed on the seven segment displays.
Note that this will run as fast as possible which I've found dims the overall display of the value. Some kind of delay might be needed between each individual digit (just to show it for a tad bit longer) to make the display brighter, but not too long as to make the display start to flicker. I've accomplished that in my code using the millis() command to run certain code at different intervals.

Here follows my full code. I haven't explained anything over and above the 7-segment code, so if something doesn't make sense, please leave a comment or send me a message and I'll try to explain it to you.
(click me)

#define ADCREF 4.67

const uint8_t sevensegment[10] PROGMEM = {
  0b11000000,
  0b11111001,
  0b10100100,
  0b10110000,
  0b10011001,
  0b10010010,
  0b10000010,
  0b11111000,
  0b10000000,
  0b10010000,
};

const unsigned char numDigits = 4;
const unsigned char digits[numDigits] = { 38, 39, 40, 41 };

long value;
double aValue;
unsigned char digit;
unsigned char decimalPoint = 3;
char s_buf[20];
unsigned long nextValueTime;
const unsigned long valueInterval = 100;
unsigned long nextDigitTime;
const unsigned long digitInterval = 3;
bool minus = false;

void setup() {
  Serial.begin(9600);

  DDRA = 0b11111111;
  PORTA = 0b00000000;

  for (unsigned char i = 0; i < numDigits; i++) {
    pinMode(digits[i], OUTPUT);
    digitalWrite(digits[i], HIGH);
  }

  nextValueTime = millis() + 2000;
  nextDigitTime = millis() + 1000;

  pinMode(A0, INPUT);
  pinMode(A1, INPUT);
}

void loop() {
  if (millis() >= nextDigitTime) {
    digit = (digit + 1) % numDigits;

    PORTA = 0b11111111;
    for (unsigned char i = 0; i < numDigits; i++) {
      if (i == digit) {
        digitalWrite(digits[i], HIGH);
      } else {
        digitalWrite(digits[i], LOW);
      }
    }
    nextDigitTime = millis() + digitInterval;

    unsigned char pattern;
    if ((value < 0) && (digit == numDigits - 1))   {
      pattern = 0b10111111;
    } else {
      pattern = pgm_read_byte_near(sevensegment + (abs(value) / int(pow(10, digit)) % 10));
    }

    if (digit == decimalPoint) {
      pattern &= 0b01111111;
    }
    PORTA = pattern;
  }

  if (millis() >= nextValueTime) {
    aValue = analogRead(A1);
    value = aValue * ADCREF * 1000 / 1023;
   
    nextValueTime = millis() + valueInterval;
  }
}

Using less pins on your Arduino

Using all those pins on your microcontroller is actually wasteful when an option exists that uses fewer pins. This is very useful on smaller Arduino boards with less I/O. The only drawback is that it adds additional external components to your design. In the next tutorial, I'll build on this one and show how to reduce the number of I/O pins used on your microcontroller or Arduino board.

Facebooktwittergoogle_plusredditpinterestlinkedinmail

1 thought on “Connecting an Arduino to a 7 segment display

Leave a Comment