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:
-
pwmioalong withadafruit_motorare used to control the motor speed and direction via the H-bridge driver -
analogiois used to read the slide potentiometer as an analog input -
touchiois 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_positionvalue - 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.