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, plus 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 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 and Tod Kurt for Adafruit Industries
# SPDX-License-Identifier: MIT
# Darth Faders motorized slide potentiometer sculpture/prop
import time
import board
import analogio
from adafruit_debouncer import Debouncer
from adafruit_motorkit import MotorKit
from adafruit_seesaw import seesaw, rotaryio, digitalio
num_faders = 3
i2c = board.I2C() # uses board.SCL and board.SDA
# i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller
motorwing = MotorKit(i2c=i2c)
motorwing.frequency = 122 # tune this 50 - 200 range
max_throttle = 0.18 # tune this 0.2 - 1 range
# make arrays for all the things we care about
motors = [None] * num_faders
faders = [None] * num_faders
faders_pos = [None] * num_faders
last_faders_pos = [None] * num_faders
# set up motors in motor array
motors[0] = motorwing.motor1
motors[1] = motorwing.motor2
motors[2] = motorwing.motor3
# set motors to "off"
for i in range(num_faders):
motors[i].throttle = None
# STEMMA QT Rotary encoder setup
seesaw = seesaw.Seesaw(i2c, addr=0x36) # default address is 0x36
seesaw.pin_mode(24, seesaw.INPUT_PULLUP)
button_in = digitalio.DigitalIO(seesaw, 24)
button = Debouncer(button_in)
encoder = rotaryio.IncrementalEncoder(seesaw)
last_encoder_pos = 0
# set up faders
fader_pins = (board.A0, board.A1, board.A2)
for i in range(num_faders):
faders[i] = analogio.AnalogIn(fader_pins[i])
faders_pos[i] = faders[i].value // 256 # make it 0-255 range
last_faders_pos[i] = faders_pos[i]
def update_position(fader, new_position, speed):
global faders_pos # pylint: disable=global-statement
faders_pos[fader] = int(faders[fader].value//256)
if abs(faders_pos[fader] - new_position) > 2 :
if faders_pos[fader] > new_position :
motors[fader].throttle = speed
if faders_pos[fader] < new_position:
motors[fader].throttle = speed * -1
faders_pos[fader] = int(faders[fader].value//256)
if faders_pos[fader] == new_position:
motors[fader].throttle = None
# pre-saved positions for the buttons to call
H = 240 # high
M = 127 # mid
L = 10 # low
# create custom animation patterns here:
saved_positions = (
(M, H, L, L, H, M, L), # fader 1
(M, H, L, H, H, M, L), # fader 2
(M, L, L, H, H, M, L) # fader 3
)
num_positions = len(saved_positions[0]) # how many moves in our move list
position = 0 # which column of our 'saved_position' move list we're currently on
last_time = time.monotonic()
period = 4 # time in seconds between new desitnation picks
print('Darth Fader will see you know.')
move_state = False
encoder_delta = 0
while True:
button.update()
if button.fell:
move_state = not move_state
print("move_state is " + str(move_state))
if not move_state:
for i in range(num_faders):
motors[i].throttle = None
# always move all motors toward destinations
for i in range(num_faders):
update_position(i, saved_positions[i][position], max_throttle)
# has encoder been turned?
encoder_pos = -encoder.position
if encoder_pos != last_encoder_pos:
encoder_delta = encoder_pos - last_encoder_pos
last_encoder_pos = encoder_pos
# if we are not moving automatically, allow encoder tuning animation frames
if not move_state:
if encoder_delta: # encoder was turned
direction = 1 if (encoder_delta > 0) else -1 # which direction encoder was turned
position = (position + direction) % num_positions # increment/decrement
encoder_delta = 0
# else we are moving automatically
if move_state:
# if it is time to go to a new destination, do it
if time.monotonic() - last_time > period: # after 'period' seconds, change
last_time = time.monotonic()
position = (position + 1) % num_positions
How It Works
The Darth Faders have two modes -- free running where the faders will move to different predefined positions continuously, and per-pose mode where the rotary encoder switches from pose to pose.
The motorized slide pots act as linear actuators with servo control -- there is a feedback loop where the motor moves the slide which changes the analog read value of the potentiometer's resistance. This is what allows you to send a fader to a specific position.
Libraries
You'll import libraries to provide functionality in the code:
- time
- board
- analogio
- adafruit_debouncer
- adafruit_motorkit
- adafruit_seesaw
import time import board import analogio from adafruit_debouncer import Debouncer from adafruit_motorkit import MotorKit from adafruit_seesaw import seesaw, rotaryio, digitalio
num_faders = 3
motorwing = MotorKit(i2c=board.I2C())
motorwing.frequency = 122 # tune this 50 - 200 range
max_throttle = 0.18 # tune this 0.2 - 1 range
# make arrays for all the things we care about
motors = [None] * num_faders
faders = [None] * num_faders
faders_pos = [None] * num_faders
last_faders_pos = [None] * num_faders
# set up motors in motor array
motors[0] = motorwing.motor1
motors[1] = motorwing.motor2
motors[2] = motorwing.motor3
# set motors to "off"
for i in range(num_faders):
motors[i].throttle = None
The PWM frequency and throttle values allow you to find the right balance between smoothness and speed (more on tuning PWM for DC motors in this guide).
Rotary Encoder
The STEMMA QT Rotary Encoder breakout runs on a seesaw chip and communicates via I2C. The push encoder button is set up with debouncer.
# STEMMA QT Rotary encoder setup seesaw = seesaw.Seesaw(board.I2C(), addr=0x36) # default address is 0x36 seesaw.pin_mode(24, seesaw.INPUT_PULLUP) button_in = digitalio.DigitalIO(seesaw, 24) button = Debouncer(button_in) encoder = rotaryio.IncrementalEncoder(seesaw) last_encoder_pos = 0
Fader Analog Read
The three slide potentiometers are set up on analog read pins and their raw values are re-mapped to 0-255.
# set up faders
fader_pins = (board.A0, board.A1, board.A2)
for i in range(num_faders):
faders[i] = analogio.AnalogIn(fader_pins[i])
faders_pos[i] = faders[i].value // 256 # make it 0-255 range
last_faders_pos[i] = faders_pos[i]
Position Function
Whenever the faders need to move, this update_positions() function is called with the arguments for which fader, what position, and the speed.
def update_position(fader, new_position, speed):
global faders_pos # pylint: disable=global-statement
faders_pos[fader] = int(faders[fader].value//256)
if abs(faders_pos[fader] - new_position) > 2 :
if faders_pos[fader] > new_position :
motors[fader].throttle = speed
if faders_pos[fader] < new_position:
motors[fader].throttle = speed * -1
faders_pos[fader] = int(faders[fader].value//256)
if faders_pos[fader] == new_position:
motors[fader].throttle = None
Animation List
We can treat the positions sort of like poses in an animation or values in a sequence. Each fader has a list of positions, these are used when calling the update_position() function.
# pre-saved positions for the buttons to call
H = 240 # high
M = 127 # mid
L = 10 # low
# create custom animation patterns here:
saved_positions = (
(M, H, L, L, H, M, L), # fader 1
(M, H, L, H, H, M, L), # fader 2
(M, L, L, H, H, M, L) # fader 3
)
num_positions = len(saved_positions[0]) # how many moves in our move list
position = 0 # which column of our 'saved_position' move list we're currently on
last_time = time.monotonic()
period = 4 # time in seconds between new desitnation picks
Main Loop
The main loop checks for encoder button presses to change between the two modes, then it checks to see if the encoder has been turned to switch from pose-to-pose.
If the move state is true, the update_position() function is called, and after the pre-set period is complete it moves on to the next one in the list.
while True:
button.update()
if button.fell:
move_state = not move_state
print("move_state is " + str(move_state))
if not move_state:
for i in range(num_faders):
motors[i].throttle = None
# always move all motors toward destinations
for i in range(num_faders):
update_position(i, saved_positions[i][position], max_throttle)
# has encoder been turned?
encoder_pos = -encoder.position
if encoder_pos != last_encoder_pos:
encoder_delta = encoder_pos - last_encoder_pos
last_encoder_pos = encoder_pos
# if we are not moving automatically, allow encoder tuning animation frames
if not move_state:
if encoder_delta: # encoder was turned
direction = 1 if (encoder_delta > 0) else -1 # which direction encoder was turned
position = (position + direction) % num_positions # increment/decrement
encoder_delta = 0
# else we are moving automatically
if move_state:
# if it is time to go to a new destination, do it
if time.monotonic() - last_time > period: # after 'period' seconds, change
last_time = time.monotonic()
position = (position + 1) % num_positions
Page last edited January 21, 2025
Text editor powered by tinymce.