Chatting as you do about “stuff” on the Raspberry Pi, someone on the #raspberrypi IRC channel mentioned LEDs and driving them. Subsequently someone else on the raspberrypi.org forums was asking about then too… And so another little project was born…
So off to my (now usual!) online store, SKPang and a 4-digit, 7-segment display module found its way into my order. (Actually, since it has decimal points, then technically it’s an 8-digit display, but for some reason they’re always referred to as 7-segment displays!)
There are many strategies for driving 7-segment displays – I’ve chosen to use one of the lower-level ones, doing more in software than in hardware to keep the hardware design simple – so as you can see here, it’s almost as simple as it can get. We have 8 GPIO outputs going to the 8 segments of the display (white + yellow wires) and 4 GPIO outputs going to the individual digits common line. (the black and green wires) This is a common anode display, so the anodes of each of the 8 LEDs in a digit are connected together.
To drive the display, we start by setting all GPIO pins to outputs, set the 4 digit GPIO pins to logic zero and the 8 segment GPIO pins to logic 0. Now we need to light up the display one segment at a time…We do this by selecting the digit and driving it’s common connection to logic 1 (this is the common anode), then drive the segments one at a time to logic 0 to illuminate them, pause for a brief period of time (I’m using 500 μS here), then turn it off by setting it to logic 1 and moving on to the next segment. 4 digits, 8 segments per digit, 500 μS per segment means that the total scan time will be 16mS, or just over 60 times a second. That ought to be fast enough to eliminate flicker.
To increase scan frequency, we could light up more than one segment, but as they are all going via the same resistor then the segments will get dimmer the more we turn on… To prevent this, we could use 8 resistors, however then the combined current of 8 GPIO pins being fed from one GPIO pin would be too much for the Pi’s SoC. We could then use a seprate drive transistor to boost the current, but we’re trying to keep things as simple as possible here…
So, we illuminate each segment of each digit in-turn and if we do it fast enough then our persistence of vision will fool us into thinking they’re all turned on all the time.
So now we have a problem… How do we scan through the segments fast enough while at the same time allowing our program to run? And, what happens if, while scanning the segments, Linux decides to go off and so something else? (Linux is a pre-emptive, multi-tasking, multi-user operating system, after-all!)
This is when we really need a real-time operating system, and while there are some patches to the Linux kernel, they’re not standard in the Pi kernels, and even if they were, they still might not be suitable for use here – to do it “properly” really does require very tight control over the hardware, interrupts, peripherals and so on, and I am not convinced that Linux will give us all that control.
However, there are some simple things we can do to improve things – to the point of making a task like this relatively easy and do-able.
This first thing we can do is tell Linux that we want to have a higher scheduling priority and that we want to be considered for real-time scheduling too. We used the sched_setscheduler() system call to effect this. It’s not perfect – a higher priority process can interrupt your program (and if you want to maintain things like keyboard entry and allowing other programs to run, then you must allow this!) but it’s a good way to give your program a good boost.
The other thing we can do is to use Posix Threads and run what’s effectively a 2nd program concurrently with your main program, and have that 2nd program (although it’s actually a function inside your main program rather than a 2nd program) manage the LED updates.
As long as the LED display routine calls delay() every now and then, then the rest of the system will carry on which the display is kept updating. (It can use other methods to de-schedule itself, but calling a wait function is easiest – I actually call delayMicroseconds() in the display code).
All that remains is to establish a way to communicate between our main program and the display routine and I use a simple global variable here: A string array which I can write to in the main program and read from in the display routine – we don’t need any fancy locking, etc. for something as simple as this.
Even after that, setting the real-time priorities and running the separate display thread, I still see the occasional flicker or glitch on the display. It’s probably no real issue for a display, but imagine if we were driving a stepper motor… One reason I still maintain that Linux is really not the right tool to do that form of “hard” real-time control, but for LED displays? it’s OK.
Any down-sides? Well the overhead of keeping the display updated is between 15 and 20% of the CPU usage on my Pi! So it has to be said that the Raspberry Pi really isn’t the right device to be driving such a display, but it’s good to know that if we had to then we can.
And I’m sure some of the tricks in my program will be useful in other applications. You can find the software here.
Finally another of my most excellent videos: