The following example shows a counter on a 4-digit 7-segment LED without a separate driver chip. Don't forget you need CircuitPython 7.3 or later.

The main program just counts up forever, looping back to 0000 after 9999.

This example is designed for a Raspberry Pi Pico and bare LED display. For simplicity, it is wired without any current limiting resistors, instead relying on a combination of the RP2040's pin drive strength and the 1/4 duty cycle to limit LED current to an acceptable level, and longevity of the display was not a priority. Also, we're feeling punk today!

Before integrating a variant of this example code in a project, evaluate whether the design needs to add current-limiting resistors.

Parts

For this example, you need additional parts:

Premium Male/Male Jumper Wires - 20 x 6 (150mm) folded over
These Male/Male Jumper Wires are handy for making wire harnesses or jumpering between headers on PCB's. These premium jumper wires are 6" (150mm) long and come in a...
$1.95
In Stock
Angled shot of full sized breadboard.
This is a 'full-size' premium quality breadboard, 830 tie points. Good for small and medium projects. It's 2.2" x 7" (5.5 cm x 17 cm) with a standard double-strip...
$5.95
In Stock
White 7-segment clock display with all segments lit
Design a clock, timer or counter into your next project using our pretty 4-digit seven-segment display. These bright crisp displays are good for adding numeric output. Besides the four...
Out of Stock

Wiring

Place the Pico and the 7-segment display on the breadboard, noting the orientation of the decimal points.

Make the following connections:

  • Pico GP15 to LED matrix 1 (E SEG)
  • Pico GP14 to LED matrix 2 (D SEG)
  • Pico GP13 to LED matrix 3 (DP SEG)
  • Pico GP12 to LED matrix 4 (C SEG)
  • Pico GP11 to LED matrix 5 (G SEG)
  • Pico GP10 to LED matrix 6 (COM4)
  • Pico GP9 to LED matrix 7 (COLON COM)
  • Pico GP22 to LED matrix 8 (COLON SEG)
  • Pico GP21 to LED matrix 9 (B SEG)
  • Pico GP20 to LED matrix 10 (COM3)
  • Pico GP19 to LED matrix 11 (COM2)
  • Pico GP18 to LED matrix 12 (F SEG)
  • Pico GP17 to LED matrix 13 (A SEG)
  • Pico GP16 to LED matrix 14 (COM1) 

Next, load the code below on your Raspberry Pi Pico's CIRCUITPY drive:

# SPDX-FileCopyrightText: 2022 Jeff Epler, written for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""Drive a 7-segment display entirely from the PIO peripheral

By updating the buffer being written to the display, the shown digits can be changed.

The main program just counts up, looping back to 0000 after 9999.

This example is designed for a Raspberry Pi Pico and bare LED display. For
simplicity, it is wired without any current limiting resistors, instead relying
on a combination of the RP2040's pin drive strength and the 1/4 duty cycle to
limit LED current to an acceptable level, and longevity of the display was not
a priority.

Before integrating a variant of this example code in a project, evaluate
whether your design needs to add current-limiting resistors.

https://www.adafruit.com/product/4864
https://www.adafruit.com/product/865

Wiring:
 * Pico GP15 to LED matrix 1 (E SEG)
 * Pico GP14 to LED matrix 2 (D SEG)
 * Pico GP13 to LED matrix 3 (DP SEG)
 * Pico GP12 to LED matrix 4 (C SEG)
 * Pico GP11 to LED matrix 5 (G SEG)
 * Pico GP10 to LED matrix 6 (COM4)
 * Pico GP9 to LED matrix 7 (COLON COM)
 * Pico GP22 to LED matrix 8 (COLON SEG)
 * Pico GP21 to LED matrix 9 (B SEG)
 * Pico GP20 to LED matrix 10 (COM3)
 * Pico GP19 to LED matrix 11 (COM2)
 * Pico GP18 to LED matrix 12 (F SEG)
 * Pico GP17 to LED matrix 13 (A SEG)
 * Pico GP16 to LED matrix 14 (COM1)
"""

import array
import time
import board
import rp2pio
import adafruit_pioasm

_program = adafruit_pioasm.Program(
    """
    out pins, 14       ; set the pins to their new state
    """
)

# Display Pins 1-7 are GP 15-9
# Display Pins 8-12 are GP 22-16
COM1_WT = 1 << 7
COM2_WT = 1 << 10
COM3_WT = 1 << 11
COM4_WT = 1 << 1
COMC_WT = 1 << 0

SEGA_WT = 1 << 8
SEGB_WT = 1 << 12
SEGC_WT = 1 << 3
SEGD_WT = 1 << 5
SEGE_WT = 1 << 6
SEGF_WT = 1 << 9
SEGG_WT = 1 << 2

SEGDP_WT = 1 << 4
SEGCOL_WT = 1 << 13

ALL_COM = COM1_WT | COM2_WT | COM3_WT | COM4_WT | COMC_WT

SEG_WT = [
    SEGA_WT,
    SEGB_WT,
    SEGC_WT,
    SEGD_WT,
    SEGE_WT,
    SEGF_WT,
    SEGG_WT,
    SEGDP_WT,
    SEGCOL_WT,
]
COM_WT = [COM1_WT, COM2_WT, COM3_WT, COM4_WT, COMC_WT]

DIGITS = [
    0b0111111,  # 0
    0b0000110,  # 1
    0b1011011,  # 2
    0b1001111,  # 3
    0b1100110,  # 4
    0b1101101,  # 5
    0b1111100,  # 6
    0b0000111,  # 7
    0b1111111,  # 8
    0b1101111,  # 9
]


def make_digit_wt(v):
    val = ALL_COM
    seg = DIGITS[v]
    for i in range(8):
        if seg & (1 << i):
            val |= SEG_WT[i]
    return val


DIGITS_WT = [make_digit_wt(i) for i in range(10)]


class SMSevenSegment:
    def __init__(self, first_pin=board.GP9):
        self._buf = array.array("H", (DIGITS_WT[0] & ~COM_WT[i] for i in range(4)))
        self._sm = rp2pio.StateMachine(
            _program.assembled,
            frequency=2000,
            first_out_pin=first_pin,
            out_pin_count=14,
            auto_pull=True,
            pull_threshold=14,
            **_program.pio_kwargs,
        )
        self._sm.background_write(loop=self._buf)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.deinit()

    def deinit(self):
        self._sm.deinit()

    def __setitem__(self, i, v):
        if v is None:
            self._buf[i] = 0
        else:
            self._buf[i] = DIGITS_WT[v] & ~COM_WT[i]

    def set_number(self, number):
        for j in range(4):
            self[3 - j] = number % 10
            number //= 10


def count(start=0):
    val = start
    while True:
        yield val
        val += 1


def main():
    with SMSevenSegment(board.GP9) as s:
        for i in count():
            s.set_number(i)
            time.sleep(0.05)


if __name__ == "__main__":
    main()

Let's focus on some specific parts of the program. First, take a look at the PIO program itself. This program simply reads each value then sends it to the output pins:

_program = adafruit_pioasm.Program(
    """
    out pins, 14       ; set the pins to their new state                        
    """                                                                         
)

To display a particular digit on a particular position of the display,

  • Each SEG pin must be set HIGH if it is to be turned on, and LOW otherwise
  • Each COM pin must be set LOW if the corresponding digit is to be turned on, and HIGH otherwise

To display a different digit in each position, the program needs to repeatedly loop through the correct pin values for each digit.

Next, some constants are built up to describe the function of each pin, and finally the list DIGITS_WT is created with the correct bits set to show a particular digit, 0 to 9, plus the bits for each COM (common) pin are turned on:

def make_digit_wt(v):
    val = ALL_COM
    seg = DIGITS[v]
    for i in range(8):
        if seg & (1 << i):
            val |= SEG_WT[i]
    return val

DIGITS_WT = [make_digit_wt(i) for i in range(10)]

A SMSevenSegment object continually scans out the digits to the display. It operates at a clock speed of 2000Hz, and it displays each digit for 1 cycle at a time, so the refresh rate of the display is 500kHz. In almost all situations, this is enough for the digit to appear 'solid' to the human eye.

class SMSevenSegment:
    def __init__(self, first_pin=board.GP9):
        self._buf = array.array("H", (DIGITS_WT[0] & ~COM_WT[i] for i in range(4)))
        self._sm = rp2pio.StateMachine(
            _program.assembled,
            frequency=2000,
            first_out_pin=first_pin,
            out_pin_count=14,
            auto_pull=True,
            pull_threshold=14,
            **_program.pio_kwargs,
        )
        self._sm.background_write(loop=self._buf)

To display a particular digit at a particular position, we take the DIGITS_WT for that value and turn OFF the single COM bit for the position in question. A 4-digit number can be displayed by putting each of its digits in one of the positions. Updating the number from right to left makes it easy to use division and modulo to compute each digit:

def __setitem__(self, i, v):
    if v is None:
        self._buf[i] = 0
    else:
        self._buf[i] = DIGITS_WT[v] & ~COM_WT[i]
        
def set_number(self, number):
    for j in range(4):
        self[3 - j] = number % 10
        number //= 10

Ready for more? Bring some color into your life with PIO and NeoPixels: writing to NeoPixels LEDS "in the background" while your CircuitPython code continues to run.

This guide was first published on Mar 03, 2021. It was last updated on 2022-05-10 21:59:48 -0400.

This page (Advanced: Driving 7-segment displays with Background Writes) was last updated on May 24, 2022.

Text editor powered by tinymce.