Mason MelendezErik Welsh
Published

Pocket Bucket

Shoot the ball and score!

BeginnerShowcase (no instructions)61
Pocket Bucket

Things used in this project

Hardware components

RGB Backlight LCD - 16x2
Adafruit RGB Backlight LCD - 16x2
×1
Adafruit 0.56" 7-segment LED HT16K33 Display
×1
Adafruit IR Break Beam Sensor with Premium Wire Header Ends - 5mm LEDs
×1
Adafruit Large Enclosed Piezo Element w/Wires
×1
Adafruit Tactile Button switch (6mm)
×1
PocketBeagle
BeagleBoard.org PocketBeagle
×1
Male/Female Jumper Wires
Male/Female Jumper Wires
×1
Jumper wires (generic)
Jumper wires (generic)
×1

Hand tools and fabrication machines

Hot glue gun (generic)
Hot glue gun (generic)
Wire Cutter / Stripper, 5.25 " Overall Length
Wire Cutter / Stripper, 5.25 " Overall Length
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

Hoop Rim

The hoop was 3-D printed into three separate pieces that were super glued together. This is the rim.

Hoop Base

The hoop was 3-D printed into three separate pieces that were super glued together. This is the base of the hoop. The hole was originally so that wires from a servo could come out, however I did not end up using the servo in the final product.

Hoop Skeleton

The hoop was 3-D printed into three separate pieces that were super glued together. This is the "skeleton" of the hoop.

Schematics

System Block Diagram

Code

BangNote

Python
This code uses a note library in combination with the piezo element and the 7-segment display to play a note when a basket is made
"""
--------------------------------------------------------------------------
BangNote
--------------------------------------------------------------------------
License:   
Copyright 2023 Mason Melendez

Redistribution and use in source and binary forms, with or without 
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this 
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, 
this list of conditions and the following disclaimer in the documentation 
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors 
may be used to endorse or promote products derived from this software without 
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------

Use IR breakbeam to detect a made basket. Then, play a note with a piezo element. Finally, add two points to the scoreboard. 

Requirements:
    - Play a note when the ir beam is broken.
    - Display the fill percentage using measurements from the force sensitive 
      resistor

"""
import sys
import time
import math
import random

import Adafruit_BBIO.ADC as ADC
import Adafruit_BBIO.GPIO as GPIO
import Adafruit_BBIO.PWM as PWM
from ht16k33_project import HT16K33

# ------------------------------------------------------------------------
# Global variables
# ------------------------------------------------------------------------
servo_pin = "P1_36"
piezo_pin = "P2_3"
FSR_pin = "AIN5"
ir_pin = "P2_2"


# ------------------------------------------------------------------------
# Note Library
# ------------------------------------------------------------------------
NOTE_F5  = 698
NOTE_C7  = 2093


# ------------------------------------------------------------------------
# Main Tasks
# ------------------------------------------------------------------------

def setup():
    """Sets up the hardware components."""
    ADC.setup()
    GPIO.setup(ir_pin, GPIO.IN)
    """ HT16K33.display_setup() 
    HT16K33.display_clear() """
    
# end def

def play_note(Note, Length):
    """Plays a given note for a given length."""
    PWM.start(piezo_pin, 50, Note)
    time.sleep(Length)
    PWM.stop(piezo_pin)
    PWM.cleanup() #Stops continuous tone after function runs

# end def

    
def bang():
    """Plays a sound for a made basket"""
    play_note(NOTE_F5, 0.1)  # Play the first note
    time.sleep(0.1)  # Add a short pause between the notes
    play_note(NOTE_C7, 0.2)  # Play the final note with a longer duration
   # end def


# ------------------------------------------------------------------------
# Main script
# ------------------------------------------------------------------------

if __name__ == '__main__':
    setup()
    
    display = HT16K33(1, 0x70)

    bang()
    
    score = 0

    while True:
        if GPIO.input("P2_2") == 0:
            #Basket is made
            print("basket")
            score += 2
            #Update display
            display.update(score)
            bang()
            time.sleep(0.05)
        else:
            pass
        time.sleep(0.1)

ht16k33_project.py

Python
Code display for the scoreboard, which is used in the main script to count the score
"""
--------------------------------------------------------------------------
HT16K33 I2C Library
--------------------------------------------------------------------------
License:
Copyright 2023 Mason Melendez

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------
"""

import os
import time

# ------------------------------------------------------------------------
# Functions / Classes
# ------------------------------------------------------------------------
# Constants
HEX_DIGITS                  = [0x3f, 0x06, 0x5b, 0x4f,     # 0, 1, 2, 3
                               0x66, 0x6d, 0x7d, 0x07,     # 4, 5, 6, 7
                               0x7f, 0x6f, 0x77, 0x7c,     # 8, 9, A, b
                               0x39, 0x5e, 0x79, 0x71]     # C, d, E, F

LETTERS                     = { "a" : 0x77, "A" : 0x77,    # "A"
                                "b" : 0x7c, "B" : 0x7c,    # "b"
                                "c" : 0x58, "C" : 0x39,    # "c", "C"
                                "d" : 0x5e, "D" : 0x5e,    # "d"
                                "e" : 0x79, "E" : 0x79,    # "E"
                                "f" : 0x71, "F" : 0x71,    # "F"
                                "g" : 0x6F, "G" : 0x6F,    # "g"
                                "h" : 0x74, "H" : 0x76,    # "h", "H"
                                "i" : 0x04, "I" : 0x30,    # "i", "I"
                                "j" : 0x0e, "J" : 0x0e,    # "J"
# Cannot be implemented         "k" : None, "K" : None,    
                                "l" : 0x38, "L" : 0x38,    # "L"
# Cannot be implemented         "m" : None, "M" : None,    
                                "n" : 0x54, "N" : 0x54,    # "n"
                                "o" : 0x5c, "O" : 0x3f,    # "o", "O"
                                "p" : 0x73, "P" : 0x73,    # "P"
                                "q" : 0x67, "Q" : 0x67,    # "q"
                                "r" : 0x50, "R" : 0x50,    # "r"
                                "s" : 0x6D, "S" : 0x6D,    # "S"
                                "t" : 0x78, "T" : 0x78,    # "t"
                                "u" : 0x1c, "U" : 0x3e,    # "u", "U"
# Cannot be implemented         "v" : None, "V" : None,    
# Cannot be implemented         "w" : None, "W" : None,    
# Cannot be implemented         "x" : None, "X" : None,    
                                "y" : 0x6e, "Y" : 0x6e,    # "y"
# Cannot be implemented         "z" : None, "Z" : None,    
                                " " : 0x00,                # " "
                                "-" : 0x40,                # "-"
                                "0" : 0x3f,                # "0"
                                "1" : 0x06,                # "1"
                                "2" : 0x5b,                # "2"
                                "3" : 0x4f,                # "3"
                                "4" : 0x66,                # "4"
                                "5" : 0x6d,                # "5"
                                "6" : 0x7d,                # "6"
                                "7" : 0x07,                # "7"
                                "8" : 0x7f,                # "8"
                                "9" : 0x6f,                # "9"
                                "?" : 0x53                 # "?"
                              }                               

CLEAR_DIGIT                 = 0x7F
POINT_VALUE                 = 0x80

DIGIT_ADDR                  = [0x00, 0x02, 0x06, 0x08]
COLON_ADDR                  = 0x04

HT16K33_BLINK_CMD           = 0x80
HT16K33_BLINK_DISPLAYON     = 0x01
HT16K33_BLINK_OFF           = 0x00
HT16K33_BLINK_2HZ           = 0x02
HT16K33_BLINK_1HZ           = 0x04
HT16K33_BLINK_HALFHZ        = 0x06

HT16K33_SYSTEM_SETUP        = 0x20
HT16K33_OSCILLATOR          = 0x01

HT16K33_BRIGHTNESS_CMD      = 0xE0
HT16K33_BRIGHTNESS_HIGHEST  = 0x0F
HT16K33_BRIGHTNESS_DARKEST  = 0x00

# Maximum decimal value that can be displayed on 4 digit Hex Display
HT16K33_MAX_VALUE           = 9999




class HT16K33():
    """ Class to manage a HT16K33 I2C display """
    # Class variables
    bus = None
    address = None
    command = None
    def __init__(self, bus, address=0x70, blink=HT16K33_BLINK_OFF, brightness=HT16K33_BRIGHTNESS_HIGHEST):
        """ Initialize class variables; Set up display; Set display to blank """
        # Initialize class variables
        self.bus = bus
        self.address = address
        self.command = f"/usr/sbin/i2cset -y {bus} {address}"

        # Set up display
        self._setup(blink, brightness)
        # Set display to blank
        self.blank()

    def _setup(self, blink, brightness):
        """Initialize the display itself"""
        if self.command:
            os.system(f"{self.command} {HT16K33_SYSTEM_SETUP | HT16K33_OSCILLATOR}")
            os.system(f"{self.command} {HT16K33_BLINK_CMD | blink | HT16K33_BLINK_DISPLAYON}")
            os.system(f"{self.command} {HT16K33_BRIGHTNESS_CMD | brightness}")

    def encode(self, data, double_point=False):
        """Encode data to TM1637 format."""
        ret_val = 0
        try:
            if data != CLEAR_DIGIT:
                ret_val = HEX_DIGITS[data] + POINT_VALUE if double_point else HEX_DIGITS[data]
        except ValueError:
            raise ValueError("Digit value must be between 0 and 15.")
        return ret_val

    def set_digit(self, digit_number, data, double_point=False):
        """Update the given digit of the display."""
        if self.command:
            os.system(f"{self.command} {DIGIT_ADDR[digit_number]} {self.encode(data, double_point)}")

    def set_colon(self, enable):
        """Set the colon on the display."""
        if self.command:
            os.system(f"{self.command} {COLON_ADDR} {0x02 if enable else 0x00}")
    
    def set_digit_raw(self, digit_number, data):
        """Update the given digit of the display using raw data value"""
        if self.command:
            os.system(f"{self.command} {DIGIT_ADDR[digit_number]} {data}")

    def blank(self):
        """Clear the display to read nothing"""
        if self.command:
            self.set_colon(False)
            for i in range(4):
                self.set_digit_raw(i, 0x00)

    def clear(self):
        """Clear the display to read '0000'"""
        if self.command:
            self.set_colon(False)
            self.update(0)

    def update(self, value):
        """This function will clear the display and then set the appropriate digits."""
        if not 0 <= value <= HT16K33_MAX_VALUE:
            raise ValueError("Value is not between 0 and 9999")
        self.set_digit(3, value % 10)
        self.set_digit(2, (value // 10) % 10)
        self.set_digit(1, (value // 100) % 10)
        self.set_digit(0, (value // 1000) % 10)

scoring_logic

Python
This is the logic that is used in the main code for the threading of counting a made basket with the IR breakbeams
"""
--------------------------------------------------------------------------
Scoring_logic.py
--------------------------------------------------------------------------
License:
Copyright 2023 Mason Melendez

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------

Logic to use IR breakbeam to detect a made basket, implementing the score note
"""
import Adafruit_BBIO.GPIO as GPIO
import time
import digitalio
from BangNote import setup as bang_setup, bang
from ht16k33_project import HT16K33

def scoring_logic_timer(display, stop_event):
    bang_setup()
    
    score = 0

    ir_pin = digitalio.DigitalInOut(board.P2_2)
    ir_pin.switch_to_input(pull=digitalio.Pull.UP)
    
    while not stop_event.is_set():
        if ir_pin.value == False:
            # A basket is made, increment the score by 2
            score += 2
            # Update the HT16K33 Display with the new score
            display.update(score)
            # Play sound or perform other actions for a made basket
            bang()                
            time.sleep(0.0001)
        else:
            time.sleep(0.0001)

Main_Script

Python
This is the main code, which when ran initiates the entirety of the game. This code implements threading to ensure all elements of the game are able to run simultaneously.
"""
--------------------------------------------------------------------------
Main_Script
--------------------------------------------------------------------------
License:   
Copyright 2023 Mason Melendez

Redistribution and use in source and binary forms, with or without 
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this 
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, 
this list of conditions and the following disclaimer in the documentation 
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors 
may be used to endorse or promote products derived from this software without 
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------- 
"""
# Import necessary libraries
import time
import threading
import digitalio
import board
import Adafruit_BBIO.PWM as PWM
import adafruit_character_lcd.character_lcd as characterlcd
from scoring_logic import scoring_logic_timer, bang_setup
from ht16k33_project import HT16K33
from BangNote import bang

# LCD setup
lcd_columns = 16
lcd_rows = 2
lcd_rs = digitalio.DigitalInOut(board.P2_18)
lcd_en = digitalio.DigitalInOut(board.P2_17)
lcd_d7 = digitalio.DigitalInOut(board.P2_20)
lcd_d6 = digitalio.DigitalInOut(board.P2_19)
lcd_d5 = digitalio.DigitalInOut(board.P2_24)
lcd_d4 = digitalio.DigitalInOut(board.P2_22)

lcd = characterlcd.Character_LCD_Mono(
    lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6, lcd_d7, lcd_columns, lcd_rows)

# GPIO setup
button_pin = digitalio.DigitalInOut(board.P2_3)
ir_pin = digitalio.DigitalInOut(board.P2_2)
ir_pin.switch_to_input(pull=digitalio.Pull.UP)

# Initialize the HT16K33 Display
display = HT16K33(1, 0x70)


# Function to display blinking message until the button is pressed
def wait_for_button_press(message):
    lcd.clear()
    while button_pin.value:
        lcd.message = message
        time.sleep(0.5)
        lcd.clear()
        time.sleep(0.5)

# Function to display countdown on LCD
def countdown(t, message):
    while t:
        mins, secs = divmod(t, 60)
        timer = '{:02d}:{:02d}'.format(mins, secs)
        lcd.message = timer
        time.sleep(1)
        t -= 1

    lcd.clear()  # Clear the LCD after the countdown is complete
    lcd.message = message
    time.sleep(1)
    lcd.clear()

# Start the main script
if __name__ == '__main__':
    # Wait for button press to start the game
    wait_for_button_press("Press button\n to start")

    # Initialize components
    bang_setup()

    # Start the score at 0
    score = 0

    # Event to signal the scoring logic to stop after 20 seconds
    stop_scoring_event = threading.Event()

    # Start the scoring logic in a separate thread
    scoring_thread = threading.Thread(target=scoring_logic_timer, args=(display, stop_scoring_event))
    scoring_thread.start()
    
    try:
        # Initial countdown of 5 seconds
        countdown(5, "Begin!")
    
        # Sets game timer to 20 seconds
        t = 20 
    
        while t>0 and not stop_scoring_event.is_set():
            mins, secs = divmod(t, 60)
            timer = '%02d:%02d' % (mins, secs)
            lcd.message = timer
            time.sleep(0.05) #Sets how long to wait in between checking if IR break beam is in tact
            t -= 0.05
    
            if ir_pin.value == False: 
                # Add score
                score += 2
                # Update Display
                display.update(score)
                # Play sound
                bang()
    
            if t == 0:
                # Signal the scoring thread to stop after 45 seconds
                stop_scoring_event.set()
                # Wait for the scoring thread to finish
                scoring_thread.join()
                break  # Exit the loop right before displaying "Game over!"
    
        lcd.clear()  # Clear the LCD after the countdown is complete
        lcd.message = "Game over!"
        time.sleep(3)  # Display "Game over!" for 3 seconds
        lcd.clear()
        display = HT16K33(1, 0x70)
    
    except KeyboardInterrupt:
        pass  # Handle KeyboardInterrupt (Ctrl+C) gracefully

ENGI301

This repository contains a file that includes all of the project code, as well as a README outlining software operation

Credits

Mason Melendez
1 project • 0 followers
Contact
Erik Welsh
33 projects • 19 followers
Contact

Comments

Please log in or sign up to comment.