element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • About Us
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
7-Segment Display
  • Challenges & Projects
  • Project14
  • 7-Segment Display
  • More
  • Cancel
7-Segment Display
Blog The Crazy Countdown Timer Revisited
  • Blog
  • Forum
  • Documents
  • Polls
  • Files
  • Events
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: ntewinkel
  • Date Created: 15 Jun 2022 12:07 AM Date Created
  • Views 42161 views
  • Likes 10 likes
  • Comments 4 comments
  • clock
  • 7segmentdisplaych
  • 7-segment
  • raspberry pi pico
  • seven segment display
  • 7segmentdisplaysch
  • xl-star
  • pi pico
Related
Recommended

The Crazy Countdown Timer Revisited

ntewinkel
ntewinkel
15 Jun 2022

Over the last few weeks (months maybe?) I've been playing around with 7-segment displays and an old clock display I had in my pile of hoarded things.

My most recent blog post in the series was a throwback to the 1980's: The VCR Emulator

But now here comes the moment we've all been waiting for (right?) ---> this is it! the finale of all of these seemingly meaningless blog posts... the project I was aiming for! It's a Crazy Countdown Timer!!
Complete with wires to cut! kinda like those stereotypical movie time bombs where the hero of the story has to cut the correct wire to save the world... that might have been a 1980s thing too Thinking In any case, for reasons of political correctness and to avoid calls from important government people, I gave this project a toned-down PG kind of title.

Now, those of you who have been paying very careful attention for the last dozen years might have noticed a similarity to a project I posted about, right here on element14, nearly exactly 11 years ago!
In fact, this was MY VERY FIRST PROJECT on element14 !! Woot!!, for that year's "Summer of Design" contest, using the XL-Star board! How cool is that? It's also the same clock display module Smile

Back then I was just getting back into electronics, and I think I had just purchased a few Texas Instruments MSP430 boards, which were surprisingly just $4.30 (Canadian even, if I recall correctly) with free shipping to boot, but I digress (as I so often do), and I had just started playing around with firmware. Theirs was a fairly complicated system at the time, with the IDE being in Eclipse still. I think the XL-Star was similarly complex to program -- check out the source code! Scream cat

I didn't win any prize for my countdown timer project, but because I was so new to the electronics hobby and dev boards, I had been asking a whole lot of questions that summer, and that led me to have more posts than most people I guess Sweat smile and as a result I won the very first "member of the month" prize! That turned out to be somewhat life-changing, as the care package element14 sent me included a few different dev boards, including my very first Arduino-compatible board. That made dev board programming so much easier, and made the hobby much more enjoyable for me. Ever since then I've been mostly Arduino based for my hobby projects.

So this project was a really cool throw-back for me. What was also very interesting to see is how much easier it is to do a nearly identical project today, using Python on the Pi Pico. The new version doesn't include a bump sensor, but even that should be fairly easy to add if desired.

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

In case you're curious, this is what happens when you cut the yellow wire:

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

The setup for this project is the same as my last few blog posts: The VCR Emulator and The Countdown Timer.
The biggest change is that I added 3 wires, which are set up as switches that are "on" by default and "off" when cut (using the built-in pull-down resistors). The source code gives the pins to connect those wires to - GP 7, 8, and 9.
I also changed the timer to just count ticks instead of trying to be precise for time - this allows us to more easily speed up the count-down clock when the wrong wire is cut.

As was hinted at in my Pi Pico Sound blog post, I also added a beeper for when the timer runs out.

Here is the source code:

#
# The Crazy Timer!
#
# The timer starts at 3 minutes
#
#

from machine import Pin, Timer, PWM
import time


# For my clock display, the common wires connects to pins that must be set HIGH to enable each position
#  -> Note that this means we have to turn a pin "off" to light the segment LED, and "on" to turn it off
#
# You can test the display by using a small 3.3v button battery (CR2032 seems cheap and plentiful)
# Hopefully the display you have will have a model number you can look up to find the pinout.

# Define the segment GPIO connections
# hook up the segments as per the defined constants below
# Use a current limiting resistor for each segment (you'll need 7!)
# The resistors are also necessary to keep a constant brightness on the display
SEG_A_PIN = 13
SEG_B_PIN = 19
SEG_C_PIN = 17
SEG_D_PIN = 16
SEG_E_PIN = 12  # oops, I put the display too close to the Pi Pico, so pin 15 is covered.
SEG_F_PIN = 14
SEG_G_PIN = 18

# The clock display has 4 digit positions
# I'm calling them positions, as it could be hr:mm or mm:ss
POSITION_1 = 10
POSITION_2 = 11
POSITION_3 = 20
POSITION_4 = 21

DIVIDER_COLON = 22

# Python allows us to define global variables anywhere all willy-nilly,
#   but for clarity lets define them here at the top like good little programmers
#   The type is here just for clarity too - Python allows us to change it at any time
DIGITS :[[Pin]] = []
BLANK_DIGIT : [Pin] = []
POSITIONS : [Pin] = []

# Add the crazy wires
STOP_WIRE_PIN = 7 # green wire, stops the timer
ZERO_WIRE_PIN = 8 # yellow wire, sets time down to 1 second
PANIC_WIRE_PIN = 9 # red wire, speeds up the timer

stop_wire : Pin
zero_wire : Pin
panic_wire : Pin

BUZZER_PIN = 6 # Piezo buzzer + is connected to GP6, - is connected to the GND right beside GP6
buzzer : PWM

# time.time() returns float number of seconds since epoch
MINUTE : float = 60
HOUR = MINUTE * 60
TOTAL_TIME = 3 * MINUTE

tickerTimer = Timer(-1) # For more info on Timers see https://docs.micropython.org/en/latest/library/machine.Timer.html
time_left = TOTAL_TIME
ticksCounted = 0
colon_visible = False
display_buffer : [[Pin]] = [BLANK_DIGIT, BLANK_DIGIT, BLANK_DIGIT, BLANK_DIGIT]  # this will hold the 4 digits currently being displayed - the display needs continuous refreshing in order to work.

flashing = False
vcr_mode = False

timer_stopped = False
timer_panic = False
timer_zero = False
music_played = False

def setup():
    # Define each segment
    SEG_A = Pin(SEG_A_PIN, Pin.OUT)
    SEG_B = Pin(SEG_B_PIN, Pin.OUT)
    SEG_C = Pin(SEG_C_PIN, Pin.OUT)
    SEG_D = Pin(SEG_D_PIN, Pin.OUT)
    SEG_E = Pin(SEG_E_PIN, Pin.OUT)
    SEG_F = Pin(SEG_F_PIN, Pin.OUT)
    SEG_G = Pin(SEG_G_PIN, Pin.OUT)
    
    # Define which segments make up each digit
    DIGIT_0 = [SEG_A, SEG_B, SEG_C, SEG_D, SEG_E, SEG_F       ]
    DIGIT_1 = [       SEG_B, SEG_C                            ]
    DIGIT_2 = [SEG_A, SEG_B,        SEG_D, SEG_E,        SEG_G]
    DIGIT_3 = [SEG_A, SEG_B, SEG_C, SEG_D,               SEG_G]
    DIGIT_4 = [       SEG_B, SEG_C,               SEG_F, SEG_G]
    DIGIT_5 = [SEG_A,        SEG_C, SEG_D,        SEG_F, SEG_G]
    DIGIT_6 = [SEG_A,        SEG_C, SEG_D, SEG_E, SEG_F, SEG_G]
    DIGIT_7 = [SEG_A, SEG_B, SEG_C                            ]
    DIGIT_8 = [SEG_A, SEG_B, SEG_C, SEG_D, SEG_E, SEG_F, SEG_G]
    DIGIT_9 = [SEG_A, SEG_B, SEG_C, SEG_D,        SEG_F, SEG_G]
    
    # Note that we are not limited to decimal digits. We could continue to add A through F for hexadecimal
    
    POS_1 = Pin(POSITION_1, Pin.OUT)
    POS_2 = Pin(POSITION_2, Pin.OUT)
    POS_3 = Pin(POSITION_3, Pin.OUT)
    POS_4 = Pin(POSITION_4, Pin.OUT)

    global divider_colon
    divider_colon = Pin(DIVIDER_COLON, Pin.OUT)
    
    global DIGITS
    DIGITS = [DIGIT_0, DIGIT_1, DIGIT_2, DIGIT_3, DIGIT_4, DIGIT_5, DIGIT_6, DIGIT_7, DIGIT_8, DIGIT_9, BLANK_DIGIT]

    global POSITIONS
    POSITIONS = [POS_1, POS_2, POS_3, POS_4]
    
    global stop_wire, zero_wire, panic_wire
    stop_wire = Pin(STOP_WIRE_PIN, Pin.IN, Pin.PULL_DOWN)
    zero_wire = Pin(ZERO_WIRE_PIN, Pin.IN, Pin.PULL_DOWN)
    panic_wire = Pin(PANIC_WIRE_PIN, Pin.IN, Pin.PULL_DOWN)
    
    global buzzer
    buzzer = PWM(Pin(BUZZER_PIN, Pin.OUT))
    soundOff()
    
    displayOff()


def showPosition(position):
    positionsOff()
    position.on()

def displayDigit(digit):
    #start by turning off all the segments
    segmentsOff()
    
    for segment in digit:
        segment.off() # gpio "off" turns on the LED

def positionsOff():
    for pos in POSITIONS:
        pos.off()

def segmentsOff():
    for segment in DIGITS[8]:
        segment.on() # gpio "on" turns off the LED

def displayOff():
    # turn off all the digit positions
    positionsOff()
    # turn off all the segments
    segmentsOff()

def timeTick(timer):
    global time_left, ticksCounted, TOTAL_TIME, colon_visible, flashing
    
    if timer_stopped :
        colon_visible = True
        return

    colon_visible =  not colon_visible
    
    if vcr_mode or time_left == 0 : return
    
    # For this Crazy Timer we will now be counting ticks instead of using the time() function.
    # This allows us to increase the countdown speed when the wrong wire is disconnected
    # We are not concerned about time accuracy for this specific project.
    ticksCounted = ticksCounted + 1
        
    time_left : int = TOTAL_TIME - (ticksCounted // 2)
    
    if time_left <= 0 or timer_zero :
        time_left = 0
        resetTickerTimer(2)
        flashing = True
        
    showTime(time_left)

def startSecondsTicker():
    global tickerTimer
    tickerTimer.init(freq=2, mode=Timer.PERIODIC, callback=timeTick)

def resetTickerTimer(frequency) :
    global tickerTimer
    tickerTimer.deinit()
    tickerTimer.init(freq=frequency, mode=Timer.PERIODIC, callback=timeTick)

def displayRefresh():
    global display_buffer, POSITIONS, colon_visible, divider_colon, flashing
    
    if not colon_visible and flashing:
        displayOff()
        divider_colon.on()  # turns the LEDs off
        return
    
    led_on_time = 0.0025
    # too much on time will cause flickering - the display must keep each position lit up just long enough to repeat it again fast enough to fool the eye.
    
    for index, position in enumerate(POSITIONS) :
        showPosition(position) 
        displayDigit(display_buffer[index])
        time.sleep(led_on_time)
        # You can use a bigger delay here for debugging purposes, to make it easier to see the digits being shown in turn.
        
    displayOff() # otherwise it will linger on the last digit, making it brighter than the rest
    
    if colon_visible :
        divider_colon.off() # turns the LEDs on
    
    time.sleep(led_on_time)
    divider_colon.on()  # turns the LEDs off

def showTwelve():
    display_buffer[0] = DIGITS[1]
    display_buffer[1] = DIGITS[2]
    display_buffer[2] = DIGITS[0]
    display_buffer[3] = DIGITS[0]

def showTime(total_seconds):
    global display_buffer

    #print("show time total seconds = ", total_seconds)
    
    if total_seconds <= 0 :
        display_buffer[0] = DIGITS[0]
        display_buffer[1] = DIGITS[0]
        display_buffer[2] = DIGITS[0]
        display_buffer[3] = DIGITS[0]
        return

    # convert seconds to hours, minutes, and seconds
    hours = total_seconds // 3600  # the // is "floor division" which avoids fractions
    leftover = total_seconds % 3600
    minutes = leftover // 60
    seconds = leftover % 60
    # print("time = ", hours, ":", minutes, ":", seconds)
    
    if hours > 0 :
        first_number = hours
        second_number = minutes
    else :
        first_number = minutes
        second_number = seconds

    if hours > 99 :
        display_buffer[0] = DIGITS[8]
        display_buffer[1] = DIGITS[8]
        display_buffer[2] = DIGITS[8]
        display_buffer[3] = DIGITS[8]
    else :
        if first_number > 9 :
            display_buffer[0] = DIGITS[first_number // 10]
        else :
            display_buffer[0] = BLANK_DIGIT
        
        display_buffer[1] = DIGITS[first_number % 10]
        display_buffer[2] = DIGITS[second_number // 10]
        display_buffer[3] = DIGITS[second_number % 10]
        

def soundOff() :
    global buzzer
    buzzer.duty_u16(0) # loudness set to 0 = sound off

    
def playNote(frequency, duration, pause) :
    global buzzer
    buzzer.duty_u16(5000)  # adjust loudness: smaller number is quieter.
    buzzer.freq(frequency)
    time.sleep(duration)
    buzzer.duty_u16(0) # loudness set to 0 = sound off
    time.sleep(pause)


def playMusic() :    
    #notes = [440, 494, 523, 587, 659, 698, 784]
    #notes = [440, 523, 659, 784]
    notes = [523, 440, 494, 523, 440, 494, 523]

    for note in notes :
        playNote(note, 0.2, 0.01)


# Start main code
setup()
startSecondsTicker()

if vcr_mode :
    flashing = True
    showTwelve()
else :
    showTime(TOTAL_TIME)

# Using a Timer to refresh the display seems to cause it to hang after a while
# but using this loop we can go nuts refreshing the display with all the spare clock cycles

while True:
    displayRefresh()
    
    # check the wires - the following checks if they were disconnected (and pull-down makes them LOW)
    if not stop_wire.value() :
        timer_stopped = True
    elif not zero_wire.value() :
        timer_zero = True
    elif not panic_wire.value() :
        if not timer_panic : # only do this once
            timer_panic = True
            resetTickerTimer(20)
 
    if time_left == 0 and not music_played :
        music_played = True
        playMusic()


    
    

Just for fun, here's a snippet of the source code from the original project (so you can see how much nicer the new Python code looks!). The full source code is in the zip file that was included in the original project.

        TPM1SC = 0;       // Stop the timer  
        TPM1SC_TOF = 0;   // Clear the TOF flag          
        TPM1C0SC = 0;     // Configure the timer  
        TPM1MOD = count;  // the number of 32 microsecond increments to wait
        TPM1CNT = 0;
        TPM1SC = 0x0f;    // Restart the timer: CLKS = 01 (Use BUSCLK), PS=111 (Divide-by-128)
                          // Frequency = fbus / 128, so period = 128 / 4 MHz = 32 microseconds 
        
        /* Keep showing the display while waiting for timer to expire */  
        while (!_TPM1SC.Bits.TOF) {

          // Channel select LEDs are being used to indicate status of the crazy wires.
          // when the wires are connected to GND, PTE2,3, and 4 are at 0
          // When wires are disconnected from GND, they go to 1

          if ( !PTED_PTED2 ) {
            PTAD_PTAD5 = 0;   // "FreeFall" LED on
            timer_stopped = 0;
          }
          else {
            PTAD_PTAD5 = 1;   // off -  BLUE wire was disconnected = stop the timer
            timer_stopped = 1;
          }
       
          
          if ( !PTED_PTED3 ) {
            PTAD_PTAD6 = 0;   // "Transient" LED on
          }
          else {
            PTAD_PTAD6 = 1;   // off - YELLOW wire was disconnected = set time to 0
            hours = 0;
            minutes = 0;
            seconds = 0;
          }
          
          if ( !PTED_PTED4 ) {
            PTAD_PTAD7 = 0;   // "orientation" LED on
          }
          else {
            PTAD_PTAD7 = 1;   // off - RED wire was disconnected = panic
            flash = 1;
          }

Edit June 16th:
javagoza Had a really fun idea of randomizing the function of the wires.

Here's the code that can be used for that - simply replace the existing 3 lines of code that define the action pins (the definition of STOP_WIRE_PIN, ZERO_WIRE_PIN, and PANIC_WIRE_PIN).

import random

def shuffleArray(theArray):
    numberOfItems = len(theArray)

    startArray = theArray.copy() # duplicate the array so it doesn't break the original array. ie, no side effects.
    newArray = []

    for iterate in range(numberOfItems):  # iterate for as many items as there are in the original array
        numberOfItemsLeft = len(startArray)
        index = random.randint(0,numberOfItemsLeft-1)  # pick a random index to pull the item from
        newArray.append(startArray.pop(index)) # add that item to the new array, and remove it from the starting array

    return newArray

# Add the crazy wires
# To randomize which pin does which, start with the 3 pins
ACTION_PINS = [7, 8, 9]
randomizedActionPins = shuffleArray(ACTION_PINS)

STOP_WIRE_PIN = randomizedActionPins[0] # stops the timer
ZERO_WIRE_PIN = randomizedActionPins[1] # sets time down to 1 second
PANIC_WIRE_PIN = randomizedActionPins[2] # speeds up the timer

To add more wires to make it even harder to guess, simply add more GPIO wires and add their positions to the ACTION_PINS array. Only the first three of the GPIO wires in the randomized array will be the 3 action pins - the rest will be decoys.

Enjoy!
-Nico

  • Sign in to reply
Parents
  • javagoza
    javagoza over 3 years ago

    I love it, a cool and fun project Nico.

    I would just add a few more cables and a method to shuffle the ports assigned to stop, zero and panic. So that you don't even know which one to cut.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • ntewinkel
    ntewinkel over 3 years ago in reply to javagoza

    That a really fun idea!

    I'm guessing the adding of wires would just add a lot of decoy wires that, when cut, don't do anything?

    To randomize the 3 wires is not too hard - I'll edit the post to add the changes needed for that Slight smile

    The code can easily be expanded to add a lot of extra decoy wires simply by adding all the extra GPIO pins to the "ACTION_PINS" array. The 3 action wires will be the first 3 in the randomized array and the rest will be decoys.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
Comment
  • ntewinkel
    ntewinkel over 3 years ago in reply to javagoza

    That a really fun idea!

    I'm guessing the adding of wires would just add a lot of decoy wires that, when cut, don't do anything?

    To randomize the 3 wires is not too hard - I'll edit the post to add the changes needed for that Slight smile

    The code can easily be expanded to add a lot of extra decoy wires simply by adding all the extra GPIO pins to the "ACTION_PINS" array. The 3 action wires will be the first 3 in the randomized array and the rest will be decoys.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
Children
No Data
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube