As an exercise, I recently threw together a 6-digit 7-segment LED display and wrote a Python program on a Raspberry Pi to display the current time on it.
Sure, it's a bit ugly and not very robust, being a quick prototype built on a Wombat board (see the photo), but the Python code is so simple, compared with what I'd done before with PICs in assembly language, that it's worth taking a look at.
It's 880 lines of PIC assembly language - not for the faint hearted! But it's extensively commented, so if you don't speak PIC assembler (if you'd like to learn, we have tutorials...), you can still get an idea of what each part is doing.
Here's the much simpler RPi / Python code.
We'll start by importing the time and GPIO libraries:
import RPi.GPIO as GPIO
Next we define the pins used to drive each segment and digit (each digit is controlled via a transistor, so that the digits can be lit one at a time, as we'll see below), using lists:
# pin assignments
segments = [22, 19, 18, 4, 21, 26, 20] # A, B, C, D, E, F, G
digits = [16, 13, 5, 23, 17, 27] # 1, 2, 3, 4, 5, 6
This is similar to the "pin assignments" #define statements toward the start of the PIC source code, but much more concise.
We then define the "pattern table", which specifies which segments are lit to display each digit, as a list of tuples:
# pattern table for 7 segment display
# A B C D E F G
pat7seg = [(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, 1, 0, 1, 1)] # '9'
Before using the GPIO library, we need to tell it that we're using the BCM pin numbering scheme:
# configure I/O
Having defined the pins to be used, we can configure them:
# all segment and digit pins outputs, initially low
GPIO.setup(segments, GPIO.OUT, initial=0)
GPIO.setup(digits, GPIO.OUT, initial=0)
This is where Python starts to really shine. Having defined all the segment pins as a list, we can implement "make all the segment pins outputs, and all initially off" as a single, simple statement.
We want the clock to run forever, so we'll use an endless "while True" loop, but we also want to be able to stop the program with e.g. a Ctrl-C, and we want it to clean up after itself when it's stopped, so we'll put the loop inside a "try" construct:
The first thing to do in the loop is to get the current time, so that we can display it, and we can do that very easily, converting it into the 6-digit format we need, all in a single operation, using the 'time.strftime()' function:
# get current local time
ltime = time.strftime("%H%M%S")
Finally, we step through each digit, outputting the pattern corresponding to that digit on the segment pins, and enabling only that digit's display. So, if the time was '10:56:29', and we slowed this right down, you'd see a '1' displayed on the 1st digit, with all the others blank. Then that digit would turn off and the pattern corresponding to a '0' would be output on the segment pins and then the 2nd digit would be enabled and you would see a '0' displayed on the 2nd digit, with everything else blank, and so so on. This called display multiplexing, and by doing it quickly enough (each digit is on for only 1ms here), the human eye is tricked into seeing all the the digits apparently lit:
# display each digit in local time string
for d in range(6):
# lookup each digit in time string and output corresponding pattern
GPIO.output(digits[d], 1) # enable this digit display
time.sleep(0.001) # for 1 ms
This is where Python is at its most powerful. We can implement "output the segment pattern corresponding to a given digit" as a single 'GPIO.output' function call. We're doing the pattern lookup and applying that pattern to all seven segments, all in a single operation. If you take a look at the "Display current time" subroutine in the PIC source code, you can see why these few lines of Python are so amazing - dozens of lines of PIC assembly language are reduced to a couple of function calls...
Ok, so that wasn't quite "finally", above. At the end of the program we catch any interrupt or error that's stopping the program, and clean up:
And that's really it - the whole clock program consists of only 19 Python statements! Quite a bit shorter and easier to understand than 800+ lines of PIC assembly language!
This simple RPi implementation has a couple of huge advantages over the PIC version:
- The Raspberry Pi's clock is derived from Internet time servers and is therefore very accurate
- It will adjust automatically to daylight savings time
But there's a disadvantage right there - the RPi doesn't include a "proper" battery-backed clock, so has to get it's time from the Internet. That's great if you have a Internet connection, but not terribly useful as a clock if you don't. Of course, you can buy clock add-ons for your RPi, but the PIC clock already is a "proper" clock - it doesn't rely on anything external.
Also the comparison is a bit unfair, because the PIC clock has some features that this RPi version doesn't:
- It has an alarm
- The display can be dimmed
- It's battery-powered
- The clock can be put into "sleep mode", where it keeps the time while using very little power
Taking the alarm into account, the PIC source code is really "only" about 600 lines long. Ok, that's still an order of magnitude bigger than the Python version!
More importantly, "low-power" is one of the main reasons (other than cost!) why you'd go to the trouble of using a microcontroller, such as a PIC, to build a battery-powered device such as a travel clock. Sure, you can run a Raspberry Pi from batteries, but try having it keep the time for a whole year on a pair of AA cells. The PIC clock does that with no trouble at all.
There's another "issue" with this simple RPi clock, that highlights why you probably wouldn't want to build a 7-segment LED clock in this way, and why a microcontroller is probably a better solution - the display on the RPi clock occasionally flickers! The PIC version, in comparison, is rock solid and steady.
This happens because the Raspberry Pi is a Linux computer, and Linux likes to do its own thing in the background from time to time, interrupting the smooth flow of our display multiplexing. The flip side is that Linux gives us lots of high-level facilities, such as being able to get the current time from Internet time servers, that we can easily make use of. There's no need to reinvent the wheel - it's all there for you. The flip-flip side is that you no longer have direct control of the hardware - your program is a step removed from it. And that means that it's hard to maintain precise "real-time" timing of things like multiplexed displays, or to respond instantly to an input.
If, on the other hand, you work at a lower level with your program running directly on the hardware, whether using a PIC or an Arduino, you don't get all that support that an operating system gives you, but your program is fully in charge and can control outputs and respond to inputs consistently, and with microsecond resolution.
So, would I build a clock like this with a Raspberry Pi? No way. But am I impressed with what can be done with so little Python code? You bet!