Now that CircuitPython is installed on the CPB, we can move on to installing the project software.
The project code requires two code libraries to assist with Bluetooth communication. Click the link below to download the CircuitPython library bundle:
Unzip the library bundle, and open the lib folder inside.
Locate the folder named adafruit_bluefruit_connect and the file named neopixel.mpy – copy them both and paste them into the CIRCUITPY drive's lib folder.
Unzip the downloaded zip file, and open the lib folder inside. Copy the folder named adafruit_ble and paste it into the CIRCUITPY drive's lib folder.
Your CIRCUITPY drive's file structure should now look like this:
Upload code
Copy the code below and paste it into a new text file.
Save the text file as code.py to the root of the CIRCUITPY drive.
""" LED Disco Tie with Bluetooth ========================================================= Give your suit an sound-reactive upgrade with Circuit Playground Bluefruit & Neopixels. Set color and animation mode using the Bluefruit LE Connect app. Author: Collin Cunningham for Adafruit Industries, 2019 """ # pylint: disable=global-statement import time import array import math import audiobusio import board import neopixel from adafruit_ble.uart_server import UARTServer from adafruit_bluefruit_connect.packet import Packet from adafruit_bluefruit_connect.color_packet import ColorPacket from adafruit_bluefruit_connect.button_packet import ButtonPacket uart_server = UARTServer() # User input vars mode = 0 # 0=audio, 1=rainbow, 2=larsen_scanner, 3=solid user_color= (127,0,0) # Audio meter vars PEAK_COLOR = (100, 0, 255) NUM_PIXELS = 10 CURVE = 2 SCALE_EXPONENT = math.pow(10, CURVE * -0.1) NUM_SAMPLES = 160 # Restrict value to be between floor and ceiling. def constrain(value, floor, ceiling): return max(floor, min(value, ceiling)) # Scale input_value between output_min and output_max, exponentially. def log_scale(input_value, input_min, input_max, output_min, output_max): normalized_input_value = (input_value - input_min) / \ (input_max - input_min) return output_min + \ math.pow(normalized_input_value, SCALE_EXPONENT) \ * (output_max - output_min) # Remove DC bias before computing RMS. def normalized_rms(values): minbuf = int(mean(values)) samples_sum = sum( float(sample - minbuf) * (sample - minbuf) for sample in values ) return math.sqrt(samples_sum / len(values)) def mean(values): return sum(values) / len(values) def volume_color(volume): return 200, volume * (255 // NUM_PIXELS), 0 # Set up NeoPixels and turn them all off. pixels = neopixel.NeoPixel(board.A1, NUM_PIXELS, brightness=0.1, auto_write=False) pixels.fill(0) pixels.show() mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA, sample_rate=16000, bit_depth=16) # Record an initial sample to calibrate. Assume it's quiet when we start. samples = array.array('H', [0] * NUM_SAMPLES) mic.record(samples, len(samples)) # Set lowest level to expect, plus a little. input_floor = normalized_rms(samples) + 10 # Corresponds to sensitivity: lower means more pixels light up with lower sound input_ceiling = input_floor + 500 peak = 0 def wheel(wheel_pos): # Input a value 0 to 255 to get a color value. # The colours are a transition r - g - b - back to r. if wheel_pos < 0 or wheel_pos > 255: r = g = b = 0 elif wheel_pos < 85: r = int(wheel_pos * 3) g = int(255 - wheel_pos*3) b = 0 elif wheel_pos < 170: wheel_pos -= 85 r = int(255 - wheel_pos*3) g = 0 b = int(wheel_pos*3) else: wheel_pos -= 170 r = 0 g = int(wheel_pos*3) b = int(255 - wheel_pos*3) return (r, g, b) def rainbow_cycle(delay): for j in range(255): for i in range(NUM_PIXELS): pixel_index = (i * 256 // NUM_PIXELS) + j pixels[i] = wheel(pixel_index & 255) pixels.show() time.sleep(delay) def audio_meter(new_peak): mic.record(samples, len(samples)) magnitude = normalized_rms(samples) # Compute scaled logarithmic reading in the range 0 to NUM_PIXELS c = log_scale(constrain(magnitude, input_floor, input_ceiling), input_floor, input_ceiling, 0, NUM_PIXELS) # Light up pixels that are below the scaled and interpolated magnitude. pixels.fill(0) for i in range(NUM_PIXELS): if i < c: pixels[i] = volume_color(i) # Light up the peak pixel and animate it slowly dropping. if c >= new_peak: new_peak = min(c, NUM_PIXELS - 1) elif new_peak > 0: new_peak = new_peak - 1 if new_peak > 0: pixels[int(new_peak)] = PEAK_COLOR pixels.show() return new_peak pos = 0 # position direction = 1 # direction of "eye" def larsen_set(index, color): if index < 0: return else: pixels[index] = color def larsen(delay): global pos global direction color_dark = (int(user_color[0]/8), int(user_color[1]/8), int(user_color[2]/8)) color_med = (int(user_color[0]/2), int(user_color[1]/2), int(user_color[2]/2)) larsen_set(pos - 2, color_dark) larsen_set(pos - 1, color_med) larsen_set(pos, user_color) larsen_set(pos + 1, color_med) if (pos + 2) < NUM_PIXELS: # Dark red, do not exceed number of pixels larsen_set(pos + 2, color_dark) pixels.write() time.sleep(delay) # Erase all and draw a new one next time for j in range(-2, 2): larsen_set(pos + j, (0, 0, 0)) if (pos + 2) < NUM_PIXELS: larsen_set(pos + 2, (0, 0, 0)) # Bounce off ends of strip pos += direction if pos < 0: pos = 1 direction = -direction elif pos >= (NUM_PIXELS - 1): pos = NUM_PIXELS - 2 direction = -direction def solid(new_color): pixels.fill(new_color) pixels.show() def map_value(value, in_min, in_max, out_min, out_max): out_range = out_max - out_min in_range = in_max - in_min return out_min + out_range * ((value - in_min) / in_range) speed = 6.0 wait = 0.097 def change_speed(mod, old_speed): new_speed = constrain(old_speed + mod, 1.0, 10.0) return(new_speed, map_value(new_speed, 10.0, 0.0, 0.01, 0.3)) while True: # While BLE is *not* connected if not uart_server.connected: # OK to call again even if already advertising uart_server.start_advertising() # While BLE is connected else: if uart_server.in_waiting: packet = Packet.from_stream(uart_server) # Received ColorPacket if isinstance(packet, ColorPacket): user_color = packet.color # Received ButtonPacket elif isinstance(packet, ButtonPacket): if packet.pressed: if packet.button == ButtonPacket.UP: speed, wait = change_speed(1, speed) elif packet.button == ButtonPacket.DOWN: speed, wait = change_speed(-1, speed) elif packet.button == ButtonPacket.BUTTON_1: mode = 0 elif packet.button == ButtonPacket.BUTTON_2: mode = 1 elif packet.button == ButtonPacket.BUTTON_3: mode = 2 elif packet.button == ButtonPacket.BUTTON_4: mode = 3 # Determine animation based on mode if mode == 0: peak = audio_meter(peak) elif mode == 1: rainbow_cycle(0.001) elif mode == 2: larsen(wait) elif mode == 3: solid(user_color)
Once the project code is saved to CIRCUITPY as code.py, the software is all set – time to move on to assembling the hardware.