Dual 7-segment Displays Controlled by Potentiometer in CircuitPython - Demonstration of Persistence of Vision




Introduction: Dual 7-segment Displays Controlled by Potentiometer in CircuitPython - Demonstration of Persistence of Vision

About: Retired teacher of computing - started 1967 with FORTRAN IV. I now play with development boards such as Raspberry Pi, Pico, Arduino, micro:bit and Adafruit CircuitPython boards like the Insybitsy M4 and Circui…

This project uses a potentiometer to control the display on a couple of 7-segment LED displays (F5161AH). As the potentiometer knob is turned the displayed number changes in the range 0 to 99. Only one LED is lit at any moment, very briefly, but the eye or a camera does not notice the flicker. This is persistence of vision.

Pressing the button slows down the action and you can see the individual LEDs turning on and off.

I've noticed that there are very few Instructables using CircuitPython so this project uses an Adafruit Itsybitsy M4 development board which runs CircuitPython beautifully. If you want to use a Raspberry Pi, or other microprocessor development board you only need to change the pins and their setup in the script.

Step 1: What We Need for the Project


  • Adafruit Itsybitsy M4 - a small, powerful and inexpensive development board
  • microUSB cable - for programming from PC
  • breadboard (or stripboard and soldering iron)
  • breadboard jumper cables (or connecting wire and solder)
  • a pair of F5161AH 7-segment displays
  • a 10 K Ohm potentiometer
  • a button switch
  • a pair of 330 Ohm resistors


  • Mu Editor - to write the code and program the board

Setting up the Itsybitsy is explained here: https://learn.adafruit.com/introducing-adafruit-it...

The latest version of CircuitPython: https://circuitpython.org/board/itsybitsy_m4_expre...

CircuitPython libraries: https://circuitpython.org/libraries

Mu Editor: https://codewith.mu/

I normally build a project with stripboard after testing out a few ideas out on a breadboard. This means I can keep finished projects ready for demonstrations at 'show & tell' events or to show my students.

Step 2: Building the Circuit

The 7 segment displays each have 10 pins. The centre pins at the top and bottom are connected internally and are common cathodes. That means that all the 8 LEDs, 7 segments and a decimal point, on the display share a common line to a GND connection. This should be via a 330 Ohm resistor to limit the current. Each of the other 8 pins are anodes and are connected directly to output pins on the Itsybitsy.

This means that pin 13 on the Itsybitsy, which controls the centre top segment (A), is connected to pin 7 on BOTH 7-segment displays. Similarly, pin 12 on the Itsybitsy, which controls the top right segment (B), is connected to pin 6 on BOTH 7-segment displays. The rest of the anodes are similarly connected.

The common cathodes are connected, via resistors, to pins D3 and D4 on the Itsybitsy. They are NOT connected to GND, so that we can select the display chips individually by pulling their cathodes low to select the required one..

Step 3: Itsybitsy M4 Pinout

This shows the pins on the Itsybitsy M4 more clearly.

Step 4: Stripboard Connections

This should help your understanding. The left hand block of connections (red ... grey) are the anodes and are connected to pins: D13, D12, D11, D10, D9, D7, Tx and Rx.

In the centre pair of connections; Pin 8, the cathode of the left (tens) display is connected to D4 via a resistor. Pin 3, the cathode of the right (units) display is connected to D3 via a resistor. They are 330 Ohm

Important: All the tracks under the display have been cut. In the 4th track from the right there is a cut on the 12th row from the bottom of the board. It is between thee black and white wires

The right hand connections are:

  • White to A0 from left side of button
  • Green, wiper of the potentiometer to A4
  • Orange to 3.3v and right pin of potentiometer - high end
  • Black to GND: right side of button and left pin on potentiometer - low end

Step 5: Code: Part 1 - Setting Up Digital Pins

This setups up the digital pins - anodes, cathodes and the button. These loop are an efficient method of setting several similar pins.

Step 6: Code: Part 2 - Set Up Analog Pins and Code the Numeric Characters

Only one of the analog pins is used here.

Each line of the table represents a single character. The 7 ones or zeros, left to right, represent the segments A to G. A '1' means the segment is ON and a 0 that the segment is OFF.

Once you have got this project working you might want to extend the table to include a,b,c,d,e and f and modify the code for a hexadecimal display (base 16).

Step 7: Code: Part 3 - Procedures

This is where the real work is done. The LED segment will only light up if the cathode is LOW and the anode HIGH.


  1. split the number into its tens and units components
  2. pull the cathode low on one display to turn it on and then flash the segments one at a time if needed
  3. pull the cathode high to turn off that display
  4. repeat for other display
  5. Do this over and over very quickly so that the observer cannot see the flicker.

Slow things down if button pressed.

Step 8: Code: Part 4 - the Main Loop

In a loop:

  • Read the pot
  • Scale the value to range 0 to 99
  • Display the digits
  • If button pressed increase the delay to show LED flashes
  • Halt if value is zero AND button pressed

Step 9: Code: Download to Save You Time

Who wants to type all that out?

Here is a download to save you time and typos.

1 Hour Challenge

Participated in the
1 Hour Challenge

Be the First to Share


    • Puzzles Challenge

      Puzzles Challenge
    • Rice & Grains Challenge

      Rice & Grains Challenge
    • Lamps Challenge

      Lamps Challenge



    3 years ago

    You have coded it like this:

    1 split the number into its tens and units components
    2 pull the cathode low on one display to turn it on and then flash the segments one at a time if needed
    3 pull the cathode high to turn off that display
    4 repeat for other display
    5 Do this over and over very quickly so that the observer cannot see the flicker.

    But you can do better than that. The way it works now means that one segment is "active" only 1/14 th of the time. But that isn't necessary as you can activate the segments in a round robin way and pull down the common cathode of the display that needs to display it. This way you can have the segments active 1/7 th of the time. (I ignore the DP) Both displays are updated at the "same" time.

    I have made it on a ATTINY2313, see the C code below.

    * zeven_segment_alternatieve_sturing.c
    * Created: 19-6-2019 13:42:16
    * Author : wilko
    * ATTINY2313
    * 8 MHz RC oscillator
    * TIMER0 generates 500 Hz overflow IRQs
    * displays are updated in IRQ routine
    * 2 common cathode 7segment displays
    * rotate segment selection instead of common cathode
    * selection, only one segment (of each display) is
    * switched on at any time, but both displays are
    * updated at the "same" time.

    #include <avr/io.h>
    #include "util/delay.h"
    #include "avr/interrupt.h"

    void initialisatie(void);

    uint8_t digits[2];

    int main(void)
    uint8_t teller = 0;

    while (1)
    if (teller < 99) teller++;
    else teller = 0;

    digits[1] = (uint8_t) teller / 10;
    digits[0] = (uint8_t) teller % 10;


    void initialisatie()
    DDRB |= (1 << PB0) | (1 << PB1); //PB0, PB1 (displays)
    DDRD = 0x7F; //PD0 .. PD6 (segments)

    TCCR0A = 0;
    TCCR0B |= (1 << CS01) | (1 << CS00); //prescaler = 64
    TIMSK |= (1 << TOIE0); //interrupt at overflow (~500 Hz)


    ISR (TIMER0_OVF_vect)
    static uint8_t tabel[10] = {0x7E, 0x30, 0x6D, 0x79, 0x33, 0x5B, 0x5F, 0x70, 0x7F, 0x7B};
    static uint8_t segment = 6;
    static uint8_t temp0 = 0;
    static uint8_t temp1 = 0;

    if (segment < 6) segment++;
    segment = 0;
    temp0 = tabel[digits[0]]; //read 7_segment_char
    temp1 = tabel[digits[1]];
    PORTD = (1 << segment); //switch on selected segment

    PORTB |= (1 << PB0); //display off
    if (temp0 % 2) PORTB &= ~(1 << PB0); //display on when needed

    PORTB |= (1 << PB1);
    if (temp1 % 2) PORTB &= ~(1 << PB1);

    temp0 >>= 1; //shift left 7_segment_char
    temp1 >>= 1;


    Reply 3 years ago

    Thank you for your comments.

    I agree that I could have coded it more efficiently if I just
    wanted to drive the displays flat out and appear as bright as possible.

    I particularly wanted to demonstrate and encourage the use
    of CircuitPython, much easier to understand than the C, using registers and bits rather than pins, if you are new to coding. (CircuitPython fits between Scratch 'blocky' code and more machine dependent languages and is becoming very popular in schools. Full Python is expected to overtake C++ and Java in industrial use in the near future.) I also wanted to be able to slow the code right down so that you can
    see the individual segments turning on and off. The resulting display is bright
    enough when running at full speed.


    Reply 3 years ago

    I'll take another look at CircuitPython...
    "Ordinary" Python I do use every now and then, when making things on a linux server, but CircuitPython is new for me.

    I don't think C is hard to understand though, but then again, I come from hex-displays and keyboards on Z80 boards at school, followed by Pascal on teletype paper terminals, assembly for 6809 at work and assembly for 6502 as a hobby. C is a luxury.

    What I always aim for is efficiency in power consumption and/or in execution time. (And I see it as a challenge to use as small a controller as possible.)


    3 years ago

    Very interesting. and well explained. I am learning and this seems a good project to experiment with. Thanks for sharing


    Reply 3 years ago

    ? I think you meant to reply to somone else?


    Reply 3 years ago

    Ok, thanks. I read part of jessyratfink's Instructable. I will complete it. I use Corel Paint Shop and a Lumix camera. Thank you for the hints! Best regards


    3 years ago

    Very nice. Please, what equipment did you use to take the photos of your
    Intractable? Very sharp photos! Thanks,


    Reply 3 years ago

    Hi Paulo,
    Thanks for your kind comments.
    I used a Canon 700D SLR with a Canon 18 - 135mm EF-S f3.5-5.6 IS lens. I shoot in RAW and process the images with Lightroom and Photoshop. These were hand held.


    Reply 3 years ago

    Thank you! The photos are very sharp and well illuminated. Do you have
    any kind of special lights, light tent or similar? Best


    Reply 3 years ago

    I have lights and a light tent but did not use them here. I have good light from my kitchen and lounge windows and use a white paper reflector to help fill in harsh shadows. I think it is the adjustments in Lightroom that make a difference, once you have a sharp image.
    Have you read jessyratfink's piece on taking photographs?