Controlling A Quad 7 Segment Display Using A Pi Pico, Two Ways
Intro
I have recently been toying with some new…toys I got for my workbench. One of them being quadruple 7-segment display. I had mistakenly purchased the version without a “backpack” that contains an LED driver, so I had to figure how how to drive the device manually.
Here is the data-sheet: https://cdn-shop.adafruit.com/datasheets/1001datasheet.pdf
How this device works is rather simple:
- All LEDs are tied to the same pins. This means the top LED for each number is the same pin to be powered.
- Each panel will only operate if its appropriate COM pin is given a “0” or a LOW signal.
- The center colon has its own separate LED and COM pin.
This means that if you give the COM pin for digit 1 and 2 a LOW signal, they will both display whatever LED pins you pass a HIGH signal to.
Once you have all of the pins connected to an appropriate one on the Pico it is pretty easy to make each pin say the same thing: turn all digit COM pins to 0 and then turn on some LED pins!
For detailed wiring diagrams for how to do this yourself, see the links at the bottom of the post. You can find all of the code used in this project here: https://gist.github.com/turnipsoup/c5ca00fc8157414484f2cbf142032a6b.
Via Direct GPIO
I ran into an issue where I could not get different numbers to properly show on individual displays, only properly if the numbers were all the same.
The issue was that they were still using the “old” data from the pins when switching over and this caused some digits to display a mix of two or more numbers, with some more faded than others. The solution was the clear the cell immediately after drawing it
# Draw each digit
for i in range(4):
draw_number_on_digit(number[i], i)
clear_digit(i)
Next I wanted to count up and display the numbers 0000 through 9999 as a proof of concept that this works. Just letting it run is kind of fast, so I wanted to slow down the counting to see more numbers – but this lead to more flashing! I then realized that I could use one core of the Pi Pico CPU to draw the display and the other to count up.
import _thread
def draw_four_thread() -> None:
"""
Takes a four digit integer and draws it on the display
"""
from machine import Pin
while True:
draw_number_four_digits(display_digit)
if __name__ == "__main__":
#[.....]
# Start the display thread
second_thread = _thread.start_new_thread(draw_four_thread, ())
Not only did this work and allow me to change the pace of the number counting, it also increased the rate at which the CPU was counting, since core_0 could just focus entirely on the variables and core_1 can just focus on displaying the numbers stored in the global variable of display_digit
.
Via Shift Registers
As you may have noticed these “7-segment” displays actually have 8 LEDs, one is the dot. With exactly 8 LEDs to control these seems like the perfect candidate for driving with a shift register. I also wanted to drive the COM pins with a shift register to make it a tad easier (see: cooler) as well.
It turns out that doing it this way is actually conceptually much simpler code-wise, but a tad bit more complex circuit wise. What advantage could this give us, then? We could have a separate circuit for the display that takes fewer pins from the microcontroller, thus leaving us with more GPIO access for other activities.
For this project I used two SN54HC595N shift registers, whose data-sheet is located here: https://www.ti.com/lit/ds/symlink/sn54hc595-sp.pdf
In a similar fashion to the previous stage of the project, I created some maps/arrays of which pins needed to be on to draw each number and iterated over these:
#SNIP
...
numbers = {
"0": "11111100",
"1": "01100000",
"2": "11011010",
"3": "11110010",
"4": "01100110",
"5": "10110110",
"6": "10111110",
"7": "11100000",
"8": "11111110",
"9": "11100110"
}
coms = ["01111111", "10111111", "11011111", "11101111", "11110111"]
#SNIP
...
if __name__ == "__main__":
# Start the display thread
second_thread = _thread.start_new_thread(display_number_thread, ())
count = 0
while True:
display_number = count
count += 1
if count > 9999:
count = 0
time.sleep(0.1)
After some troubleshooting (fiddling with cables) everything ended up working as expected!
Things I Could Have Done Differently (Lessons Learned)
- I could used probably 1/3 of the cables for each project. I did it this way because I wanted the cabling to be conceptually easy (cable up top coordinates with pin up top, etc). In the future I can connect the resistors directly to the chip and forgo and entire “layer” of cables.
- Probably use a slightly bigger breadboard, and not be afraid to use the longer cables even if they are a bit dangly.
- Next time I would try to use the PIO for the Pi Pico and really get some “performance”.
Sources Referenced/Used
- https://learn.adafruit.com/intro-to-rp2040-pio-with-circuitpython/advanced-driving-seven-segment-displays-with-background-writes
- https://learn.adafruit.com/intro-to-rp2040-pio-with-circuitpython/advanced-driving-seven-segment-displays-with-background-writes
- https://forums.raspberrypi.com/viewtopic.php?t=307851
- https://raspi.tv/2015/how-to-drive-a-7-segment-display-directly-on-raspberry-pi-in-python