2-Digit 7-Segment Counter Using Raspberry Pi Pico with Buttons and Buzzer

Ever wanted your project to go tik-tik as it counts? In this fun electronics project, we’ll build a 2-digit up/down auto counter using a Raspberry Pi Pico, two 7-segment displays, buttons for control, and even a buzzer that beeps with every count!

Perfect for school students, beginners, or hobbyists exploring MicroPython, displays, and sound.


🎯 What You’ll Learn

✅ Interface 7-segment displays
✅ Use buttons to control GPIO inputs
✅ Use a buzzer to generate tik-tik sounds
✅ Control timing and logic in MicroPython
✅ Understand multiplexing and PWM


A running auto-counter — meaning:

✅ Press Start-Up → it starts counting automatically every second
✅ Press Start-Down → it counts down every second
✅ Press Stop → it pauses
✅ Press Reset → goes back to 00

🧠 How It Works:

FeatureWhat It Does
mode = "up"Starts incrementing counter automatically every second
mode = "down"Starts decrementing every second
mode = NonePauses (used in Stop or Reset)
utime.ticks_diff()Waits 1000ms (1 sec) before next update
display_digit(...)Uses multiplexing to show both digits smoothly

🧰 Components Needed

ComponentQuantity
Raspberry Pi Pico1
7-Segment Display (Common Cathode)2
Push Buttons4
Passive Buzzer1
220Ω resistors (for segments/buzzer)As needed
Jumper Wires + Breadboard1 set

📌 Button Functions

ButtonActionGPIO
Start UpAuto count upwardGP10
Start DownAuto count downwardGP11
ResetSet counter to 00GP12
StopPause counterGP13

🔌 Buzzer Connection

Buzzer PinConnect To
+GP15 (via 220Ω)
GND

We’re using a passive buzzer so we can control the frequency for different tones.


🔢 Segment to GPIO Pin Mapping

Connect segment a to dp of both 7-segment displays to:

a  → GP0  
b → GP1
c → GP2
d → GP3
e → GP4
f → GP5
g → GP6
dp → GP7

Common cathode pins of:

  • Left display → GP16
  • Right display → GP17

💻 Full MicroPython Code

from machine import Pin, PWM
import utime

# Segment pins a to dp (GPIO0 to GPIO7)
segment_pins = [0, 1, 2, 3, 4, 5, 6, 7]
segments = [Pin(pin, Pin.OUT) for pin in segment_pins]

# Digit control pins (common cathode control)
digit_pins = [Pin(16, Pin.OUT), Pin(17, Pin.OUT)]

# Buzzer using PWM (GPIO15)
buzzer = PWM(Pin(15))

# Buttons connected to GPIOs with internal pull-downs
btn_up = Pin(10, Pin.IN, Pin.PULL_DOWN)
btn_down = Pin(11, Pin.IN, Pin.PULL_DOWN)
btn_reset = Pin(12, Pin.IN, Pin.PULL_DOWN)
btn_stop = Pin(13, Pin.IN, Pin.PULL_DOWN)

# 7-segment digit patterns for 0-9 (common cathode)
digit_patterns = [
(1,1,1,1,1,1,0,0), # 0
(0,1,1,0,0,0,0,0), # 1
(1,1,0,1,1,0,1,0), # 2
(1,1,1,1,0,0,1,0), # 3
(0,1,1,0,0,1,1,0), # 4
(1,0,1,1,0,1,1,0), # 5
(1,0,1,1,1,1,1,0), # 6
(1,1,1,0,0,0,0,0), # 7
(1,1,1,1,1,1,1,0), # 8
(1,1,1,1,0,1,1,0) # 9
]

# Initialize counter and mode
counter = 0
mode = None # 'up', 'down', or None (paused)
last_tick = utime.ticks_ms()

# -------- Buzzer Beep Functions -------- #
def beep_up():
buzzer.freq(1000)
buzzer.duty_u16(1000)
utime.sleep(0.05)
buzzer.duty_u16(0)

def beep_down():
buzzer.freq(400)
buzzer.duty_u16(1000)
utime.sleep(0.1)
buzzer.duty_u16(0)

# -------- Display Function -------- #
def display_digit(pos, digit):
pattern = digit_patterns[digit]
for seg, val in zip(segments, pattern):
seg.value(val)

# Activate only one digit at a time
digit_pins[0].value(1)
digit_pins[1].value(1)
digit_pins[pos].value(0)
utime.sleep_ms(5)
digit_pins[pos].value(1)

# -------- Main Loop -------- #
while True:
# ---- BUTTONS ---- #
if btn_up.value():
mode = "up"
utime.sleep(0.3)

if btn_down.value():
mode = "down"
utime.sleep(0.3)

if btn_reset.value():
counter = 0
mode = None
utime.sleep(0.3)

if btn_stop.value():
mode = None
utime.sleep(0.3)

# ---- COUNTING (every 1s) ---- #
if mode and utime.ticks_diff(utime.ticks_ms(), last_tick) >= 1000:
if mode == "up" and counter < 99:
counter += 1
beep_up()
elif mode == "down" and counter > 0:
counter -= 1
beep_down()
last_tick = utime.ticks_ms()

# ---- SPLIT COUNTER INTO DIGITS ---- #
tens = counter // 10
ones = counter % 10

# ---- DISPLAY BOTH DIGITS ---- #
for _ in range(10): # Repeat to reduce flickering
display_digit(0, tens)
display_digit(1, ones)

🔊 How the Sound Works

ActionSound TypeFrequency
Count UpShort beep1000 Hz
Count DownLower tone400 Hz
Reset/Stop(You can add more if needed)

You can even add unique beep patterns if you like!


🧠 Why Use _?

Normally, in a loop like this:

for i in range(10):
print(i)

The variable i holds values from 0 to 9.

But in your case, you’re not using that loop variable at all — you’re just repeating something 10 times.

So instead of writing:

for i in range(10):
display_digit(0, tens)
display_digit(1, ones)

…and having i sit there unused, we write:

for _ in range(10):
display_digit(0, tens)
display_digit(1, ones)

This tells readers (and Python) clearly:

“We’re just looping for repetition. We don’t need the counter.”


✅ Summary

SyntaxMeaning
for i in range(10)Use i if you need the loop index
for _ in range(10)Use _ if you don’t care about the index

It’s a clean, readable, and recommended practice in Python when you don’t need the loop variable.

🔁 If counter = 47

We split it:

  • tens = 4
  • ones = 7

Now, this line runs:

for _ in range(10):
display_digit(0, tens) # Show 4 on first display
display_digit(1, ones) # Show 7 on second display

🧠 What Happens in display_digit()?

Let’s assume utime.sleep_ms(5) is used:

Loop iteration #1:

  • Display 1 ON: Show 4 for 5 milliseconds
  • Display 2 ON: Show 7 for 5 milliseconds

Loop iteration #2:

  • Repeat the same:
    • 4 on digit 1 → 5ms
    • 7 on digit 2 → 5ms

… and this is done 10 times total:

🧮 10 × (5ms + 5ms) = 100ms

So you’re refreshing the entire display 10 times in 100 milliseconds, which is fast enough that:

  • The human eye sees both digits as being constantly ON
  • No flicker is noticed
  • Both digits appear to light up simultaneously, even though only one is ON at a time

💡 Why This Works

Your eyes can’t notice flicker if the screen is refreshed faster than about 60 times per second (60Hz or ~16ms per frame).
This code is refreshing it much faster (~100Hz), so both digits look perfectly stable.


✅ Summary

StepDuration
Show 4 on digit 15 ms
Show 7 on digit 25 ms
Repeat above steps 10×Total ≈ 100 ms

🔁 And this cycle keeps running forever, so it looks like both digits are always glowing.

✅ Wiring Your Button with Pin.PULL_DOWN

Since your MicroPython code uses:

button_up = Pin(10, Pin.IN, Pin.PULL_DOWN)

That means:

  • The GPIO pin (GP10) will read 0 (LOW) when the button is not pressed.
  • When you press the button, you want it to connect to 3.3V, making it HIGH (1).

✅ So your button wiring should be:

Button PinConnects To
One side3.3V (Pin 36 on Pico) ✅
Other sideGPIO10 (button input pin) ✅

📌 No external resistor is needed because you’re using the internal pull-down resistor.


🔁 What Happens:

  • Button not pressed:
    GPIO10 is pulled LOW (0) by the internal pull-down.
  • Button pressed:
    3.3V flows into GPIO10 → reads HIGH (1) → your code detects button press.

⚠️ Don’t do this:

  • ❌ Don’t connect one side to GND and the other to GPIO10 when using Pin.PULL_DOWN, because that would never go HIGH.
  • ❌ Don’t connect 3.3V directly to GPIO10 without a button — you must go through the button!

✅ Conclusion

Your setup:

One pin of button to 3.3V (Pin 36)
Other pin to GPIO10

✔️ Correct!

Main Program Loop

while True:

This loop keeps checking buttons and refreshing the display.

🧠 Code Explained in Simple Terms


🧱 1. Import necessary modules

pythonCopyEditfrom machine import Pin, PWM
import utime
  • machine: lets you control Pico’s hardware (GPIO, PWM, etc.).
  • Pin: controls GPIO pins.
  • PWM: controls the buzzer (to create tones).
  • utime: for delays, timers, etc.

💡 2. 7-Segment display setup

pythonCopyEditsegment_pins = [0, 1, 2, 3, 4, 5, 6, 7]
segments = [Pin(pin, Pin.OUT) for pin in segment_pins]
  • These GPIO pins are connected to segments a to dp of the 7-segment displays.
  • Each segment is set as an output pin.
pythonCopyEditdigit_pins = [Pin(16, Pin.OUT), Pin(17, Pin.OUT)]
  • These two pins control which digit to light up (common cathode).
  • One is for the tens digit, the other for the ones digit.

🔊 3. Buzzer setup

pythonCopyEditbuzzer = PWM(Pin(15))
  • Buzzer is connected to GPIO15 and controlled using PWM to make different sounds.

🎮 4. Button setup

pythonCopyEditbtn_up = Pin(10, Pin.IN, Pin.PULL_DOWN)
btn_down = Pin(11, Pin.IN, Pin.PULL_DOWN)
btn_reset = Pin(12, Pin.IN, Pin.PULL_DOWN)
btn_stop = Pin(13, Pin.IN, Pin.PULL_DOWN)
  • Four buttons:
    • UP ➜ count up
    • DOWN ➜ count down
    • RESET ➜ set counter to 0 and stop
    • STOP ➜ pause the counter
  • Each is connected with an internal pull-down resistor, so it reads 0 by default.

🔢 5. Digit patterns for displaying 0–9

pythonCopyEditdigit_patterns = [
    (1,1,1,1,1,1,0,0),  # 0
    (0,1,1,0,0,0,0,0),  # 1
    ...
    (1,1,1,1,0,1,1,0)   # 9
]
  • Each tuple controls the 8 segments (a to dp) of the display.
  • For example, 1 means ON (segment glows), 0 means OFF.

🔄 6. Counter setup

pythonCopyEditcounter = 0
mode = None
last_tick = utime.ticks_ms()
  • counter: stores the current number (0 to 99)
  • mode: "up", "down", or None (pause)
  • last_tick: stores the last time we counted 1 second

🎵 7. Beep sound functions

pythonCopyEditdef beep_up():
    buzzer.freq(1000)
    buzzer.duty_u16(1000)
    utime.sleep(0.05)
    buzzer.duty_u16(0)
  • Makes a high-pitch beep for UP counting.
pythonCopyEditdef beep_down():
    buzzer.freq(400)
    buzzer.duty_u16(1000)
    utime.sleep(0.1)
    buzzer.duty_u16(0)
  • Makes a lower-pitch beep for DOWN counting.

🔢 8. Display a single digit on a 7-segment

pythonCopyEditdef display_digit(pos, digit):
    pattern = digit_patterns[digit]
    for seg, val in zip(segments, pattern):
        seg.value(val)

    digit_pins[0].value(1)
    digit_pins[1].value(1)
    digit_pins[pos].value(0)
    utime.sleep_ms(5)
    digit_pins[pos].value(1)
  • pos: 0 = tens, 1 = ones.
  • Lights up segments for that digit using the pattern.
  • Activates the right display by pulling its pin LOW (common cathode).
  • Then waits 5 ms and turns it off again.
  • This is done repeatedly (many times per second) to create the illusion of both digits showing at once (called multiplexing).

🔁 9. Main loop

pythonCopyEditwhile True:

Runs forever.


🟢 Detect button presses

pythonCopyEditif btn_up.value():
    mode = "up"
    utime.sleep(0.3)
  • If UP button is pressed, set mode to "up", so counter starts increasing.
pythonCopyEditif btn_down.value():
    mode = "down"
    utime.sleep(0.3)
  • DOWN mode activated.
pythonCopyEditif btn_reset.value():
    counter = 0
    mode = None
    utime.sleep(0.3)
  • RESET ➜ counter goes back to 0 and stops.
pythonCopyEditif btn_stop.value():
    mode = None
    utime.sleep(0.3)
  • STOP ➜ pauses counting.

⏱ Update counter every 1 second

pythonCopyEditif mode and utime.ticks_diff(utime.ticks_ms(), last_tick) >= 1000:
  • Only update if a mode is active and 1 second has passed.
pythonCopyEditif mode == "up" and counter < 99:
    counter += 1
    beep_up()
  • Increase counter and beep, only up to 99
pythonCopyEditelif mode == "down" and counter > 0:
    counter -= 1
    beep_down()
  • Decrease counter and beep, only down to 0
pythonCopyEditlast_tick = utime.ticks_ms()
  • Reset the tick timer.

🔢 Split digits and display

pythonCopyEdittens = counter // 10
ones = counter % 10

for _ in range(10):
    display_digit(0, tens)
    display_digit(1, ones)
  • Break the counter into tens and ones digits.
  • Show each digit rapidly and repeatedly to make the display appear steady.

✅ Summary

This program turns your Raspberry Pi Pico into a 2-digit timer with:

  • Two 7-segment displays
  • Control using buttons (UP, DOWN, RESET, STOP)
  • Beep feedback for user interaction
  • Multiplexing for clean display

🧪 Try These Experiments

  • Add a buzzer beep when Reset is pressed.
  • Add auto stop at 99 or 0.
  • Use an OLED display instead of 7-segment.
  • Add LEDs for each button press.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *