Pieces of the Modular Marble Run

Abstract: The overall goal of the project was to create a marble track game that could be configured in many different ways. We wanted to emphasize modularity through design choices so the pieces could be used for many play modes and to allow multiple kids to play at once. The older kids were much more receptive to the idea of creating different piece configurations while the younger kids needed a lot of suggestions. Overall, we believe the kids enjoyed playing with our marble toy.

Objectives: The primary goal was to create a collection of tile pieces that could be arranged in many permutations to form long tracks for the marbles to roll down. We wanted there to be enough pieces that multiple kids could interact with the toy at the same time and possibly even make multiple tracks. We planned on designing pieces that varied in complexity to allow for many modes of interaction with the pieces.

Implementation: We created three categories of pieces for the marble to roll through: complex pieces, medium pieces, and simple pieces. Complex pieces would be motorized and mostly used to lift up marbles, medium pieces that usually include user interaction, and simple pieces that have tracks for the ball to roll through and to connect other pieces. We wanted to make the complex lifting pieces interesting to look at so the kids could watch their marbles get lifted up. During the first children’s school visit the gear spinning pieces wasn’t working exactly how we intended; the gear would only spin if you moved it with your hands. But, we learned from watching the kids that they enjoyed the interaction with the piece, and we designed a second medium piece with this goal in mind. We also later decided to paint some of the pieces in different patterns and colors to make the whole collection less mundane and to entice the kids to want to play with our toy. The idea behind the moveable bounce pad on the Archimedes screw piece was to allow children to aim a jumping ball into another piece and to redirect the ball in new directions to promote piece configurations that weren’t just straight lines.

Outcomes: The first two outcomes had to do with the children’s’ responses, while the latter two had to do with problems we encountered throughout the process. The first outcome was that younger kids didn’t make as much of an active effort to rearrange pieces. They didn’t understand the concept of building a continuous track, they focused more on playing with individual pieces and on observing the current system state. The second outcome was that older kids were more interested in arranging the pieces to create custom tracks. They actively moved pieces and understood the idea that pieces needed to be lined up to create a continuous path. The third outcome was that controlling the output location of marble from the piece was harder than expected. Various characteristics of user interaction such as force applied and exit location influences where the marble lands when exiting from the pieces. The fourth outcome was that creating perfectly aligned tracks was harder than expected. Since we didn’t standardize the input and output heights, it took a lot of effort to get pieces to align.

Future Work: The main goal moving forward would be to create a plan to allow for easier alignment of pieces by creating a standardized guide that allow for more permutations of tracks. We could do this by widening the track width, controlling the output path, standardizing the heights of pieces, and creating more vertical lifting pieces.

Contribution: Diederik designed, fabricated, and coded the two complex pieces and the spinning gear medium piece, and Saloni designed and fabricated the other medium pieces, all of the simple pieces, and all the height blocks and ideated the concept for all the pieces.

Multiple Kids Playing with One Long Track
5 Year Old Attempting to Connect two Different Pieces with a Piece in between
Multiple Kids Playing with the Pieces on Test Day 1
Possible Permutation from Showcase on Friday

Technical Documentation:

DC Motor Circuit (from course website)

Code for Both Motors (from course website)

# dual_spin.py
#
# Raspberry Pi Pico - DC motor motion demo
#
# Demonstrates operating two DC motors driven by a DRV8833.
#
# This assumes a Pololu DRV8833 dual motor driver has been wired up to the Pico as follows:
#   Pico pin 24, GPIO18   -> AIN1
#   Pico pin 25, GPIO19   -> AIN2
#   Pico pin 26, GPIO20   -> BIN2
#   Pico pin 27, GPIO21   -> BIN1
#   any Pico GND          -> GND

# DRV8833 carrier board: https://www.pololu.com/product/2130

################################################################
# CircuitPython module documentation:
# time    https://circuitpython.readthedocs.io/en/latest/shared-bindings/time/index.html
# math    https://circuitpython.readthedocs.io/en/latest/shared-bindings/math/index.html
# board   https://circuitpython.readthedocs.io/en/latest/shared-bindings/board/index.html
# pwmio   https://circuitpython.readthedocs.io/en/latest/shared-bindings/pwmio/index.html

################################################################################
# print a banner as reminder of what code is loaded
print("Starting dual_spin script.")

# load standard Python modules
import math, time

# load the CircuitPython hardware definition module for pin definitions
import board

# load the CircuitPython pulse-width-modulation module for driving hardware
import pwmio

#--------------------------------------------------------------------------------
# Class to represent a single dual H-bridge driver.

class DRV8833():
    def __init__(self, AIN1=board.GP18, AIN2=board.GP19, BIN2=board.GP20, BIN1=board.GP21, pwm_rate=20000):
        # Create a pair of PWMOut objects for each motor channel.
        self.ain1 = pwmio.PWMOut(AIN1, duty_cycle=0, frequency=pwm_rate)
        self.ain2 = pwmio.PWMOut(AIN2, duty_cycle=0, frequency=pwm_rate)

        self.bin1 = pwmio.PWMOut(BIN1, duty_cycle=0, frequency=pwm_rate)
        self.bin2 = pwmio.PWMOut(BIN2, duty_cycle=0, frequency=pwm_rate)

    def write(self, channel, rate):
        """Set the speed and direction on a single motor channel.

        :param channel:  0 for motor A, 1 for motor B
        :param rate: modulation value between -1.0 and 1.0, full reverse to full forward."""

        # convert the rate into a 16-bit fixed point integer
        pwm = min(max(int(2**16 * abs(rate)), 0), 65535)

        if channel == 0:
            if rate < 0:
                self.ain1.duty_cycle = pwm
                self.ain2.duty_cycle = 0
            else:
                self.ain1.duty_cycle = 0
                self.ain2.duty_cycle = pwm
        else:
            if rate < 0:
                self.bin1.duty_cycle = pwm
                self.bin2.duty_cycle = 0
            else:
                self.bin1.duty_cycle = 0
                self.bin2.duty_cycle = pwm


#--------------------------------------------------------------------------------
# Create an object to represent a dual motor driver.
print("Creating driver object.")
driver = DRV8833()

#--------------------------------------------------------------------------------
# Begin the main processing loop.  This is structured as a looping script, since
# each movement primitive 'blocks', i.e. doesn't return until the action is
# finished.

print("Starting main script.")
while True:
    
    driver.write(0, 1.0)

Permutation of Pieces before Painting them