Jason Perez, Gabriel Prado, Michael Nguyen
Statement
Our initial concept was to create a balancing game where the player had to keep as many marbles in the see-saw as they could. We decided to make an accelerometer the player input for controlling the see-saw movement. A DC motor was used to actuate the see-saw. We also wanted to include levels of difficulty for the player through random bias in the motor rotation that fought with the input. After our first iteration, we decided to add some feedback in the form of LEDs that signaled to the player the current difficulty. We also added a funnel that could be used to insert more marbles into the game. Our final outcome was a marble balancing game with 3 levels of difficulty that progress through time and increase the random bias in the system.
Code
# demo.py # Raspberry Pi Pico - Signal Processing Demo # Read an analog input with the ADC, apply various filters, and print filtered # data to the console for plotting. # Import CircuitPython modules. import board import math import pwmio import time import analogio import digitalio import random # Import every filter sample. These files should be copied to the top-level # directory of the CIRCUITPY filesystem on the Pico. import biquad import hysteresis import linear import median import smoothing import statistics #--------------------------------------------------------------- # Set up the hardware. # 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) red = digitalio.DigitalInOut(board.GP3) red.direction = digitalio.Direction.OUTPUT yellow = digitalio.DigitalInOut(board.GP2) yellow.direction = digitalio.Direction.OUTPUT green = digitalio.DigitalInOut(board.GP4) green.direction = digitalio.Direction.OUTPUT red.value = True yellow.value = True green.value = True #--------------------------------------------------------------- # Initialize filter objects as global variables. stats = statistics.CentralMeasures() hysteresis = hysteresis.Hysteresis(lower=0.25, upper=0.75) average = smoothing.MovingAverage() smoothing = smoothing.Smoothing() median = median.MedianFilter() lowpass = biquad.BiquadFilter(biquad.low_pass_10_1) highpass = biquad.BiquadFilter(biquad.high_pass_10_1) bandpass = biquad.BiquadFilter(biquad.band_pass_10_1) bandstop = biquad.BiquadFilter(biquad.band_stop_10_1) # Collect all the filters in a list for efficient multiple updates. all_filters = [ stats, hysteresis, average, smoothing, median, lowpass, highpass, bandpass, bandstop, ] #--------------------------------------------------------------- # Run the main event loop. # Use the high-precision clock to regulate a precise *average* sampling rate. sampling_interval = 100000000 # 0.1 sec period of 10 Hz in nanoseconds next_sample_time = time.monotonic_ns() #-------------------------------------------------------------------------------- # 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 red_time = 15 yellow_time = 5 driver = DRV8833() startTime = time.time() while True: # read the current nanosecond clock now = time.monotonic_ns() current = time.time() - startTime if current < yellow_time: k_random = 0.00 green.value = True red.value = False yellow.value = False elif current < red_time: k_random = 0.3 green.value = False red.value = False yellow.value = True else: k_random = 0.6 green.value = False red.value = True yellow.value = False if now >= next_sample_time: # Advance the next event time; by spacing out the timestamps at precise # intervals, the individual sample times may have 'jitter', but the # average rate will be exact. next_sample_time += sampling_interval # Read the sensor once per sampling cycle. raw = sensor.value # Apply calibration to map integer ADC values to meaningful units. The # exact scaling and offset will depend on both the individual device and # application. calib = linear.map(raw, 59000, 4000, -4.0, 4.0) + 0.19 # Pipe the calibrated value through all the filters. #filtered = [filt.update(calib) for filt in all_filters] #filtered = median.update(calib) filtered = calib if filtered > -0.09 and filtered < 0.09: out = 0 else: out = max(math.log(abs(filtered) + 0.9)+ 0.4, 0.55) out = min(out, 0.9) * (filtered / (abs(filtered) + 1e-10)) # Selectively report results for plotting. # print((raw, calib)) # raw and calibrated input signal # print((calib, filtered[0][0], filtered[0][1])) # calibrated, average, variance # print((calib, 1*filtered[1], filtered[2])) # calibrated, thresholded, moving average print((calib, filtered, out)) # calibrated, smoothed, median-filtered # print((calib, filtered[5], filtered[6])) # calibrated, low-pass, high-pass # print((calib, filtered[7], filtered[8])) # calibrated, band-pass, band-stop rand_bias = 2*random.random() - 1; #random number from -1 to 1 if out == 0 and current >= red_time: driver.write(1,rand_bias) else: driver.write(1, k_random * rand_bias + (1 - k_random) * -out )
Leave a Reply
You must be logged in to post a comment.