CKS Work https://courses.ideate.cmu.edu/16-223/f2021/work Creative Kinetic Systems: Student Work Fri, 03 Dec 2021 20:49:14 +0000 en-US hourly 1 https://wordpress.org/?v=5.8.9 Marble City https://courses.ideate.cmu.edu/16-223/f2021/work/2021/12/03/marble-city/ https://courses.ideate.cmu.edu/16-223/f2021/work/2021/12/03/marble-city/#respond Fri, 03 Dec 2021 20:48:13 +0000 https://courses.ideate.cmu.edu/16-223/f2021/work/?p=5162 Karen Abruzzo and Emilie Zhou

Abstract

Our goal for Marble City was to create a visually appealing marble-run with structural features and components based loosely on the buildings and architecture of Pittsburgh. The resulting structure has a wheel mechanism, accumulator, and four distinct paths. The cascade of events is initiated by spinning the wheel mechanism, which also provides a way for people to interact with the marble run, and results in marbles falling into four distinct paths before they are returned back to the wheel. 

Objectives

We wanted to include a variety of interesting paths centered on different components and also wanted users to be able to interact with the structure in some way. To fit all of our ideas into the scope for this project, we ended up creating a marble run with four distinct paths and a hand cranked wheel mechanism that would act as a return system to move marbles back onto the paths. We used both physical and computational components throughout the structure to incorporate different kinds of motion and try to add unexpected elements of surprise into the experience of interacting with the structure. 

Implementation

To bring the different elements of our project together and give us a direction to work towards, we centered our paths and pieces around a Pittsburgh city theme.

The tower is supposed to represent the Cathedral of Learning and other decorations were added to reflect some of the iconic aspects of Pittsburgh, like the bridges, incline, and rivers. Photo courtesy of Professor Garth.

The core of our marble structure is the wheel mechanism, which was inspired by a past group’s CKS project. The wheel has curved protrusions with spiked ends and a gear attached to the back of it. The wheel can be moved by spinning the gear next to the wheel. We chose to use this mechanism because it gave us the height that we needed for our structure, as the wheel could pick up marbles collected at the bottom of the wheel and then deposit them to an accumulator near the top of the wheel. For our final implementation of the wheel mechanism, we placed a curved piece of wood in the middle of the two wheel pieces to help force marbles onto the wheel and another piece of wood at the top of the wheel that would allow marbles to roll off the wheel and onto a desired path. 

The curved spikes help the wheel pick up marbles from the ramp before releasing them onto the path leading to the accumulator.
Users can interact with the marble run by spinning the gear to move the wheel and control how many marbles there are in the structure. Photo courtesy of Professor Garth.

For the accumulator, our initial idea was to have a 3D printed part that would have slots for marbles to roll into and then rotate to release the marbles once all the slots were filled. Due to 3D printer problems, we ended up laser printing the accumulator path and gate. Marbles are deposited by the wheel onto a small path that feeds into the accumulator path. There’s a sensor in the accumulator path floor and once the accumulator is full and the sensor is covered, the gate opens and releases the marbles down the four paths. We found that the moment when all the marbles were suddenly released was satisfying and mesmerizing to both see and hear.

After getting released by the wheel, the marbles fall onto the path that connects the wheel to the accumulator. When enough marbles are present and the sensor is covered, the gate is raised to release the marbles into one of the four paths.

There are four different paths that the marbles can fall into. Two of the paths, the tower and zig zag paths, only have physical components, while the other two paths, the funnel and drawbridge paths, have more actuation and computational pieces. We hoped to balance the physical and computational elements by using different components for the paths. For the funnel path, when a marble moves over the sensor, the funnel spins and creates a swirling motion for the marbles that land in it. Marbles in the drawbridge path will initially get stuck by a gate in the middle of the path, but when a marble moves over the sensor the gate will rotate to allow the marble to continue to the rest of the path. All four paths connect to a main path that brings the marbles back to the bottom of the wheel. 

The tower path is in purple, the funnel path is in red, the zig zag path is in green, and the drawbridge path is in yellow. Those four paths all lead to the main path in blue that brings marbles back to the wheel.

Outcomes

The use of the wheel, accumulator, and different paths were successful in creating a marble run with some satisfying and unexpected moments. We tried to create a balance between physical components and components that had more computation. We also found that some people were surprised by the accumulator release and the moving funnel and that most people liked the overall look of the structure. We were able to keep reusing most of the marbles as they cycled through the marble run, but a portion of marbles also fell off the paths or got stuck. Another problem we encountered was that the wheel didn’t continuously pick up the marbles. However, we were able to avoid complications from these problems by adding more marbles onto the marble run to make up for the marbles that were lost. 

Future Work

For the next iteration of this project, we could do more testing and fine tune the paths and how the different components fit together to decrease the number of marbles that fall off the paths. We could also connect a hand crank to the gear to make it easier for people to move the wheel. Adding bridge-like cut outs to the walls of the paths could also contribute to the Pittsburgh theme of the structure and make it more visually cohesive and appealing. 

Contributions

Karen: Helping throughout the stages of the project, brainstorming ideas, designing CAD pieces, assembling the project, designing the circuit, writing code

Emilie: Helping throughout the stages of the project, brainstorming ideas, designing CAD pieces and layout, assembling how the CAD components fit together, assembling the project, adding visual elements and paint, final video

Media

Citations

For our project, we were inspired by Ben Tardif’s Marble Mountain. The wheel mechanism was inspired by the Marble Maze Melody Machine made by Wenqing Yin and Hagan Miller in a previous iteration of this course. Code from Professor Garth’s website was also used as a template. 

Supporting Material

Source Code:

# marble_city.py
#
# Raspberry Pi Pico
#
#
#
# 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 marble_city 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

from digitalio import DigitalInOut, Direction, Pull

#--------------------------------------------------------------------------------
# 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, CIN2=board.GP16, CIN1=board.GP17, 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)

        self.cin1 = pwmio.PWMOut(CIN1, duty_cycle=0, frequency=pwm_rate)
        self.cin2 = pwmio.PWMOut(CIN2, 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
        elif channel == 1:
            if rate < 0:
                self.bin1.duty_cycle = pwm
                self.bin2.duty_cycle = 0
            else:
                self.bin1.duty_cycle = 0
                self.bin2.duty_cycle = pwm
        else:
            if rate < 0:
                self.cin1.duty_cycle = pwm
                self.cin2.duty_cycle = 0
            else:
                self.cin1.duty_cycle = 0
                self.cin2.duty_cycle = pwm

class BridgeSensor():
    def __init__(self, SWITCH=board.GP15):
        self.input = DigitalInOut(SWITCH)
        self.input.direction = Direction.INPUT
        self.state_index = False
        self.sampling_interval = 10000000           # period of 100 Hz in nanoseconds
        self.sampling_timer = 0
        self.switch = False
    def poll(self, elapsed):
        """Polling function to be called as frequently as possible from the event loop
        to read and process new samples.

        :param elapsed: nanoseconds elapsed since the last cycle.
        """

        self.sampling_timer -= elapsed
        if self.sampling_timer < 0:
            self.sampling_timer += self.sampling_interval
            self.switch = self.input.value

class FunnelSensor():
    def __init__(self, SWITCH=board.GP14):
        self.input = DigitalInOut(SWITCH)
        self.input.direction = Direction.INPUT
        self.state_index = False
        self.sampling_interval = 10000000           # period of 100 Hz in nanoseconds
        self.sampling_timer = 0
        self.switch = False
    def poll(self, elapsed):
        """Polling function to be called as frequently as possible from the event loop
        to read and process new samples.

        :param elapsed: nanoseconds elapsed since the last cycle.
        """

        self.sampling_timer -= elapsed
        if self.sampling_timer < 0:
            self.sampling_timer += self.sampling_interval
            self.switch = self.input.value

class StartSensor():
    def __init__(self, SWITCH=board.GP13):
        self.input = DigitalInOut(SWITCH)
        self.input.direction = Direction.INPUT
        self.state_index = False
        self.sampling_interval = 10000000           # period of 100 Hz in nanoseconds
        self.sampling_timer = 0
        self.switch = False
    def poll(self, elapsed):
        """Polling function to be called as frequently as possible from the event loop
        to read and process new samples.

        :param elapsed: nanoseconds elapsed since the last cycle.
        """

        self.sampling_timer -= elapsed
        if self.sampling_timer < 0:
            self.sampling_timer += self.sampling_interval
            self.switch = self.input.value

class Motor:
    def __init__(self, channel, rate, timeOpen, timeWait, timeClose, timeWait2):
        self.update_timer = 0
        self.timeOpen = timeOpen
        self.timeWait = timeWait
        self.timeWait2 = timeWait2
        self.timeClose = timeClose
        self.isOn = False
        self.channel = channel
        self.rate = rate
        self.time = time

    def poll(self, elapsed):
        """Polling function to be called as frequently as possible from the event loop
        with the nanoseconds elapsed since the last cycle."""
        if (self.isOn):
            self.update_timer += elapsed
            if self.update_timer < self.timeOpen:
                driver.write(self.channel, self.rate)
                #print("opening")

            elif self.update_timer < self.timeWait:
                driver.write(self.channel, 0)
                #print("waiting")
            elif self.update_timer < self.timeClose:
                driver.write(self.channel, -(self.rate))
                #print("closing")
            elif self.update_timer < self.timeWait2:
                driver.write(self.channel, 0)
                #print("closing")
            else:
                driver.write(self.channel, 0)
                self.update_timer = 0
                self.isOn = False
                #print("done")


#--------------------------------------------------------------------------------
# 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.
bridge = BridgeSensor()
funnel = FunnelSensor()
start = StartSensor()
motorBridge = Motor(0, 1.1, 21000000, 1500000000, 1520800000, 1525000000)
gateBridge = Motor(1, -1, 70000000, 3000000000, 3070000000, 0)
funnelBridge = Motor(2, -0.85, 2650000000, 0, 0, 0)
gateBridge.isOn = False
print("Starting main script.")
last_clock = time.monotonic_ns()
while True:
    #print(gateBridge.update_timer);
    now = time.monotonic_ns()
    elapsed = now - last_clock
    last_clock = now
    bridge.poll(elapsed)
    motorBridge.poll(elapsed)
    gateBridge.poll(elapsed)
    funnelBridge.poll(elapsed)
    start.poll(elapsed)
    funnel.poll(elapsed)
    if bridge.state_index is False:
        if bridge.switch is True:
            #led.value = True
            bridge.state_index = True
            #print("On")

    elif bridge.state_index is True:
        if bridge.switch is False:
            #led.value = False
            bridge.state_index = False
            #print("Off")
            motorBridge.isOn = True
    gateBridge.poll(elapsed)

    if funnel.state_index is False:
        if funnel.switch is True:
            #led.value = True
            funnel.state_index = True
            print("On")

    elif funnel.state_index is True:
        if funnel.switch is False:
            #led.value = False
            funnel.state_index = False
            funnelBridge.isOn = True
            print("Off")


    if start.state_index is False:
        if start.switch is True:
            #led.value = True
            start.state_index = True
            #print("On")

    elif start.state_index is True:
        if start.switch is False:
            #led.value = False
            start.state_index = False
            gateBridge.isOn = True
            #rint("Off")

CAD:

Drawings:

]]>
https://courses.ideate.cmu.edu/16-223/f2021/work/2021/12/03/marble-city/feed/ 0
Headbanger: A CKS Final Project https://courses.ideate.cmu.edu/16-223/f2021/work/2021/12/02/headbanger-a-cks-final-project/ https://courses.ideate.cmu.edu/16-223/f2021/work/2021/12/02/headbanger-a-cks-final-project/#respond Fri, 03 Dec 2021 00:20:07 +0000 https://courses.ideate.cmu.edu/16-223/f2021/work/?p=5168 Jasmine Cheng

Gabriel Prado

Abstract

The goal of this project was to build a music visualizer, Headbanger, that would incorporate marbles and light to correspond with the mood and rhythm of songs. We built a two-axis pan-tilt machine, which included a face character with colored LED lights. We choreographed the machine to two contrasting songs via motorized movements and light sequences.

Objectives

We wanted to create a music visualizer to blend physical motion with creative expression. To achieve this effect, we made use of a pan/tilt mechanism and neopixels lights.

The machine could be choreographed to match diverse song types.

The combination of the pan/tilt movement and neopixels control expression of marble plate. Lights give the marble plate character and are refracted by the marbles. The pan tilt induces marble motion in sync with the music, and the marbles rhythmically roll across the plate and interact with each other.

Implementation

We built a two-axis pan tilt device that utilizes DC motors for each degree of freedom. The bottom layer is the pan motor assembly. There is a 20 tooth gear on the DC motor, connected to a 40 tooth gear attached to the ring bearing case. The top of the ring bearing case is mounted onto the panning plan. We chose to use the ring bearing case so that wires could be passed through from under the pan plate. The gearing helps us achieve slower pans.

The panning plate has tabs in it for struts for the tilt mechanism. The tilt mechanism is driven by a cam, which is powered by another DC motor with a 20 tooth (on motor) to 30 tooth (on cam) gear reduction.

We choreographed two songs to Headbanger: Tourette’s by Nirvana and To Build A Home by The Cinematic Orchestra. The former is a heavy noise rock song while the latter is a soft, melodic piece. We purposefully chose opposite song types in order to provoke diverse reactions by the audience at the show.

Outcomes

We constructed two prototypes prior to our final build.

For our first prototype, we incorporated all the planned fundamental functionality into the build. However, we had to use LEDs instead of neopixels because there was not enough space for the neopixel wires to go through the smile platform. We also included a return spring for the smile so that it would return to its original state when the cam activated.

Unfortunately, the pan mechanism of the first prototype malfunctioned often due to the ring bearing partially breaking during the build. The tilt mechanism was more reliable, but the return spring seemed to be too strong to have any realistic control of the tilt via code.

Smile platform with LEDs instead of neopixels
Use of pullback spring to return to original state

For our second prototype, we kept the fundamental mechanisms of the first prototype and mainly focused on making the motion more reliable and controllable. To do this, we used a second iteration of the ring bearing to provide better pan motion. We also removed the pullback spring and instead glued the smile platform such that its own weight would return it to the original state. We were also able to add the neopixels by giving more room in the smile to feed their wires through.

This prototype was a major improvement from the first one in terms of reliability and motion, and we were able to run code for a song on this prototype. However, there were still slight issues in its sturdiness. The glue that fixed the smile to the tilt mechanism was not strong enough, our cam was slightly oversized, and we had to improvise a counterweight for the entire assembly.

Tape used as improvided counterweight
Neopixels implemented successfullly

Taking the lessons learned from the creations of our previous prototypes, we approached the final build with further mechanical refinement. We also put a lot of focus on choreographing songs via our code. The major mechanical changes we made were adjusting the cam size and its attachment to the corresponding gear mechanism as well as using trapezoidal inserts to attach the smile platform to the tilt mechanism. These changes significantly improved the reliability and performance of Headbanger. Due to time constraints, we were only able to choreograph two songs. However, there was very little trouble mechanically in achieving this.

Here is our project video demonstrating the final build:

Future Work

To further improve upon this project, we could choreograph routines to more songs across different genres. We could add sensing such as an accelerometer that would allow for more fine tuned control of the pan and tilt, or photoelectric sensors that can allow the lights to change color when a marble triggers them.

We can also clean up the build in terms of loose wires and other aesthetic detail. In addition, the gear assembly could be more robust.

Photos

The following photos were taken by Professor Garth Zeglin at our final show:

Contribution

Jasmine wrote all of the software and designed the pan and tilt mechanisms while Gabriel designed the smile platform, conducted wiring testing, and made the project video.

Citations

Sections of software were taken from Professor Garth’s class website:

Fall 2021 Course Guide — 16-223 Creative Kinetic Systems (cmu.edu)

The crooked smile was inspired by rock band Nirvana’s smiley logo, The CAD for it was downloaded on GrabCad, and it was designed by user Javier López.

Nirvana | 3D CAD Model Library | GrabCAD

Supporting Material

Below is the source code. The files are:

code.py: Contains main event loop and input recorder

lights.py: Contains Light object that sets LED to various light sequences

motor.py: Contains motor objects that send power commands to two motors

timer.py: Contains timer object that gives elapsed time since start of the code running

motor_actions.py: Contains motor motion primitives and their respective key bindings, handles commanding motor actions, calls Motor object to set motor powers for a given primitive

code.py

import time
import math

import board
import digitalio
from motor_actions import MotorActions
import neopixel_write
import pwmio


from lights import NeoPixelLights
from motor import DRV8833
import remote
import timer

# -------------------------------------------------------------------
# Initialize

motor_driver = DRV8833()
lights = NeoPixelLights()
remote  = remote.RemoteSerial()
timer = timer.Timer()


class EventObject:
    def __init__(self, event_end):
        self.event_end = event_end

event = EventObject(0)
actions = MotorActions(motor_driver, event, timer)

log_name = "log.txt"

def log_input():
    def keypress_handler(key_in):
        actions.command_action(key_in)

    remote.add_keypress_handler(keypress_handler)
    lights.sequence = lights.flash
    while True:
        lights.poll(timer.now())
        remote.poll(0)
        actions.poll()


def countdown(countdown_time):
    # Countdown
    countup_time = 0
    while True:
        elasped_s = timer.elasped_s()

        if  elasped_s &gt; countup_time:
            print("Starting in", countdown_time - countup_time)
            countup_time += 1

        if elasped_s &gt; countdown_time:
            break

def do_performance(log_name):
    f = open(log_name,'r',encoding = 'utf-8')
    event_list = []
    for line in f:
        words = line.split()
        action_time = float(words[0])
        action = words[1]
        event_list.append((action_time, action))

    # ------------------------------------------------------------
    # Main Event Loop
    timer.reset()
    i = 0
    while True:
        if i % 10000 == 0:
            pass
            #print("Time elasped", elasped_s)
        i += 1

        if timer.elasped_s() &gt; event.event_end:
            motor_driver.set_pan_motor(0)
            motor_driver.set_tilt_motor(0)
        
        if len(event_list) &gt; 0 and timer.elasped_s() &gt; event_list[0][0]:
            next_event = event_list.pop(0) 
            print("Event: ", next_event)
            # command_action will set the motor speeds and modify event.event_end
            actions.command_action(next_event[1])

        # Lights
        lights.color_sequence(timer.now())

        # Remote
        remote.poll(timer.elasped_s())   

countdown(3) 
log_input()
#do_performance(log_name)


motor.py

import time
import board
import digitalio
import neopixel_write
import math
import pwmio

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 set_pan_motor(self, rate):
        # Pan motor is Motor A
        self.write(0, rate)

    def set_tilt_motor(self, rate):
        # Tilt motor is Motor B
        self.write(1, 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


motor_actions.py

import timer

# import pdb
class MotorActions:
    def __init__(self, motor_driver, event_obj, in_timer):
        self.motor_driver = motor_driver
        self.event_obj = event_obj
        self.timer = in_timer

        self.tilt_set_up = True 

        self.action_lookup = {
            'a': self.pan_right_slow,
            'd': self.pan_left_slow,
            'w': self.tilt_up,
            's': self.tilt_down,
            'h': self.tilt_up_left,
            'j': self.tilt_up_right,
            'n': self.tilt_down_left,
            'm': self.tilt_down_right,
            'l': self.swirl_movement
        }

        self.action_queue = []
        self.action_queue_start = 0
        self.action_timer = timer.Timer()
        self.current_action = None

    def poll(self):
        if self.current_action is not None:
            if self.action_timer.elasped_s() &gt; self.current_action[2]:
                # Get next action
                if len(self.action_queue) &gt; 0:
                    self.set_next_action()
                else:
                    self.motor_driver.set_pan_motor(0)
                    self.motor_driver.set_tilt_motor(0)

    def set_next_action(self):
        if len(self.action_queue) &gt; 0:
            self.current_action = self.action_queue.pop(0)
            pan, tilt, action_end = self.current_action
            self.motor_driver.set_pan_motor(pan)
            self.motor_driver.set_tilt_motor(tilt)
            self.action_timer.reset()

    def command_action(self, action_key):
        action = self.action_lookup.get(action_key)
        if action is not None:
            print(f"{self.timer.elasped_s()} {action_key}")
            action()
            self.set_next_action()

    def pan_right_slow(self):
        print('pan right')
        self.action_queue = [(-0.7, 0, 0.5)]

    def swirl_movement(self):
        if self.tilt_set_up:
            self.action_queue = [
                (0.75, -0.8, 0.75),
                (0.75, 0.8, 0.75),
                (0.75, -0.8, 0.75)
            ]
            self.tilt_set_up = False

    def pan_left_slow(self):
        print('pan left')
        self.action_queue = [(0.7, 0, 0.5)]

    def tilt_up(self):
        if not self.tilt_set_up:
            self.tilt_set_up = True
            self.action_queue = [(0, 0.75, 0.6)]
            

    def tilt_down(self):
        if self.tilt_set_up:
            self.tilt_set_up = False
            self.action_queue = [(0, -0.75, 0.5)]

    def tilt_up_left(self):
        if not self.tilt_set_up:
            self.tilt_set_up = True
            self.action_queue = [(0.7, 0.75, 0.7)]

    def tilt_down_right(self):
        if self.tilt_set_up:
            self.tilt_set_up = False
            self.action_queue = [(-0.7, -0.75, 0.5)]
    
    def tilt_up_right(self):
        if not self.tilt_set_up:
            self.tilt_set_up = True
            self.action_queue = [(-0.7, 0.75, 0.7)]

    def tilt_down_left(self):
        if self.tilt_set_up:
            self.tilt_set_up = False
            self.action_queue = [(0.7, -0.75, 0.5)]
    
   
            

    def set_action(self, pan_motor, tilt_motor, time):
        
        self.motor_driver.set_pan_motor(pan_motor)
        self.motor_driver.set_tilt_motor(tilt_motor)
        self.event_obj.event_end = self.timer.elasped_s() + time

lights.py

import digitalio
import neopixel_write
import board

class Color():
    def __init__(self, r, g, b):
        self.r = r
        self.g = g
        self.b = b

    def rgb(self):
        return self.r, self.g, self.b

RED = Color(255, 0, 0)
GREEN = Color(0, 255, 0)
BLUE = Color(0, 0, 255)
MAGENTA = Color(255, 0, 255)
YELLOW = Color(255, 255, 0)
CYAN = Color(0, 255, 255)

class NeoPixelLights():
    def __init__(self):
        self.pixels = digitalio.DigitalInOut(board.GP13)
        self.pixels.direction = digitalio.Direction.OUTPUT

        self.num_pixels = 40

        self.frame_buffer = bytearray(3*self.num_pixels)

        self.dim_factor = 50
        self.sequence = self.color_sequence

    def set_sequence(self, sequence):
        self.sequence = sequence

    def poll(self, now):
        self.sequence(now)

    def color_sequence(self, now):
        # generate a temporal color sequence with each component out of phase
        red = int((now//11000) % 256)
        grn = int((now//33000) % 256)
        blu = int((now//55000) % 256)

        # print(f"{red}, {grn}, {blu}")

        # update the entire frame buffer including an additional position-dependent term
        # to create spatial variation
        for p in range(self.num_pixels):
            self.frame_buffer[3*p]   = (grn + 12*p) % 256
            self.frame_buffer[3*p+1] = (red + 12*p) % 256
            self.frame_buffer[3*p+2] = (blu + 12*p) % 256

        for i in range(self.num_pixels * 3):
                self.frame_buffer[i] = round(self.frame_buffer[i]/self.dim_factor)

        # transfer the new frame to the NeoPixel LED strand
        neopixel_write.neopixel_write(self.pixels, self.frame_buffer)

    def eye_swirl(self, now):
        speed = 2
        
        red = int((now//(speed * 11000)) % 256)
        grn = int((now//(speed * 33000)) % 256)
        blu = int((now//(speed * 55000)) % 256)
        
        for i in range(self.num_pixels * 3):
            self.frame_buffer[i] = 0

        swirl_speed = 4
        for p in range(12):
            self.frame_buffer[3*p]   = (grn + swirl_speed*12*p) % 256
            self.frame_buffer[3*p+1] = (red + swirl_speed*12*p) % 256
            self.frame_buffer[3*p+2] = (blu + swirl_speed*12*p) % 256

            self.frame_buffer[3*(24 - p)]   = (grn + swirl_speed*12*p) % 256
            self.frame_buffer[3*(24 - p)+1] = (red + swirl_speed*12*p) % 256
            self.frame_buffer[3*(24 - p)+2] = (blu + swirl_speed*12*p) % 256

        for i in range(self.num_pixels * 3):
                self.frame_buffer[i] = round(self.frame_buffer[i]/self.dim_factor)

        # transfer the new frame to the NeoPixel LED strand
        neopixel_write.neopixel_write(self.pixels, self.frame_buffer)

    def flash(self, now):
        flash_on = 1000
        flash_off = 1000
        on = now % (flash_on + flash_off) < flash_off

        grn = 0
        red = 255
        blu = 0

        if on:
            for p in range(self.num_pixels):
                self.frame_buffer[3*p]   = grn
                self.frame_buffer[3*p+1] = red
                self.frame_buffer[3*p+2] = blu
        else:
            for i in range(self.num_pixels * 3):
                self.frame_buffer[i] = 0

        for i in range(self.num_pixels * 3):
                self.frame_buffer[i] = round(self.frame_buffer[i]/self.dim_factor)

        # transfer the new frame to the NeoPixel LED strand
        neopixel_write.neopixel_write(self.pixels, self.frame_buffer)



    def set_led_rgb(self, led_num, r, g, b):
        self.frame_buffer[3*led_num] = (g + 12*led_num) % 256
        self.frame_buffer[3*led_num+1] = (r + 12*led_num) % 256
        self.frame_buffer[3*led_num+2] = (b + 12*led_num) % 256

    def set_led_color(self, led_num, color):
        r, g, b = color.rgb
        self.set_led_rgb(led_num, r, g, b)

    def write_to_neopixel(self):
        for i in range(self.num_pixels * 3):
            self.frame_buffer[i] = round(self.frame_buffer[i]/self.dim_factor)
        neopixel_write.neopixel_write(self.pixels, self.frame_buffer)



timer.py

import time

class Timer:
    def __init__(self):
        self.start_time = self.now_s()

    def reset(self):
        self.start_time = self.now_s()

    def elasped_s(self):
        return self.now_s() - self.start_time

    def now(self):
        return time.monotonic_ns() / 1000

    def now_s(self):
        return self.now()/float(1000000)
]]>
https://courses.ideate.cmu.edu/16-223/f2021/work/2021/12/02/headbanger-a-cks-final-project/feed/ 0
eyeDrop Attack! https://courses.ideate.cmu.edu/16-223/f2021/work/2021/12/01/eyedrop-attack/ https://courses.ideate.cmu.edu/16-223/f2021/work/2021/12/01/eyedrop-attack/#respond Wed, 01 Dec 2021 05:01:24 +0000 https://courses.ideate.cmu.edu/16-223/f2021/work/?p=5148 Team: Micheal Nguyen and Jason Perez

Abstract

Our goal was to create an interactive game where a user could use marble projectiles to shoot at targets and score points, in an engaging or addictive fashion. eyeDrop Attack is an engaging, on-the-floor game that allows players to drop, throw or otherwise launch a marble projectile at any of the six eyes of the monster. Players score points based on how long the “eyes” or limit switches are pressed, which incentivizes strategies that target multiple eyes or target a single eye for a continuous period of time.

Objectives

While our original goal was to create an arcade-style shooter which uses a horizontal launch trajectory to target multiple vertical targets, we ended up simplifying our vision to a top-to-bottom or horizontal target surface, to improve the feasibility of the project. By simplifying our project we were able to focus our efforts and achieve a functional marble return system, LCD text and score display, and target movement system capable of 360-degree rotation.

Implementation

Our final implementation consisted of the hit registration system, the rotating movement system, and the control and scoring system. The hit registration system was designed based on a limit switch and hinge combo. This system proved difficult to implement because the original hinges we used had very loose tolerances which introduced more errors and made hit registration inconsistent. The newer metal hinges we purchased had much tighter tolerances and produced much better results. 

Hit recognition system

The rotational motion system produced the most interesting challenge as we wanted to produce a system with full 360-degree rotation and electronic components on both the rotating piece and the box. We were able to solve this issue using a slip ring which allowed us to connect our rotating switches to our fixed microcontroller. This slip ring needed to be at the axis of rotation which presented an issue as this would take the place of any axle-based system we might have otherwise used. Garth’s open hub design helped us solve this problem, by allowing us to create a rotational movement system with an open center, allowing us to pass wires through the center.

We controlled the motion system by tuning the speed of the motor to suit the state of the game. In demo mode, the motor will follow a simple sinusoidal wave. During play, the motor’s speed is the sum of two sinusoidal waves of differing periods creating a randomized effect. After a hit, the motor will operate with a higher speed floor, and a shorter sinusoidal period to create the illusion that the monster is recoiling from the hit.

The electronics system also included the scoring system. The scoring system increments the score based on how long the switches on the eye have been pressed. This score is then displayed on the LCD display. The system also supports multiple games as the player can end the game by pressing the “Start/Stop” button to enter demo mode, and then pressing the “Start/Stop” button again to enter play mode once again. This action will set the score to zero.

Outcomes

Our main successes were the use of the slip ring, switch mounting, and the long-running nature of our project. The early incorporation of the slip ring in our design allowed us to manage our wiring in one place and, most importantly, gave us full rotation on a single axis. The switch mounting also introduced another degree of freedom by allowing us to fine-tune the hit recognition system. The mounting was critical in determining which position would offer the most consistent actuation. Another important success was the persistence of our project. The centralized design and mounting ensured the rotating mechanism was able to keep moving for relatively long periods of time.

Unexpected successes arose when new players interacted with our project. Many of our users were captivated by both the crazy visuals and bizarre mechanical behavior of our machine. Their experience was enhanced through the self-imposed difficulty of our game as well as the feedback from scoring. Over time, users developed their own game modes, one of which was a co-op mode of sorts that involved various users dropping marbles.

Future Work

  • Smoother and more accessible return system (possibel skeeball implementation)
  • Sound from speakers with sound effects for hits and music
  • More game modes

Contributions

Jason Perez – Helped come up with ideas during brainstorming, introduced hinges as a component to the target system, developed central rotator piece through CAD, developed switch mounting through CAD, programmed initial code for testing switches-motor-LCD, graphic design/theme of the project

Michael Nguyen – created Box Full Assembly CAD model, converted code to polling states to allow for simulated multitasking, motion profiled rotator for demo, play, and freakout states, designed gearbox and debugged LCD display.

Media

Video

Gallery

Citations

Motor Sample Code:

https://courses.ideate.cmu.edu/16-223/f2021/text/code/pico-motor.html#id7

Hub:

https://courses.ideate.cmu.edu/16-223/f2021/text/mechanism/open-hub.html

Code + CAD

# 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   -&gt; AIN1
#   Pico pin 25, GPIO19   -&gt; AIN2
#   Pico pin 26, GPIO20   -&gt; BIN2
#   Pico pin 27, GPIO21   -&gt; BIN1
#   any Pico GND          -&gt; 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

import busio

import adafruit_ht16k33.segments

from digitalio import DigitalInOut, Direction, Pull

#--------------------------------------------------------------------------------
# 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()
speed = 0.65

#LED
led = DigitalInOut(board.LED)
led.direction = Direction.OUTPUT
led.value = True

#7 Segment Display
i2c = busio.I2C(scl=board.GP5, sda=board.GP4)
display = adafruit_ht16k33.segments.Seg7x4(i2c)

#Switches

switchStart = DigitalInOut(board.GP9)
switchStart.pull = Pull.DOWN
switchStart.direction = Direction.OUTPUT
wasPressed = False

switch0 = DigitalInOut(board.GP10)
switch0.pull = Pull.DOWN
switch0.direction = Direction.OUTPUT

switch1 = DigitalInOut(board.GP11)
switch1.pull = Pull.DOWN
switch1.direction = Direction.OUTPUT

switch2 = DigitalInOut(board.GP12)
switch2.pull = Pull.DOWN
switch2.direction = Direction.OUTPUT

switch3 = DigitalInOut(board.GP13)
switch3.pull = Pull.DOWN
switch3.direction = Direction.OUTPUT

switch4 = DigitalInOut(board.GP14)
switch4.pull = Pull.DOWN
switch4.direction = Direction.OUTPUT

switch5 = DigitalInOut(board.GP15)
switch5.pull = Pull.DOWN
switch5.direction = Direction.OUTPUT

#States?
demoMode = True

#--------------------------------------------------------------------------------
# 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.

class Game:
    def __init__(self):
        self.score = 0
        self.scoreScale = 5
        self.motorTime = 0.01
        self.nextMotorTime = 0.01
        self.demoMode = True
        self.wasReleased = True
        self.startTime = time.time()

        self.hurtTime = 0

    def getSpeed(self,scale):
        currTime = time.time() - self.startTime
        speed = math.sin(currTime/scale)
        return min(max(abs(speed), 0.6), 0.8) * (speed / (abs(speed) + 0.001))

    def pollMainSwitches(self):
        if not self.demoMode:
            if switch0.value or switch1.value or switch2.value or switch3.value or switch4.value or switch5.value:
                self.score += self.scoreScale
                self.hurtTime = time.time() + 1

    def pollMotors(self):
        currTime = time.time()
        if (currTime &gt;= self.nextMotorTime):
            self.nextMotorTime = currTime + self.motorTime
            if (self.demoMode):
                speed = self.getSpeed(2)
                driver.write(1, speed)
            elif (currTime <= self.hurtTime):
                speed = 4 * self.getSpeed(0.001)
                driver.write(1, speed)
            else:
                speed = self.getSpeed(2)
                driver.write(1, speed)

    def pollStart(self):
        if not switchStart.value:
            self.wasReleased = True
        if self.wasReleased and switchStart.value:
            self.demoMode = not self.demoMode
            led.value = False
            self.wasReleased = False

    def pollDisplay(self):
        if self.demoMode:
            #Press
            display.set_digit_raw(0, 0b01110011) #P
            display.set_digit_raw(1, 0b01010000) #r
            display[2] = "e"
            display[3] = "5"
            display.scroll(1)
            display[3] = "5"
            display.show()
            display.scroll(1)
            display[3] = " "
            display.show()
            display.scroll(1)
            display.set_digit_raw(3, 0b01111000) #t
            display.show()
            display.scroll(1)
            display.set_digit_raw(3, 0b01011100) #o
            display.show()
            display.scroll(1)
            display[3] = " "
            display.show()
            display.scroll(1)
            display[3] = "5"
            display.show()
            display.scroll(1)
            display.set_digit_raw(1, 0b01111000)
            display.show()
            display.scroll(1)
            display[3] = "a"
            display.show()
            display.scroll(1)
            display.set_digit_raw(3, 0b01010000)
            display.show()
            display.scroll(1)
            display.set_digit_raw(1, 0b01111000)
            display.show()
            display.scroll(1)
            display[3] = " "
            
            
            #Start
            #display[0] = "5"
            #display.set_digit_raw(1, 0b01111000)
            #display[2] = "A"
            #display.set_digit_raw(3, 0b01010000)
            #display.set_digit_raw(1, 0b01111000)
        else:
            display.print("%04d" % self.score)





print("Starting main script.")
scale = 2.0
startTime = time.time()
game = Game();

while True:
    currTime = time.time() - startTime

    led.value = game.demoMode
    game.pollMotors()
    game.pollDisplay()
    game.pollMainSwitches()
    game.pollStart();

CAD Files:

https://drive.google.com/file/d/1EFqiocY5mWaKRPtHlE2MzSBPyFFcaLiz/view?usp=sharing

]]>
https://courses.ideate.cmu.edu/16-223/f2021/work/2021/12/01/eyedrop-attack/feed/ 0
Pittsburgh Marble Run V2 https://courses.ideate.cmu.edu/16-223/f2021/work/2021/11/15/pittsburgh-marble-run-v2/ https://courses.ideate.cmu.edu/16-223/f2021/work/2021/11/15/pittsburgh-marble-run-v2/#respond Mon, 15 Nov 2021 19:55:47 +0000 https://courses.ideate.cmu.edu/16-223/f2021/work/?p=5143 The current version of our marble run has all the paths, including a path with a drawbridge in the middle, a zig zag path, a path for a funnel, and a path that goes around a tower, and ramps that connect the individual paths to two main paths that bring the marbles back to the wheel. These paths have been assembled to fit together and have either glued onto a base or inserted into slots in the base. We’re still working on getting the accumulator to work and waiting on the funnel to be 3D printed. The wheel should have a set up similar to the one used for the proof of concept prototype but we also realized that we will need an additional path that brings the marbles to a more specific location in order to get picked up by the wheel. 

]]>
https://courses.ideate.cmu.edu/16-223/f2021/work/2021/11/15/pittsburgh-marble-run-v2/feed/ 0
Target Drop v2? https://courses.ideate.cmu.edu/16-223/f2021/work/2021/11/15/target-drop-v2/ https://courses.ideate.cmu.edu/16-223/f2021/work/2021/11/15/target-drop-v2/#respond Mon, 15 Nov 2021 15:59:57 +0000 https://courses.ideate.cmu.edu/16-223/f2021/work/?p=5136 Our latest version of Target Drop has finalized its rotator piece. Dimensions were increased to allow for bigger targets and room for mounting the switches needed for scoring. We also used our new metal hinges which offer a more stable actuation and assembly. Cable management was improved/implemented through an alteration of the design of the side panels. There is now a hole that feeds all current wiring for a cleaner look and to avoid collisions with marbles during operation. Additionally, we lowered the location of our mount for the rotator piece to avoid marbles bouncing out the box. We also introduced a seven-segment display to show the current score and later show the start message. We are in the process of adding a reset button to restart the game.

Updated box with lowered support
Updated rotator piece with metal hinges and increased width
The first iteration of the rotator piece with plastic hinges and smaller dimensions

https://drive.google.com/file/d/1l34aYv7CRfREJyPeC3UP6gdpZpHGwltG/view?usp=sharing

]]>
https://courses.ideate.cmu.edu/16-223/f2021/work/2021/11/15/target-drop-v2/feed/ 0
Pan Tilt Machine V2 https://courses.ideate.cmu.edu/16-223/f2021/work/2021/11/15/pan-tilt-machine-v2/ https://courses.ideate.cmu.edu/16-223/f2021/work/2021/11/15/pan-tilt-machine-v2/#respond Mon, 15 Nov 2021 15:15:14 +0000 https://courses.ideate.cmu.edu/16-223/f2021/work/?p=5132 For our second iteration, we made a couple of mechanical changes along with implementing code that was briefly choreographed to a song. We used a different version of the 3d-printed bearing device, made the pan plate as large as the base plate, and increased the heights of the upper struts. We were initially aiming to glue the neopixels to the plywood portion of the smile, but that was too complicated to achieve (wires/solder were not forgiving). Instead, we glued the neopixels to the bottom of the acrylic layer. In this version, we were able to pack the neopixel and wires within the machine to maximize rotational movement. Due to the weight distribution of the smile and the wires, a pullback spring was not necessary. However, we needed to add a counterweight (we used a roll of tape) to the rotational plate. When running, the glue that supported the smile was coming loose and the CAM gears sometimes slipped, but overall we were able to achieve controllable pan and tilt. Our action items are to press-fit the struts that support the smile instead of gluing them, use the serial communication for motion primitives to better choreograph songs, and use a better counterweight.

Video:

https://drive.google.com/file/d/1nsp35weg4MCzrwo7Uk-kEdKQg_moJ2l0/view?usp=sharing

]]>
https://courses.ideate.cmu.edu/16-223/f2021/work/2021/11/15/pan-tilt-machine-v2/feed/ 0
Pan Tilt Machine Prototype https://courses.ideate.cmu.edu/16-223/f2021/work/2021/11/03/pan-tilt-machine-prototype/ https://courses.ideate.cmu.edu/16-223/f2021/work/2021/11/03/pan-tilt-machine-prototype/#respond Wed, 03 Nov 2021 05:05:22 +0000 https://courses.ideate.cmu.edu/16-223/f2021/work/?p=5127 ]]> https://courses.ideate.cmu.edu/16-223/f2021/work/2021/11/03/pan-tilt-machine-prototype/feed/ 0 Squirrel Wheel Music https://courses.ideate.cmu.edu/16-223/f2021/work/2021/10/25/squirrel-wheel-music/ https://courses.ideate.cmu.edu/16-223/f2021/work/2021/10/25/squirrel-wheel-music/#respond Mon, 25 Oct 2021 12:14:17 +0000 https://courses.ideate.cmu.edu/16-223/f2021/work/?p=5119 Description:

The original intent was to create an instrument whose sound production relies on differences in materiality. The structure is a mirrored water wheel design that rotates regularly, dropping marbles on altering paddles to create sound. The first half is the control- a side of all wood paddles to provide consistency. The user is then able to alter the other half of the wheel. The first method is mechanically, physically switching the order of the materials that the ball bearings will hit. The second is through the console, where they can switch the direction that the wheel is spinning, creating an opposite descent of tones. The result is a device that can be used to experiment with multiple materials and the specific sounds that each can make. 

Photo:

https://drive.google.com/file/d/1IhyuiksY9cI05_Z0S7TpE9anxvwzo7ZN/view?usp=sharing

Code:

https://drive.google.com/file/d/1l8lhA-4YXRlHUJtgQnlsOEgS9Hdo1Hcn/view?usp=sharing

]]>
https://courses.ideate.cmu.edu/16-223/f2021/work/2021/10/25/squirrel-wheel-music/feed/ 0
Rain Box https://courses.ideate.cmu.edu/16-223/f2021/work/2021/10/25/rain-box/ https://courses.ideate.cmu.edu/16-223/f2021/work/2021/10/25/rain-box/#respond Mon, 25 Oct 2021 06:09:10 +0000 https://courses.ideate.cmu.edu/16-223/f2021/work/?p=5097 Reflection: The intent of the project was to build a rain-tube like machine, where the marbles hitting against the pegs as they rolled down the tube would create a soothing sound. The user input is positioning the sensor. If the user wants the tube to continuously spin, the user can place the sensor away from the tube. If the user wants the tube to rock back and forth, the user can place the sensor near the tube.

If we could redo aspects of this project, we would work out the spacing of the pegs better so that not all the balls would just flow down the sides when the tube is turned over slowly. We can also experiment with different modes of user input, such as the user pressing a button when they want the tube to turn. As well as implementing more interactive sensing which might give the user angle control in addition to speed control. We would also like to experiment with networking and other interfaces to increase the compatibility of the music we can create

Video: https://drive.google.com/file/d/1IrCWwKjV1mRumPzaRAGrWUcxnvIRX1Br/view?usp=sharing

# This assumes a Pololu DRV8833 dual motor driver has been wired up to the Pico as follows:
#   Pico pin 24, GPIO18   -&gt; AIN1
#   Pico pin 25, GPIO19   -&gt; AIN2
#   Pico pin 26, GPIO20   -&gt; BIN2
#   Pico pin 27, GPIO21   -&gt; BIN1
#   any Pico GND          -&gt; 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
from digitalio import DigitalInOut, Direction, Pull
 
import analogio
import digitalio
import random
 
#--------------------------------------------------------------------------------
# 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.
 
# Set up built-in green LED for output.
led = DigitalInOut(board.LED)  # GP25
led.direction = Direction.OUTPUT
 
# Set up an analog input on ADC0 (GP26), which is physically pin 31.
# E.g., this may be attached to photocell or photointerrupter with associated pullup resistor.
# sensor = analogio.AnalogIn(board.A0)

sensor_direction = analogio.AnalogIn(board.A1)
 
# These may be need to be adjusted for your particular hardware.  The Pico has
# 12-bit analog-to-digital conversion so the actual conversion has 4096 possible
# values, but the results are scaled to a 16-bit unsigned integer with range
# from 0 to 65535.
lower_threshold =  8000
upper_threshold = 45000
 

state_index = False
#switch = DigitalInOut(board.GP15)
#switch.direction = Direction.INPUT
 

last_change_time = time.time()
 
start_time = time.time()
print("Starting main script.")
change_action_time = 0
prev_direction = 1
SENSOR_THRESHOLD = 1000
state_index = False
while True:
    motor_power = 1

    sensor_direction_level = sensor_direction.value

    
    if state_index is False:
        if sensor_direction_level < lower_threshold:
            led.value = True
            state_index = True
            print("On")

    elif state_index is True:
        if sensor_direction_level &gt; upper_threshold:
            led.value = False
            state_index = False
            print("Off")
            prev_direction *= -1
            driver.write(1, prev_direction * motor_power)
            time.sleep(0.5)
    
    driver.write(1, prev_direction * motor_power)

     
   
    # uncomment the following to print tuples to plot with the mu editor
    # print((sensor_level, motor_power))
    # time.sleep(0.02)  # slow sampling to avoid flooding
]]>
https://courses.ideate.cmu.edu/16-223/f2021/work/2021/10/25/rain-box/feed/ 0
Dessert https://courses.ideate.cmu.edu/16-223/f2021/work/2021/10/24/dessert/ https://courses.ideate.cmu.edu/16-223/f2021/work/2021/10/24/dessert/#respond Mon, 25 Oct 2021 01:44:37 +0000 https://courses.ideate.cmu.edu/16-223/f2021/work/?p=5111

Gabriel Prado and Emilie Zhou

Description

Our main intent for this musical instrument was to allow users to be able to change the rhythm produced by the sound of the marbles and motors. The final outcome is an instrument with three components producing different sounds and users can interact with two of those components. When the program first starts, the metronome attached to the left motor produces a short sequence of beats that indicates the start of the program. The right motor is connected to a wheel-like container and a photoreceptor sensor that controls the direction the wheel spins in. The wheel also has a track on it that holds marbles and makes another sound when the marbles hit each other. The motor produces a continuous sound as it spins but when it changes directions, it also makes a deeper, grittier sound and the rhythm of that sound can be controlled by covering and uncovering the sensor. There’s also a tower with peg attachments and dropping marbles down the tower produces a pattern of clinking sounds as the marbles bounce off of the tower attachments. Half of the peg attachments have holes in them to provide sounds of a different pitch and all the peg attachments can be rearranged to produce a desired pattern of sounds. Our initial concept also included the metronome hitting a row of marbles to produce a clearer and more distinct sound but we decided to only have the metronome moving at the beginning of the program as the metronome arm was too strong and kept knocking the marbles off the board. 

]]>
https://courses.ideate.cmu.edu/16-223/f2021/work/2021/10/24/dessert/feed/ 0