An Exhaustive Demystification of a 7 Segment Digit

In this article I'm going to attempt to teach you the process of setting up a 7 Segment digit, rather than giving you an absolute example. This will be an exhaustive guide that goes into detail on all the ins and outs of setting up a new (albeit very simple) part, and hopefully you should learn some basic detective skills and core concepts that you can apply elsewhere.

Pin Detective

Once you've wired up your display and powered on your Pi you'll be faced with a very different challenge - writing the software.

For this step you'll need a standard RPi.GPIO setup, so get that out of the way first. It's best you do this in an interactive Python shell for now, start LXTerminal and type:

sudo python

And then type:

import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)

The next step is to put on your detective hat and methodically figure out which pin on your Pi corresponds to which segment on your 7 segment display.

The easiest way to do this is to make a list of all the pins you've used. A Python List is a good place to put a list ( clue's in the name! ), so let's use one:

pins = [21, 20, 16, 12, 26, 19, 13 6]

Lists are great, they let us do thing with groups of similar objects that would otherwise take a lot of repeated code. With this magic list we can turn:

GPIO.setup(21, GPIO.OUT)
GPIO.setup(20, GPIO.OUT)
GPIO.setup(16, GPIO.OUT)
...

Into just:

for pin in pins:
  GPIO.setup(pin, GPIO.OUT)

That's 8 lines of code down to just two, or three if you count the list itself.

Taking it a pin at a time

Now you can probe each pin and check which is which. Let's first turn them all on so you can visually check that every pin is working and connected properly:

for pin in pins:
  GPIO.output(pin, 0)

Now turn them off, one at a time, and make a note of which segment goes out:

GPIO.output(pin[0], 1)

For example, if you see the bottom segment turn off, make a note of it in a new variable like so:

B = 1

Then proceed to the next segment:

GPIO.output(pin[1], 1)

If this one was the bottom left, you'd:

BL = 2

And the next:

GPIO.output(pin[2], 1)

If this was the middle, you'd:

M = 4

Notice the pattern yet? We're giving each pin/segment in our sequence a unique number: 1, 2, 4, 8, 16, 32, 64, 128. These correspond to the bits in a binary number. They also have magical properties, no permutation of these numbers can ever add up to any other number in the sequence. Try it!

Represented as an 8-bit binary number, they'd look like this:

1 = 0b00000001
2 = 0b00000010
4 = 0b00000100
8 = 0b00001000

And so on!

Finish asssigning a variable for each segment use some short but memorable variable names. I use: T, M, B, BL, BR, TL, TR, D.

Drawing Numbers

Now we need to draw numbers with these segments. Let's visualise 0:

 -
| |
| |
 -

What segments are lit up? Add up their variables to get the "mapped" value for 0:

T + TL + TR + B + BL + BR

To make it easier to use these later we'll stick them into a handy list:

numbers = [
  T + TL + T + B + BL + BR # Zero
]

Do the same for the rest of the numbers, styling 9 and 6 as you wish ( to tail or not to tail, that is the question! ).

Now you should have a list, of length 10, with the digits 0 to 9 all mapped out and ready to go. So how would you draw them to the display?

Drawing a number to the 7segment display

def output(number):
  for n in range(8):
    bit = (number >> n) & 1
    GPIO.output(pin[n], not bit)

Well, that's short! But wait, what on earth is happening here?

First off, we step through the values 0 to 7.

At every step we check the nth bit in the map of the number we're drawing. We do this by shifting the whole byte right by n places, and then ANDing it with 1.

So if we have 0b10000100 for our 2nd segment ( n = 1 because the list goes 0, 1, 2 etc ) then:

0b100000100 >> 1 = 0b01000010

A shift works by doing just what it says, shifting all the bits n places in a given direction. In this case we're shifting right, so all the 1s and 0s march right by 1 place. Any places on the left which would be empty are filled with 0s.

So what about AND? AND is a logical operator which follows a very simple set of rules, its "Truth Table" looks like this:

1 and 1 = 1
0 and 0 = 1
1 and 0 = 0
0 and 1 = 0

In fact, you can type these right into Python, try it!

When we do:

0b010000010 & 1

We're ANDing the very last bit ( right most ) with 1, so what's 0 and 1?

0 and 1 = 0

Zero! Or False. So our bit value is 0, and this means our pin will go HIGH since we're saying:

GPIO.output(pins[n], not bit)

not 0 = 1

For posterity, not's truth table looks like this:

not 1 = 0
not 0 = 1

It's a basic inverter, a lot like a transistor in a circuit. You can type these into Python, too, and you'll get True or False as a response.

So, let's step through our loop above and see what it looks like at each step:

# n    byte            bit          output state
#     = byte >> n     = byte & 1   = not bit
 0    0b00100001      1            False
 1    0b00010000      0            True
 2    0b00001000      0            True
 3    0b00000100      0            True
 4    0b00000010      0            True
 5    0b00000001      1            False
 6    0b00000000      0            True
 7    0b00000000      0            True

To output a digit, we just send the map of the digit we want to the output function. This is where the list comes in handy again, the indexes of the list correspond to the digits we've got mapped. numbers[0] contains the map of zero's segments, numbers[9] the map of 9's!

So:

output(numbers[1]) # Display a 1

Or:

for x in range(10):
  output(numbers[x])
  time.sleep(0.5)

Will display the numbers 0 to 9 in sequence!

That's all folks!

Search above to find more great tutorials and guides.

Plasma 2040

Swathe everything in rainbows with this all-in-one, USB-C powered controller for WS2812/Neopixel and APA102/Dotstar addressable LED strip.