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.