Text Editor
Adafruit recommends using the Mu editor for editing your CircuitPython code. You can get more info in this guide.
Alternatively, you can use any text editor that saves simple text files.
Download the Project Bundle
Your project will use a specific set of CircuitPython libraries and the code.py file. To get everything you need, click on the Download Project Bundle link below, and uncompress the .zip file.
Drag the contents of the uncompressed bundle directory onto your Feather board's CIRCUITPY drive, replacing any existing files or directories with the same names, and adding any new ones that are necessary.
# SPDX-FileCopyrightText: 2022 John Park for Adafruit Industries # SPDX-License-Identifier: MIT # Motorized fader demo import time import board import pwmio import analogio import touchio import neopixel from digitalio import DigitalInOut, Pull from adafruit_debouncer import Debouncer from adafruit_motor import motor MIDI_DEMO = False # set to True to send MIDI CC # optional MIDI setup if MIDI_DEMO: import usb_midi import adafruit_midi from adafruit_midi.control_change import ControlChange midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0) fader_cc_number = 16 # Button setup to store four saved values button_pins = (board.D10, board.D9, board.D6, board.D5) buttons = [] for button_pin in button_pins: tmp_pin = DigitalInOut(button_pin) tmp_pin.pull = Pull.UP buttons.append(Debouncer(tmp_pin)) saved_positions = (230, 180, 120, 60) # pre-saved positions for the buttons to call # Slide pot setup fader = analogio.AnalogIn(board.A0) fader_position = fader.value # ranges from 0-65535 fader_pos = fader.value // 256 # make 0-255 range last_fader_pos = fader_pos # Motor setup PWM_PIN_A = board.D12 # pick any pwm pins on their own channels PWM_PIN_B = board.D11 # DC motor driver setup -- these pins go to h-bridge driver such as L9110 pwm_a = pwmio.PWMOut(PWM_PIN_A, frequency=50) pwm_b = pwmio.PWMOut(PWM_PIN_B, frequency=50) motor1 = motor.DCMotor(pwm_a, pwm_b) # Touch setup pin goes from touch pin on slide pot to touch capable pin and then 1MΩ to gnd touch = touchio.TouchIn(board.A3) touch.threshold = touch.raw_value + 30 # tune for fader knob cap # NeoPixel setup led = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2, auto_write=True) led.fill(0xff0000) def clamp(num, min_value, max_value): # function for clamping motor throttle -1.0 to 1.0 return max(min(num, max_value), min_value) def go_to_position(new_position): global fader_pos # pylint: disable=global-statement fader_pos = int(fader.value//256) while abs(fader_pos - new_position) > 2 : if fader_pos > new_position : speed = 2.25 * abs(fader_pos - new_position) / 256 + 0.12 speed = clamp(speed, -1.0, 1.0) motor1.throttle = speed led[0] = (fader_pos, 0, 0) if MIDI_DEMO: global fader_cc # pylint: disable=global-statement fader_cc = int(fader_pos / 2) # cc is 0-127 midi.send(ControlChange(fader_cc_number, fader_cc)) if fader_pos < new_position: speed = -2.25 * abs(fader_pos - new_position) / 256 - 0.12 speed = clamp(speed, -1.0, 1.0) motor1.throttle = speed led[0] = (fader_pos, 0, 0) if MIDI_DEMO: fader_cc = int(fader_pos / 2) # cc is 0-127 midi.send(ControlChange(fader_cc_number, fader_cc)) fader_pos = int(fader.value//256) motor1.throttle = None print("--__ Flying Fader Demo __--") print("\n"*4) go_to_position(saved_positions[3]) # boot up demo go_to_position(saved_positions[0]) time.sleep(.6) current_saved_position = 0 # state to store which is current position from the list while True: for i in range(len(buttons)): buttons[i].update() if buttons[i].fell: # if a button is pressed, update the position from list current_saved_position = i if touch.value: motor1.throttle = None # idle else: go_to_position(saved_positions[current_saved_position]) filter_amt = 0.1 # higher number will be a slower filter between 1.0 and 0.1 is good fader_pos = int((filter_amt * last_fader_pos) + ((1.0-filter_amt) * fader.value//256)) led[0] = (fader_pos, 0, 0) if abs(fader_pos - last_fader_pos) > 1 : # do things in here, e.g. send MIDI CC fader_width = 90 # for text visualization in serial output print("-" * (fader_width - int(fader_pos/3)), fader_pos, "-" * int(fader_pos/3), end='\r') last_fader_pos = fader_pos if MIDI_DEMO: fader_cc = int(fader_pos / 2) # cc is 0-127 midi.send(ControlChange(fader_cc_number, fader_cc))
Use the Flying Fader
Once the code is up and running, allow it to run through the boot up sequence that will move the fader to a couple of position presets, and it will also calibrate the touch sensor. Be sure not to touch the potentiometer during this start up.
Then, you can press any of the four buttons to go to a preset location, or move the fader -- you'll feel the motor is disengaged until you let go, then it will move back to the last preset position!
How It Works
Libraries
First, the code imports the necessary libraries. In particular:
-
pwmio
along withadafruit_motor
are used to control the motor speed and direction via the H-bridge driver -
analogio
is used to read the slide potentiometer as an analog input -
touchio
is used to detect when the potentiometer lever is being touched
import time import board import pwmio import analogio import touchio import neopixel from digitalio import DigitalInOut, Pull from adafruit_debouncer import Debouncer from adafruit_motor import motor
Button Setup
Button setup is optional (you can use the motorized slide pot without buttons) but it's nice to have some buttons to press to go to preset positions. You could also repurpose these to record new presets if you like.
button_pins = (board.D10, board.D9, board.D6, board.D5) buttons = [] for button_pin in button_pins: tmp_pin = DigitalInOut(button_pin) tmp_pin.pull = Pull.UP buttons.append(Debouncer(tmp_pin))
The saved_positions
list is a set of integer numbers that can be recalled by pressing buttons. Change these to whatever you like.
saved_positions = (230, 180, 120, 60)
Slide Potentiometer Setup
The slide potentiometer is set up as an analog input using analogio on pin A0 (an ADC pin is required) named fader
.
By reading the pin value (fader.value
) a 16-bit value is returned -- we then convert this to a 0-255 range for use in the code (the ADC is usually 12- or 10-bit, so the 16-bit value tends to be too noisy in practice. The 8-bit works very well.)
A state variable called last_fader_pos
is created so we can detect when the pot position is changed.
fader = analogio.AnalogIn(board.A0) fader_position = fader.value # ranges from 0-65535 fader_pos = fader.value // 256 # make 0-255 range last_fader_pos = fader_pos
Motor Setup
The motor is a DC motor that can be controlled to go in either direction and at varying speed by using a full H-bridge driver, in this case the L9910.
To do this, we'll use two PWM (pulse width modulation) pins from the microcontroller which act as a sort of pseudo analog output.
The two PWM objects are created with a frequency of 50Hz (more on tuning PWM for DC motors in this guide), and then the motor1 object is created using motor.DCMotor()
.
PWM_PIN_A = board.D12 PWM_PIN_B = board.D11 pwm_a = pwmio.PWMOut(PWM_PIN_A, frequency=50) pwm_b = pwmio.PWMOut(PWM_PIN_B, frequency=50) motor1 = motor.DCMotor(pwm_a, pwm_b)
Touch Setup
Next, the capacitive touch sense for the pot lever is set up. This requires a touch-capable pin.
You can fine-tune the touch threshold if you want, or use the setting that is self-calibrated during setup.
touch = touchio.TouchIn(board.A3) touch.threshold = touch.raw_value + 30
Clamp Function
This function is created to simply clamp the motor throttle values between -1.0 and 1.0.
def clamp(num, min_value, max_value): return max(min(num, max_value), min_value)
Go_to_position Function
This function is called whenever the fader needs to fly to a new position!
The new_position
argument can be a value from 0-255.
The way this works is a sort of PD (position-derivative) feedback loop. Essentially, the motor moves a tiny bit, then the slide potentiometer value is read. The difference between the current position and the new position will continue to decrease, approaching zero, as the motor moves the slide to the goal.
To avoid overshoot, the throttle value is decreased as the slider gets closer to the goal, sort of like slowing a car by letting off the gas before hitting the brakes at a stop sign.
def go_to_position(new_position): global fader_pos fader_pos = int(fader.value//256) while abs(fader_pos - new_position) > 2 : if fader_pos > new_position : speed = 2.25 * abs(fader_pos - new_position) / 256 + 0.12 speed = clamp(speed, -1.0, 1.0) motor1.throttle = speed led[0] = (fader_pos, 0, 0) if fader_pos < new_position: speed = -2.25 * abs(fader_pos - new_position) / 256 - 0.12 speed = clamp(speed, -1.0, 1.0) motor1.throttle = speed led[0] = (fader_pos, 0, 0) fader_pos = int(fader.value//256) motor1.throttle = None
Main Loop
The main loop of the program does three essential things:
- Check for button presses, which change the
current_saved_position
value - Check for touch sense on the potentiometer lever, which disables the motor throttle to allow free movement. When the touch is removed, the
go_to_positon()
function is called to return the fader to the last position - Watch for changes to the potentiometer value -- this can be both when manually moving the fader or when the motor is driving it. When the fader is slid, the analog values are filtered to remove noise/jitter and then anything you want to happen, happens! This can be LED fading, MIDI output, etc.
for i in range(len(buttons)): buttons[i].update() if buttons[i].fell: # if a button is pressed, update the position from list current_saved_position = i if touch.value: motor1.throttle = None # idle else: go_to_position(saved_positions[current_saved_position]) filter_amt = 0.1 # higher number will be a slower filter between 1.0 and 0.1 is good fader_pos = int((filter_amt * last_fader_pos) + ((1.0-filter_amt) * fader.value//256)) led[0] = (fader_pos, 0, 0) if abs(fader_pos - last_fader_pos) > 1 : # do things in here, e.g. send MIDI CC fader_width = 90 # for text visualization in serial output print("-" * (fader_width - int(fader_pos/3)), fader_pos, "-" * int(fader_pos/3), end='\r') last_fader_pos = fader_pos
Code Customization
The demo code includes an option to send MIDI CC values. To enable this, simply change the line:
MIDI_DEMO = False
to
MIDI_DEMO = True
This will allow the code to load the necessary MIDI libraries and then send 0-127 values over MIDI channel 1 CC number 16. (You can change those settings as well if you like.)
Try firing up a software synthesizer and set a synth parameter to be controlled by your flying fader!
Page last edited January 22, 2025
Text editor powered by tinymce.