Setup
Set up the Feather Sense for use with CircuitPython by following this guide.
To use with CircuitPython, you need to first install a few libraries, into the lib folder on your CIRCUITPY drive. Then you need to update code.py with the example script.
Thankfully, we can do this in one go. In the example below, click the Download Project Bundle button below to download the necessary libraries and the code.py file in a zip file. Extract the contents of the zip file, open the directory PowerGlove_BLE_MIDI/ and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your CIRCUITPY drive.
CIRCUITPY

Here's how the code will work:
- On start, it will advertise itself as a Bluetooth Low Energy device named "Power Glove MIDI" with the BLE_MIDI service available, and wait for a connection.
- When a connection is made, it will read the Feather's accelerometer and the four flex sensors as analog inputs.
- These sensor readings are converted to values of 0-127
- The six sensor values are sent via BLE MIDI as continuous controller (CC) numbers 7, 70, 71, 75, and 77 over MIDI channel 1.
- This repeats continuously until the connection is broken or the Power Glove is powered off.
Code
Copy the code from the code-block below and paste it into the Mu editor and save it to your Feather as code.py (or copy code.py from the zip file and place on the CIRCUITPY drive).
# SPDX-FileCopyrightText: 2021 John Park for Adafruit Industries. # # SPDX-License-Identifier: MIT """ Power Glove BLE MIDI with Feather Sense nRF52840 Sends MIDI CC values based on finger flex sensors and accelerometer """ import time import board import adafruit_ble from adafruit_ble.advertising.standard import ProvideServicesAdvertisement import adafruit_ble_midi import adafruit_midi from adafruit_midi.control_change import ControlChange # from adafruit_midi.note_on import NoteOn # from adafruit_midi.pitch_bend import PitchBend import adafruit_lsm6ds.lsm6ds33 # accelerometer import simpleio from analogio import AnalogIn i2c = board.I2C() # uses board.SCL and board.SDA # i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller sense_accel = adafruit_lsm6ds.lsm6ds33.LSM6DS33(i2c) analog_in_thumb = AnalogIn(board.A3) analog_in_index = AnalogIn(board.A2) analog_in_middle = AnalogIn(board.A1) analog_in_ring = AnalogIn(board.A0) # Pick your MIDI CC numbers here cc_x_num = 7 # volume cc_y_num = 70 # unassigned cc_thumb_num = 71 # unassigned cc_index_num = 75 # unassigned cc_middle_num = 76 # unassigned cc_ring_num = 77 # unassigned midi_channel = 1 # pick your midi out channel here # Use default HID descriptor midi_service = adafruit_ble_midi.MIDIService() advertisement = ProvideServicesAdvertisement(midi_service) ble = adafruit_ble.BLERadio() if ble.connected: for c in ble.connections: c.disconnect() midi = adafruit_midi.MIDI(midi_out=midi_service, out_channel=midi_channel - 1) print("advertising") ble.name="Power Glove MIDI" ble.start_advertising(advertisement) # reads an analog pin and returns value remapped to out range, e.g., 0-127 def get_flex_cc(sensor, low_in, high_in, min_out, max_out): flex_raw = sensor.value flex_cc = simpleio.map_range(flex_raw, low_in, high_in, min_out, max_out) flex_cc = int(flex_cc) return flex_cc debug = False # set debug mode True to test raw values, set False to run BLE MIDI while True: if debug: accel_data = sense_accel.acceleration # get accelerometer reading accel_x = accel_data[0] accel_y = accel_data[1] accel_z = accel_data[2] print( "x:{} y:{} z:{} thumb:{} index:{} middle:{} ring:{}".format( accel_x, accel_y, accel_x, analog_in_thumb.value, analog_in_index.value, analog_in_middle.value, analog_in_ring.value, ) ) time.sleep(0.2) else: print("Waiting for connection") while not ble.connected: pass print("Connected") while ble.connected: # Feather Sense accelerometer readings to CC accel_data = sense_accel.acceleration # get accelerometer reading accel_x = accel_data[0] accel_y = accel_data[1] # accel_z = accel_data[2] # Remap analog readings to cc range cc_x = int(simpleio.map_range(accel_x, 0, 9, 127, 0)) cc_y = int(simpleio.map_range(accel_y, 1, -9, 0, 127)) cc_thumb = get_flex_cc(analog_in_thumb, 49000, 35000, 127, 0) cc_index = get_flex_cc(analog_in_index, 50000, 35000, 0, 127) cc_middle = get_flex_cc(analog_in_middle, 55000, 40000, 0, 127) cc_ring = get_flex_cc(analog_in_ring, 55000, 42000, 0, 127) ''' print( "CC_X:{} CC_Y:{} CC_Thumb:{} CC_Index:{} CC_Middle:{} CC_Ring:{}".format( cc_x, cc_y, cc_thumb, cc_index, cc_middle, cc_ring ) )''' # send all the midi messages in a list midi.send( [ ControlChange(cc_x_num, cc_x), ControlChange(cc_y_num, cc_y), ControlChange(cc_thumb_num, cc_thumb), ControlChange(cc_index_num, cc_index), ControlChange(cc_middle_num, cc_middle), ControlChange(cc_ring_num, cc_ring), ] ) # If you want to send NoteOn or Pitch Bend, here are examples: # midi.send(NoteOn(44, 120)) # G sharp 2nd octave # a_pitch_bend = PitchBend(random.randint(0, 16383)) # midi.send(a_pitch_bend) print("Disconnected") print() ble.start_advertising(advertisement)
Libraries
First we'll import the necessary libraries. These include time
, board
, adafruit_lsm6ds
, simpleio
, and analogio
to deal with pauses, board hardware access, LSM6DS accelerometer reading, IO, and analog reads.
We also import adafruit_ble
, adafruit_ble_midi
, and adafruit_midi
to enable Bluetooth LE control, sending BLE MIDI messages, and general MIDI commands.
import time import board import adafruit_ble from adafruit_ble.advertising.standard import ProvideServicesAdvertisement import adafruit_ble_midi import adafruit_midi from adafruit_midi.control_change import ControlChange # from adafruit_midi.note_on import NoteOn # from adafruit_midi.pitch_bend import PitchBend import adafruit_lsm6ds # accelerometer import simpleio from analogio import AnalogIn
i2c = board.I2C() sense_accel = adafruit_lsm6ds.LSM6DS33(i2c)
Analog Read Pins
The finger flex sensors will be read on four analog input pins -- we'll set them up with nice names.
analog_in_thumb = AnalogIn(board.A3) analog_in_index = AnalogIn(board.A2) analog_in_middle = AnalogIn(board.A1) analog_in_ring = AnalogIn(board.A0)
MIDI CCs and Channel
We'll create variables for each of the six sensors we'll be reading to assign them to MIDI CC numbers.
Some CC numbers have standardized assignments on most synthesizers and synth software, while others are wide open to your own assignments. Here's a good list to consult. I chose:
- For Power Glove tilt I chose CC 7, which is typically used for overall volume.
- For Power Glove roll, CC 70, which is a vague "sound variation" control, but you should be able to assign as you like.
- Thumb flex is CC 71, typically filter resonance.
- Index flex is CC 75, generic, so you can assign freely.
- Middle flex is CC 76, generic.
- Ring flex is CC 77, generic.
We'll also set the MIDI output to channel 1. You can pick any channel from 1-16 depending on your needs.
cc_x_num = 7 # volume cc_y_num = 70 # unassigned cc_thumb_num = 71 # unassigned cc_index_num = 75 # unassigned cc_middle_num = 76 # unassigned cc_ring_num = 77 # unassigned midi_channel = 1 # pick your midi out channel here
MIDI BLE Setup
Now we will set up the BLE MIDI service, and set the BLE radio to advertise the device, with the name "Power Glove MIDI".
midi_service = adafruit_ble_midi.MIDIService() advertisement = ProvideServicesAdvertisement(midi_service) ble = adafruit_ble.BLERadio() if ble.connected: for c in ble.connections: c.disconnect() midi = adafruit_midi.MIDI(midi_out=midi_service, out_channel=midi_channel - 1) print("advertising") ble.name="Power Glove MIDI" ble.start_advertising(advertisement)
Get Flex CC Function
To enable reading of the flex sensors and converting their values from a 16-bit analog read to a 7-bit MIDI CC value, we'll create a function called get_flex_cc()
. This function takes five arguments: sensor name to read, low input value, high input value, minimum output value, and maximum output value.
When it is called it will return a CC value somewhere between 0-127.
def get_flex_cc(sensor, low_in, high_in, min_out, max_out): flex_raw = sensor.value flex_cc = simpleio.map_range(flex_raw, low_in, high_in, min_out, max_out) flex_cc = int(flex_cc) return flex_cc
Debug
In order to facilitate a type of manual sensor calibration you can turn the debug
variable from False
to True
.
In the main loop of the program, when debug
is True
the raw accelerometer and flex sensor data will print to the serial port (REPL in Mu). This way you can check the value ranges and adjust the ranges in the main code to match.
debug = False # set debug mode True to test raw values, set False to run BLE MIDI while True: if debug: accel_data = sense_accel.acceleration # get accelerometer reading accel_x = accel_data[0] accel_y = accel_data[1] accel_z = accel_data[2] print( "x:{} y:{} z:{} thumb:{} index:{} middle:{} ring:{}".format( accel_x, accel_y, accel_x, analog_in_thumb.value, analog_in_index.value, analog_in_middle.value, analog_in_ring.value, ) ) time.sleep(0.2)
Wait for Connection
When we're out of debug mode, the first thing that happens in the main loop is that the BLE connection will be tested. It will remain in this state until a connection is made.
print("Waiting for connection") while not ble.connected: pass print("Connected")
Connected
Once the Feather has connected to a BLE MIDI device, we proceed with checking all of the sensors.
First, the accelerometer values are read, and then the x (tilt) and y (roll) values are remapped as integers between 0-127 and cast to a pair of variables named cc_x
and cc_y
.
You can see here that the cc_x
is set to send a value of 127
when the accelerometer x-axis reads 0
, meaning the volume will be full when the Power Glove is parallel to the ground. When you tilt the Power Glove up toward the sky, the accelerometer x-axis will read around 9
, and the CC value will be set to 0
, so the volume will be turn down to nothing.
accel_data = sense_accel.acceleration # get accelerometer reading accel_x = accel_data[0] accel_y = accel_data[1] # accel_z = accel_data[2] # Remap analog readings to cc range cc_x = int(simpleio.map_range(accel_x, 0, 9, 127, 0)) cc_y = int(simpleio.map_range(accel_y, 1, -9, 0, 127))
If you wanted to reverse the tilt/volume behavior you could specify the remapping as: cc_x = int(simpleio.map_range(accel_x, 0, 9, 0, 127))
Flex
Then, the four variables for the flex sensors are cast with the remapped values returned by the get_flex_cc()
function.
Note the values for the upper and lower ranges were determined first by running the program in debug mode and writing down those values.
Again, you can swap the "knob direction" of any of these by swapping the min-max CC values called from 0-127 or 127-0.
cc_thumb = get_flex_cc(analog_in_thumb, 49000, 35000, 127, 0) cc_index = get_flex_cc(analog_in_index, 50000, 35000, 0, 127) cc_middle = get_flex_cc(analog_in_middle, 55000, 40000, 0, 127) cc_ring = get_flex_cc(analog_in_ring, 55000, 42000, 0, 127)
Send MIDI Message
With all of the CC values read, we'll now send a series of 8-bit MIDI messages. You can call the midi.send()
command individually for each MIDI message, or call the command as a list. (They'll all still be sent as individual MIDI messages.)
midi.send( [ ControlChange(cc_x_num, cc_x), ControlChange(cc_y_num, cc_y), ControlChange(cc_thumb_num, cc_thumb), ControlChange(cc_index_num, cc_index), ControlChange(cc_middle_num, cc_middle), ControlChange(cc_ring_num, cc_ring), ] )
These will be transmitted in near real-time to the MIDI device, but not instantaneously -- BLE MIDI latency runs between 7.5 ms-22 ms typically. This isn't really noticeable with most CC parameters or playing notes with soft attacks, but could be bothersome with percussive, sharp attack NoteOn messages.
Optional Message Types
If you uncomment the necessary library imports, you can try out the NoteOn and PitchBend message types.
These lines of commented code show the essentials.
# midi.send(NoteOn(44, 120)) # G sharp 2nd octave # a_pitch_bend = PitchBend(random.randint(0, 16383)) # midi.send(a_pitch_bend)
Disconnected
If the connection is broken, the ble.start_advertising(advertisement)
will begin again.
print("Disconnected") print() ble.start_advertising(advertisement)
Page last edited January 21, 2025
Text editor powered by tinymce.