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

This guide was first published on Feb 07, 2020. It was last updated on Feb 07, 2020.

This page (CircuitPython Code Walkthrough) was last updated on Feb 04, 2020.

Text editor powered by tinymce.