Both the Feather nrF52840 and Circuit Playground Bluefruit are running their own code.py file. In this case, the Feather's code.py file could be considered the A file and the Circuit Playground Bluefruit's code.py file could be considered the B file. The Feather is in charge because the Circuit Playground Bluefruit is waiting for the button inputs to be able to do anything.
The Feather's code begins by setting up its digital inputs and outputs. The on-board blue LED will be used to indicate whether the boards have connected with each other via BLE. The rest of the pins are setup to be button inputs and are placed into the array switch_array[]
.
blue_led = digitalio.DigitalInOut(board.BLUE_LED) blue_led.direction = digitalio.Direction.OUTPUT switch_pins = [board.D5, board.D6, board.D9, board.D10, board.D11, board.D12, board.D13, board.A0, board.A1, board.A2, board.A3, board.A4] switch_array = [] for pin in switch_pins: switch_pin = digitalio.DigitalInOut(pin) switch_pin.direction = digitalio.Direction.INPUT switch_pin.pull = digitalio.Pull.UP switch_array.append(switch_pin)
Next, state machines are setup for the button debouncing. These states are then put into the array switches_pressed[]
.
switch1_pressed = False switch2_pressed = False switch3_pressed = False switch4_pressed = False switch5_pressed = False switch6_pressed = False switch7_pressed = False switch8_pressed = False switch9_pressed = False switch10_pressed = False switch11_pressed = False switch12_pressed = False switches_pressed = [switch1_pressed, switch2_pressed, switch3_pressed, switch4_pressed, switch5_pressed, switch6_pressed, switch7_pressed, switch8_pressed, switch9_pressed, switch10_pressed, switch11_pressed, switch12_pressed]
Going to the Circuit Playground Bluefruit, at the beginning of its code.py file, there is some setup for the NeoPixels Animations library. The Comet animation is being used to get a nice swirly effect each time a note is played.
COMET_SPEED = 0.04 # Lower numbers increase the animation speed CPB_COMET_TAIL_LENGTH = 5 # The length of the comet on the Circuit Playground Bluefruit CPB_COMET_BOUNCE = False # Set to True to make the comet "bounce" the opposite direction on CPB animations = AnimationSequence( AnimationGroup( Comet(cpb.pixels, COMET_SPEED, off, tail_length=CPB_COMET_TAIL_LENGTH, bounce=CPB_COMET_BOUNCE)))
Speaking of notes, the tones that will be played through the STEMMA Speaker when the buttons are pressed are then setup. Variables matching the note and octave are set to match the correct frequencies of the notes. These are then put into an array called note[]
.
C4 = 261.63 Csharp4 = 277.18 D4 = 293.66 Dsharp4 = 311.13 E4 = 329.63 F4 = 349.23 Fsharp4 = 369.99 G4 = 392 Gsharp4 = 415.3 A4 = 440 Asharp4 = 466.16 B4 = 493.88 note = [C4, Csharp4, D4, Dsharp4, E4, F4, Fsharp4, G4, Gsharp4, A4, Asharp4, B4]
This next portion has a bit of synergy between both code.py files. Previously it was mentioned that the Feather is sending BLE packets to the Circuit Playground Bluefruit, but what type? Color packets. This means that the CPB has to know which colors to be listening for. As a result, both the Feather and CPB have identical colors defined. These are then put into an array in the same order. It so happens that both files call this array color[]
. The colors being used are also from the Animations library. They're predefined in that library so that we don't have to worry about dialing in the exact RGB values.
color_C = color.RED color_Csharp = color.ORANGE color_D = color.YELLOW color_Dsharp = color.GREEN color_E = color.TEAL color_F = color.CYAN color_Fsharp = color.BLUE color_G = color.PURPLE color_Gsharp = color.MAGENTA color_A = color.GOLD color_Asharp = color.PINK color_B = color.WHITE color = [color_C, color_Csharp, color_D, color_Dsharp, color_E, color_F, color_Fsharp, color_G, color_Gsharp, color_A, color_Asharp, color_B]
The next portions for both files are some BLE setup and the beginning of the loop. At the start of the loop, BLE connections are made between the two boards and if the connection is present then they can proceed to the synth portions of the loop.
# CPB BLE portion ble = BLERadio() uart = UARTService() advertisement = ProvideServicesAdvertisement(uart) while True: ble.start_advertising(advertisement) # Start advertising. was_connected = False while not was_connected or ble.connected: if ble.connected: # If BLE is connected... was_connected = True animations.animate() if uart.in_waiting: # Check to see if any data is available from the Remote Control. try: packet = Packet.from_stream(uart) # Create the packet object. except ValueError: continue
# Feather BLE portion def send_packet(uart_connection_name, packet): """Returns False if no longer connected.""" try: uart_connection_name[UARTService].write(packet.to_bytes()) except: # pylint: disable=bare-except try: uart_connection_name.disconnect() except: # pylint: disable=bare-except pass return False return True ble = BLERadio() uart_connection = None if ble.connected: for connection in ble.connections: if UARTService in connection: uart_connection = connection break while True: blue_led.value = False if not uart_connection or not uart_connection.connected: # If not connected... print("Scanning...") for adv in ble.start_scan(ProvideServicesAdvertisement, timeout=5): # Scan... if UARTService in adv.services: # If UARTService found... print("Found a UARTService advertisement.") blue_led.value = True uart_connection = ble.connect(adv) # Create a UART connection... break # Stop scanning whether or not we are connected. ble.stop_scan() # And stop scanning.
For the Feather, there is a for
statement that iterates through the switch_array
, which holds the button pins. It defines the indexed position as i
. Then i
can be used to also track the debounce state and the color that is going to be sent as a color packet. All of these arrays have 12 indexes, one for each note that being used. This way, the indexes can easily be called out as needed from each array all at once without too many lines of code.
while uart_connection and uart_connection.connected: for switch_pin in switch_array: i = switch_array.index(switch_pin) switches_pressed_state = switches_pressed[i] colors = color[i]
Then there's a check to see if a button has been released with an if
statement. You'll see that if it has, that a button packet is sent. You'll see why this is included when you see the CPB's loop in a moment.
if switch_pin.value and switches_pressed_state: # On button release... print("button off") if not send_packet(uart_connection, # ColorPacket(colors)): ButtonPacket(ButtonPacket.RIGHT, pressed=True)): uart_connection = None continue switches_pressed[i] = False # Set to False. time.sleep(0.05)
After that though, there's a check to see if a button has been pressed. If it has, then a color packet is sent. The color depends on the indexed color that matches the indexed button pin.
if not switch_pin.value and not switches_pressed_state: # If button A pressed... if not send_packet(uart_connection, ColorPacket(colors)): uart_connection = None continue switches_pressed[i] = True # Set to True. time.sleep(0.05) # Debounce.
And that is how packets are being sent to the Circuit Playground Bluefruit. Now let's see what the Circuit Playground Bluefruit is doing when it receives those packets.
For the Circuit Playground Bluefruit, there is an if statement that checks if a color packet is being received. This is followed by a for
statement that iterates through the color
and note
arrays.
if isinstance(packet, ColorPacket): # If the packet is color packet... for i in range(12): colors = color[i] notes = note[i]
Then there's an if statement that checks to see if the color packet being sent by the Feather matches with any of the colors in our colors array. If it does, then a color is sent to the Comet animation and plays the matching tone. tone
is also being used as a state machine to debounce our CPB.
if packet.color == colors and not tone: animations.color = colors cpb.start_tone(notes) tone = True
Following that, the loop ends with an elif
statement. It's checking to see if a button packet
is coming in. Remember, the Feather was setup to send a button packet once a physical button is released.
If the button packet is a ButtonPacket.RIGHT
(which it will be), then the CPB will stop playing the tone. The animations will continue to animate but with the NeoPixels turned off.
off
was defined earlier to equal (0, 0, 0)
. If this portion of code wasn't included, then the last tone played would continue playing without stopping. The same would be the case for the NeoPixel animation.
elif isinstance(packet, ButtonPacket) and packet.pressed: # If the packet is a button packet... if packet.button == ButtonPacket.RIGHT and tone: # If button B is pressed... tone = False cpb.stop_tone() animations.color = off
Page last edited March 08, 2024
Text editor powered by tinymce.