Once you've finished setting up your KB2040 with CircuitPython, you can access the code, images, font and necessary libraries by downloading the Project Bundle.
To do this, click on the Download Project Bundle button in the window below. It will download to your computer as a zipped folder.
# SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries # # SPDX-License-Identifier: MIT import time import supervisor import rotaryio import keypad import board import busio import displayio from adafruit_display_text import label from fourwire import FourWire from adafruit_st7789 import ST7789 from adafruit_bitmap_font import bitmap_font from adafruit_tmc2209 import TMC2209 displayio.release_displays() RAILS = 520 # length of rails in mm microsteps = 128 gear_ratio = 41 / 16 shot_velocities = [ 20, 15, 10 ] keys = keypad.Keys((board.D2, board.A2, board.A3), value_when_pressed=False, pull=True) encoder = rotaryio.IncrementalEncoder(board.D7, board.D6) last_position = None spi = board.SPI() tft_cs = board.D10 tft_dc = board.D8 display_bus = FourWire(spi, command=tft_dc, chip_select=tft_cs, reset=board.D9) display = ST7789(display_bus, width=240, height=240, rowstart=80, auto_refresh=False) splash = displayio.Group() display.root_group = splash bitmap = displayio.OnDiskBitmap(open("/icons.bmp", "rb")) grid_bg = displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader, tile_height=100, tile_width=100, x=(display.width - 100) // 2, y=(display.height - 100) // 2) splash.append(grid_bg) text_group = displayio.Group() font = bitmap_font.load_font("/Arial-14.bdf") title_text = "Camera Slider" title_area = label.Label(font, text=title_text, color=0xFFFFFF) title_area.anchor_point = (0.5, 0.0) title_area.anchored_position = (display.width / 2, 25) text_group.append(title_area) splash.append(text_group) font = bitmap_font.load_font("/Arial-14.bdf") text_area = label.Label(font, text="", color=0xFFFFFF) text_area.anchor_point = (0.5, 1.0) text_area.anchored_position = (display.width / 2, display.height - 25) text_group.append(text_area) uart = busio.UART(tx=board.TX, rx=board.RX, baudrate=115200, timeout=0.1) driver1 = TMC2209(uart=uart, addr=0) driver2 = TMC2209(tx_pin=board.D4, rx_pin=board.D5, addr=0) version1 = driver1.version version2 = driver2.version print(f"TMC2209 #1 Version: 0x{version1:02X}") print(f"TMC2209 #2 Version: 0x{version2:02X}") driver1.microsteps = microsteps print(driver1.microsteps) driver2.microsteps = microsteps print(driver2.microsteps) STEPS_PER_MM = 200 * microsteps / 8 driver1.direction = False driver2.direction = True last_pos = 0 select = 0 menu = 0 time_mode = 0 shot_mode = 0 timelapse = True movement_time = 0 titles = ["Camera Slider", "Motor 1", "Motor 2", "Mode", "Timelapse", "One-Shot", "Start?", "Running"] home_text = ["Press to Begin", "0"] motor1_text = ["Slide to Start Point", "0"] motor2_text = ["Move to Start", "Move to End", "0"] mode_text = ["Timelapse", "One-Shot"] time_text = ["1", "5", "10", "15", "30"] shot_speeds = [10, 5, 2] speeds = [] shot_text = ["Slow", "Medium", "Fast"] start_text = ["Go!", 0] running_text = ["STOP!", "Pause/Resume"] running_icons = [6, 7] mode_icons = [3, 4] sub_titles = [home_text, motor1_text, motor2_text, mode_text, time_text, shot_text, start_text, running_text] motor2_coordinates = [0.0, 0.0] text_area.text = home_text[0] display.refresh() def adv_menu(m): m = (m + 1) % 8 title_area.text = titles[m] sub = sub_titles[m] if m == 4: grid_bg[0] = 3 elif m == 5: grid_bg[0] = 4 elif m > 5: grid_bg[0] = m - 1 else: grid_bg[0] = m text_area.text = sub[0] display.refresh() return m motor1_movement = { "is_active": False, "current_step": 0, "total_steps": 0, "start_pos": 0, "end_pos": 0, "step_direction": 1, "last_step_time": 0, "step_interval": 0, "is_paused": False, "toggle_pause": False, "stop_requested": False } motor2_movement = { "is_active": False, "current_step": 0, "total_steps": 0, "start_pos": 0, "end_pos": 0, "step_direction": 1, "last_step_time": 0, "step_interval": 0, "is_paused": False, "toggle_pause": False, "stop_requested": False } # pylint: disable=too-many-branches, too-many-statements, inconsistent-return-statements def calculate_linear_velocity(steps_per_second, clock_frequency=12000000, micro=128, scaling_factor=6): frequency = steps_per_second * micro vactual = int((frequency * (1 << 23)) / (clock_frequency * scaling_factor)) vactual = max(-(1 << 23), min((1 << 23) - 1, vactual)) return vactual def move_steps_over_time(camera_driver, start_position, end_position, time_seconds, micro=128, ratio=None): steps = abs(end_position - start_position) if camera_driver: direction = -1 if end_position < start_position else 1 time_seconds = time_seconds * 2 else: direction = 1 if driver1.direction else -1 if ratio is not None: steps = steps / ratio total_microsteps = steps * micro microsteps_per_second = total_microsteps / time_seconds fCLK = 12000000 if camera_driver: vactual = int(microsteps_per_second / (fCLK / (1 << 24))) else: vactual = int(microsteps_per_second / (fCLK / (1 << 27))) velocity = max(-(1 << 23), min((1 << 23) - 1, vactual)) velocity *= direction return velocity def calculate_timelapse_velocity(start_position, end_position, duration_seconds, micro=128, clock_frequency=12000000, scaling_factor=6, min_velocity=100): total_steps = abs(end_position - start_position) steps_per_second = total_steps / duration_seconds full_steps_per_second = steps_per_second / micro vactual = calculate_linear_velocity(full_steps_per_second, clock_frequency, micro, scaling_factor) direction = -1 if end_position < start_position else 1 if abs(vactual) < min_velocity and vactual != 0: vactual = min_velocity * direction return vactual def calculate_rail_velocity(total_steps, duration_sec, direction, is_timelapse=True, micro=128, clock_frequency=12000000): steps_per_second = total_steps / duration_sec full_steps_per_second = steps_per_second / micro if not is_timelapse: base_scaling = 1.0 min_velocity = 400 vactual = int((full_steps_per_second * micro * (1 << 23)) / (clock_frequency * base_scaling)) vactual *= direction if abs(vactual) < min_velocity: vactual = min_velocity * direction else: base_scaling = 6.0 min_velocity = 50 vactual = int((full_steps_per_second * micro * (1 << 23)) / (clock_frequency * base_scaling)) vactual *= direction if abs(vactual) < min_velocity: vactual = min_velocity * direction vactual = max(-(1 << 23), min((1 << 23) - 1, vactual)) return vactual def move_motor_with_rotate(driver, movement_state, start_position=None, end_position=None, duration_sec=0, micro=128): if start_position is not None and end_position is not None and not movement_state["is_active"]: if timelapse: driver.enable_motor(run_current=20) scaling_factor = 6 min_velocity = 50 velocity = calculate_timelapse_velocity( start_position, end_position, duration_sec, micro, scaling_factor=scaling_factor, min_velocity=min_velocity ) else: driver.enable_motor(run_current=30) velocity = calculate_rail_velocity( int(RAILS*STEPS_PER_MM), duration_sec, movement_state["step_direction"], is_timelapse=timelapse, micro=micro ) initial_velocity = int(velocity * 0.2) if abs(initial_velocity) < 200: initial_velocity = 200 * (1 if velocity > 0 else -1) driver.rotate(initial_velocity) movement_state["initial_velocity"] = initial_velocity movement_state["final_velocity"] = velocity movement_state["ramp_up_done"] = False movement_state["ramp_up_time"] = 500 movement_state["total_steps"] = int(RAILS*STEPS_PER_MM) movement_state["step_direction"] = 1 if end_position > start_position else -1 movement_state["start_pos"] = 0 movement_state["end_pos"] = int(RAILS*STEPS_PER_MM) movement_state["movement_start_time"] = supervisor.ticks_ms() movement_state["movement_duration_ms"] = duration_sec * 1000 movement_state["is_active"] = True movement_state["is_paused"] = False return movement_state["total_steps"] = int(RAILS*STEPS_PER_MM) movement_state["step_direction"] = driver.direction movement_state["start_pos"] = 0 movement_state["end_pos"] = int(RAILS*STEPS_PER_MM) if duration_sec > 0 and movement_state["total_steps"] > 0: movement_state["velocity"] = velocity driver.rotate(velocity) movement_state["movement_start_time"] = supervisor.ticks_ms() movement_state["movement_duration_ms"] = duration_sec * 1000 else: default_velocity = 2000 * movement_state["step_direction"] driver.rotate(default_velocity) movement_state["movement_duration_ms"] = movement_state["total_steps"] * 10 movement_state["movement_start_time"] = supervisor.ticks_ms() movement_state["is_active"] = True movement_state["is_paused"] = False if movement_state["is_active"] and movement_state["toggle_pause"]: movement_state["is_paused"] = not movement_state["is_paused"] movement_state["toggle_pause"] = False if movement_state["is_paused"]: driver.rotate(0) movement_state["pause_time"] = supervisor.ticks_ms() else: elapsed_ms = movement_state["pause_time"] - movement_state["movement_start_time"] remaining_ms = movement_state["movement_duration_ms"] - elapsed_ms if remaining_ms > 0: driver.rotate(movement_state["velocity"]) movement_state["movement_start_time"] = supervisor.ticks_ms() - elapsed_ms else: driver.rotate(0) driver.disable_motor() movement_state["is_active"] = False if movement_state["is_active"] and movement_state["stop_requested"]: driver.rotate(0) driver.disable_motor() movement_state["is_active"] = False movement_state["stop_requested"] = False return { "active": False, "complete": False, "progress_percent": (supervisor.ticks_ms() - movement_state["movement_start_time"]) / movement_state["movement_duration_ms"] * 100, "stopped_by_user": True } if movement_state["is_active"] and not movement_state["is_paused"]: current_t = supervisor.ticks_ms() e = current_t - movement_state["movement_start_time"] if e >= movement_state["movement_duration_ms"]: print("Movement time complete!") driver.rotate(0) driver.disable_motor() movement_state["is_active"] = False return { "active": False, "complete": True, "progress_percent": 100, "stopped_by_user": False } return { "active": movement_state["is_active"], "paused": movement_state["is_paused"], "progress_percent": (supervisor.ticks_ms() - movement_state["movement_start_time"]) / movement_state["movement_duration_ms"] * 100 if movement_state["is_active"] else 0, "stopped_by_user": False } def pause_resume_motor1(): motor1_movement["toggle_pause"] = True def stop_motor1(): driver1.disable_motor() driver1.reset_position() motor1_movement["stop_requested"] = True def pause_resume_motor2(): motor2_movement["toggle_pause"] = True def stop_motor2(): driver2.disable_motor() driver2.reset_position() motor2_movement["stop_requested"] = True def stop_all_motors(): driver1.rotate(0) driver2.rotate(0) driver1.disable_motor() driver2.disable_motor() motor1_movement["is_active"] = False motor2_movement["is_active"] = False motor1_movement["stop_requested"] = False motor2_movement["stop_requested"] = False time.sleep(0.1) driver1.disable_motor() driver1.reset_position() driver2.reset_position() while True: if motor1_movement["is_active"]: current_time = supervisor.ticks_ms() elapsed = current_time - motor1_movement["movement_start_time"] if elapsed >= motor1_movement["movement_duration_ms"]: driver1.rotate(0) driver1.disable_motor() motor1_movement["is_active"] = False if motor2_movement["is_active"]: current_time = supervisor.ticks_ms() elapsed = current_time - motor2_movement["movement_start_time"] if elapsed >= motor2_movement["movement_duration_ms"]: driver2.rotate(0) driver2.disable_motor() motor2_movement["is_active"] = False if menu == 7: active_motors = 0 progress1 = 0 progress2 = 0 if motor1_movement["is_active"]: active_motors += 1 current_time = supervisor.ticks_ms() elapsed = current_time - motor1_movement["movement_start_time"] progress1 = (elapsed / motor1_movement["movement_duration_ms"]) * 100 if motor2_movement["is_active"]: active_motors += 1 current_time = supervisor.ticks_ms() elapsed = current_time - motor2_movement["movement_start_time"] progress2 = (elapsed / motor2_movement["movement_duration_ms"]) * 100 if active_motors > 0: avg_progress = (progress1 + progress2) / active_motors text_area.text = f"{running_text[select]} {avg_progress:.1f}%" display.refresh() elif active_motors == 0 and (motor1_movement["movement_duration_ms"] > 0 or motor2_movement["movement_duration_ms"] > 0): text_area.text = "Movement Complete!" display.refresh() event = keys.events.get() if event: if event.pressed: print(f"{event.key_number} pressed") if event.key_number == 0: if menu == 0: menu = adv_menu(menu) elif menu == 2: if select == 0: motor2_coordinates[select] = driver2.position if select == 1: motor2_coordinates[select] = driver2.position select += 1 text_area.text = motor2_text[select] if select > 1: select = 0 menu = adv_menu(menu) if motor2_coordinates[0] > motor2_coordinates[1]: move = motor2_coordinates[0] - motor2_coordinates[1] else: move = motor2_coordinates[1] - motor2_coordinates[0] move = -move driver2.step(move) elif menu == 3: if select == 1: timelapse = False menu += 1 select = 0 else: timelapse = True menu = adv_menu(menu) elif menu == 4: menu += 1 time_mode = select menu = adv_menu(menu) select = 0 print(f"{time_text[time_mode]}, timelapse: {timelapse}") elif menu == 5: shot_mode = select menu = adv_menu(menu) select = 0 print(f"{shot_text[shot_mode]}, timelapse: {timelapse}") elif menu == 6: menu = adv_menu(menu) if timelapse: movement_time = int(time_text[time_mode]) * 60 print(f"starting a timelapse for {time_text[time_mode]} minutes") status1 = move_motor_with_rotate( driver1, motor1_movement, start_position=0, end_position=int(RAILS * STEPS_PER_MM), duration_sec=movement_time, microsteps=microsteps ) if abs(motor2_coordinates[1] - motor2_coordinates[0]) > 0: velocity2 = move_steps_over_time(camera_driver=True, start_position=motor2_coordinates[0], end_position=motor2_coordinates[1], time_seconds=movement_time, microsteps=microsteps, ratio=gear_ratio) print(f"driver2 velocity is: {velocity2}") driver2.enable_motor(run_current=25) driver2.rotate(velocity2) motor2_movement["is_active"] = True motor2_movement["start_pos"] = motor2_coordinates[0] motor2_movement["end_pos"] = motor2_coordinates[1] motor2_movement["movement_start_time"] = supervisor.ticks_ms() motor2_movement["movement_duration_ms"] = movement_time * 1000 motor2_movement["velocity"] = velocity2 motor2_movement["total_steps"] = (abs(motor2_coordinates[1] - motor2_coordinates[0])) else: print(f"starting a {shot_text[shot_mode]} one-shot") movement_time = shot_velocities[shot_mode] status1 = move_motor_with_rotate( driver1, motor1_movement, start_position=0, end_position=int(RAILS * STEPS_PER_MM), duration_sec=movement_time, microsteps=microsteps ) if abs(motor2_coordinates[1] - motor2_coordinates[0]) > 0: velocity2 = move_steps_over_time(camera_driver=True, start_position=motor2_coordinates[0], end_position=motor2_coordinates[1], time_seconds=movement_time, microsteps=microsteps, ratio=gear_ratio) driver2.enable_motor(run_current=25) driver2.rotate(velocity2) motor2_movement["is_active"] = True motor2_movement["start_pos"] = motor2_coordinates[0] motor2_movement["end_pos"] = motor2_coordinates[1] motor2_movement["movement_start_time"] = supervisor.ticks_ms() motor2_movement["movement_duration_ms"] = movement_time * 1000 motor2_movement["velocity"] = velocity2 motor2_movement["total_steps"] = (abs(motor2_coordinates[1] - motor2_coordinates[0])) elif menu == 7: if select == 0: stop_all_motors() text_area.text = "Stopping..." menu = adv_menu(menu) elif select == 1: pause_resume_motor1() pause_resume_motor2() paused_state = motor1_movement["is_paused"] or motor2_movement["is_paused"] text_area.text = "Paused" if paused_state else "Running" display.refresh() if event.key_number == 1: if menu == 1: driver1.direction = False driver1.reset_position() menu = adv_menu(menu) elif menu == 7: stop_all_motors() menu = adv_menu(menu) if event.key_number == 2: if menu == 1: driver1.direction = True driver1.reset_position() menu = adv_menu(menu) elif menu == 7: stop_all_motors() menu = adv_menu(menu) display.refresh() pos = encoder.position if pos != last_pos: if pos > last_pos: if menu == 2: driver2.step(-10) if menu == 3: select = (select + 1) % 2 text_area.text = mode_text[select] grid_bg[0] = mode_icons[select] if menu == 4: select = (select + 1) % len(time_text) text_area.text = time_text[select] if menu == 5: select = (select + 1) % len(shot_text) text_area.text = shot_text[select] if menu == 7: select = (select + 1) % len(running_text) text_area.text = running_text[select] grid_bg[0] = running_icons[select] else: if menu == 2: driver2.step(10) if menu == 3: select = (select - 1) % 2 text_area.text = mode_text[select] grid_bg[0] = mode_icons[select] if menu == 4: select = (select - 1) % len(time_text) text_area.text = time_text[select] if menu == 5: select = (select - 1) % len(shot_text) text_area.text = shot_text[select] if menu == 7: select = (select - 1) % len(running_text) text_area.text = running_text[select] grid_bg[0] = running_icons[select] last_pos = pos display.refresh()
Upload the Code and Libraries to the KB2040
After downloading the Project Bundle, plug your KB2040 into the computer's USB port with a known good USB data+power cable. You should see a new flash drive appear in the computer's File Explorer or Finder (depending on your operating system) called CIRCUITPY. Unzip the folder and copy the following items to the KB2040's CIRCUITPY drive.
- lib folder
- code.py
- adafruit_tmc2209.py
- generic_uart_device.py
- icons.bmp
- Arial-14.bdf
Your KB2040 CIRCUITPY drive should look like this after copying the lib folder, image file, font file and the Python files.

How the Code Works
There are three code files: adafruit_tmc2209.py, generic_uart_device.py and the main code.py. adafruit_tmc2209.py and generic_uart_device.py are driver files for the TMC2209 UART control. The code.py file depends on these files to control the TMC2209 drivers.
At the top of code.py are some user parameters. RAILS
is the length of the rails in mm. microsteps
are the number of microsteps used by the stepper motors. gear_ratio
is the gear reduction for the camera pan mechanism. Finally, shot_velocities
contains the one-shot speeds in seconds.
RAILS = 520 # length of rails in mm microsteps = 128 gear_ratio = 41 / 16 shot_velocities = [ 20, 15, 10 ]
Keypad, TFT and Encoder
The two end stop switches and the rotary encoder button are instantiated as a Keypad
object. The TFT is controlled via SPI. The rotary encoder is wired directly to GPIO for rotaryio
.
keys = keypad.Keys((board.D2, board.A2, board.A3), value_when_pressed=False, pull=True) encoder = rotaryio.IncrementalEncoder(board.D7, board.D6) last_position = None spi = board.SPI() tft_cs = board.D10 tft_dc = board.D8 display_bus = FourWire(spi, command=tft_dc, chip_select=tft_cs, reset=board.D9) display = ST7789(display_bus, width=240, height=240, rowstart=80, auto_refresh=False)
Graphics
The icons for the different menu options are on a single bitmap file (icons.bmp) and are used as a sprite sheet with TileGrid
. There are two text objects: title_text
and text_area
that update depending on the menu.
splash = displayio.Group() display.root_group = splash bitmap = displayio.OnDiskBitmap(open("/icons.bmp", "rb")) grid_bg = displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader, tile_height=100, tile_width=100, x=(display.width - 100) // 2, y=(display.height - 100) // 2) splash.append(grid_bg) text_group = displayio.Group() font = bitmap_font.load_font("/Arial-14.bdf") title_text = "Camera Slider" title_area = label.Label(font, text=title_text, color=0xFFFFFF) title_area.anchor_point = (0.5, 0.0) title_area.anchored_position = (display.width / 2, 25) text_group.append(title_area) splash.append(text_group) text_area = label.Label(font, text="", color=0xFFFFFF) text_area.anchor_point = (0.5, 1.0) text_area.anchored_position = (display.width / 2, display.height - 25) text_group.append(text_area)
TMC2209 UART
The two TMC2209 drivers are controlled over UART. Each driver is on its on UART line. Driver 1 uses the default UART (RX and TX) and driver 2 uses the secondary UART (D4 and D5). The microsteps are set for each of the drivers, along with a starting direction.
uart = busio.UART(tx=board.TX, rx=board.RX, baudrate=115200, timeout=0.1) driver1 = TMC2209(uart=uart, addr=0) driver2 = TMC2209(tx_pin=board.D4, rx_pin=board.D5, addr=0) version1 = driver1.version version2 = driver2.version print(f"TMC2209 #1 Version: 0x{version1:02X}") print(f"TMC2209 #2 Version: 0x{version2:02X}") driver1.microsteps = microsteps print(driver1.microsteps) driver2.microsteps = microsteps print(driver2.microsteps) STEPS_PER_MM = 200 * microsteps / 8 driver1.direction = False driver2.direction = True
Menu
The menu system is the backbone of the project. It is used to navigate through setting up a shot sequence. The TFT displays icons and text that correspond with each menu scene. The adv_menu()
function takes care of advancing the graphics.
titles = ["Camera Slider", "Motor 1", "Motor 2", "Mode", "Timelapse", "One-Shot", "Start?", "Running"] home_text = ["Press to Begin", "0"] motor1_text = ["Slide to Start Point", "0"] motor2_text = ["Move to Start", "Move to End", "0"] mode_text = ["Timelapse", "One-Shot"] time_text = ["1", "5", "10", "15", "30"] shot_text = ["Slow", "Medium", "Fast"] start_text = ["Go!", 0] running_text = ["STOP!", "Pause/Resume"] running_icons = [6, 7] mode_icons = [3, 4] sub_titles = [home_text, motor1_text, motor2_text, mode_text, time_text, shot_text, start_text, running_text] text_area.text = home_text[0] def adv_menu(m): m = (m + 1) % 8 title_area.text = titles[m] sub = sub_titles[m] if m == 4: grid_bg[0] = 3 elif m == 5: grid_bg[0] = 4 elif m > 5: grid_bg[0] = m - 1 else: grid_bg[0] = m text_area.text = sub[0] display.refresh() return m
Moving the Motors
Each driver has a dictionary to track its movement and start/pause/stop state throughout the different movement functions and the main loop.
motor1_movement = { "is_active": False, "current_step": 0, "total_steps": 0, "start_pos": 0, "end_pos": 0, "step_direction": 1, "last_step_time": 0, "step_interval": 0, "is_paused": False, "toggle_pause": False, "stop_requested": False } motor2_movement = { "is_active": False, "current_step": 0, "total_steps": 0, "start_pos": 0, "end_pos": 0, "step_direction": 1, "last_step_time": 0, "step_interval": 0, "is_paused": False, "toggle_pause": False, "stop_requested": False }
A few functions are used to control the motor movement. Driver 1, which moves the camera along the rails, has its own set of functions since it is moving a much longer distance than driver 2; which pans the camera. Both drivers utilize the velocity calculation from the datasheet:
vactual = int(microsteps_per_second / (fCLK / (1 << 24))) vactual = max(-(1 << 23), min((1 << 23) - 1, vactual))
Velocity is used instead of individual steps because its a lot smoother and silent. The functions let you pass the time and number of steps and calculate the velocity from that. Additionally, with the combination of the dictionaries, both motors can be paused/resumed/stopped without blocking in the loop.
The Loop
At the top of the loop, if either of the motors' ["is_active"]
parameter is True
, it means that they are rotating. ticks_ms()
is used to keep track of the time.Â
while True: if motor1_movement["is_active"]: current_time = supervisor.ticks_ms() elapsed = current_time - motor1_movement["movement_start_time"] if elapsed >= motor1_movement["movement_duration_ms"]: driver1.rotate(0) driver1.disable_motor() motor1_movement["is_active"] = False if motor2_movement["is_active"]: current_time = supervisor.ticks_ms() elapsed = current_time - motor2_movement["movement_start_time"] if elapsed >= motor2_movement["movement_duration_ms"]: driver2.rotate(0) driver2.disable_motor() motor2_movement["is_active"] = False
Keypad
events and the rotary encoder are used to navigate the menu system. When the rotary encoder button is pressed (key_number 0
), the menu advances. It is also used to pause or stop the motors while they are moving through a shot. The end stop switches and rotary encoder have different functionality depending on the menu index. The display is updated with different text if the rotary encoder is used to scroll through options.
event = keys.events.get() if event: if event.pressed: print(f"{event.key_number} pressed") if event.key_number == 0: if menu == 0: menu = adv_menu(menu) elif menu == 2: if select == 0: motor2_coordinates[select] = driver2.position if select == 1: motor2_coordinates[select] = driver2.position select += 1 text_area.text = motor2_text[select] if select > 1: select = 0 menu = adv_menu(menu) if motor2_coordinates[0] > motor2_coordinates[1]: move = motor2_coordinates[0] - motor2_coordinates[1] else: move = motor2_coordinates[1] - motor2_coordinates[0] move = -move driver2.step(move) elif menu == 3: if select == 1: timelapse = False menu += 1 select = 0 else: timelapse = True menu = adv_menu(menu) elif menu == 4: menu += 1 time_mode = select menu = adv_menu(menu) select = 0 print(f"{time_text[time_mode]}, timelapse: {timelapse}") elif menu == 5: shot_mode = select menu = adv_menu(menu) select = 0 print(f"{shot_text[shot_mode]}, timelapse: {timelapse}") elif menu == 6: menu = adv_menu(menu) if timelapse: movement_time = int(time_text[time_mode]) * 60 print(f"starting a timelapse for {time_text[time_mode]} minutes") status1 = move_motor_with_rotate( driver1, motor1_movement, start_position=0, end_position=int(RAILS * STEPS_PER_MM), duration_sec=movement_time, microsteps=microsteps ) if abs(motor2_coordinates[1] - motor2_coordinates[0]) > 0: velocity2 = move_steps_over_time(camera_driver=True, start_position=motor2_coordinates[0], end_position=motor2_coordinates[1], time_seconds=movement_time, microsteps=microsteps, ratio=gear_ratio) print(f"driver2 velocity is: {velocity2}") driver2.enable_motor(run_current=25) driver2.rotate(velocity2) motor2_movement["is_active"] = True motor2_movement["start_pos"] = motor2_coordinates[0] motor2_movement["end_pos"] = motor2_coordinates[1] motor2_movement["movement_start_time"] = supervisor.ticks_ms() motor2_movement["movement_duration_ms"] = movement_time * 1000 motor2_movement["velocity"] = velocity2 motor2_movement["total_steps"] = (abs(motor2_coordinates[1] - motor2_coordinates[0])) else: print(f"starting a {shot_text[shot_mode]} one-shot") movement_time = shot_velocities[shot_mode] status1 = move_motor_with_rotate( driver1, motor1_movement, start_position=0, end_position=int(RAILS * STEPS_PER_MM), duration_sec=movement_time, microsteps=microsteps ) if abs(motor2_coordinates[1] - motor2_coordinates[0]) > 0: velocity2 = move_steps_over_time(camera_driver=True, start_position=motor2_coordinates[0], end_position=motor2_coordinates[1], time_seconds=movement_time, microsteps=microsteps, ratio=gear_ratio) driver2.enable_motor(run_current=25) driver2.rotate(velocity2) motor2_movement["is_active"] = True motor2_movement["start_pos"] = motor2_coordinates[0] motor2_movement["end_pos"] = motor2_coordinates[1] motor2_movement["movement_start_time"] = supervisor.ticks_ms() motor2_movement["movement_duration_ms"] = movement_time * 1000 motor2_movement["velocity"] = velocity2 motor2_movement["total_steps"] = (abs(motor2_coordinates[1] - motor2_coordinates[0])) elif menu == 7: if select == 0: stop_all_motors() text_area.text = "Stopping..." menu = adv_menu(menu) elif select == 1: pause_resume_motor1() pause_resume_motor2() paused_state = motor1_movement["is_paused"] or motor2_movement["is_paused"] text_area.text = "Paused" if paused_state else "Running" display.refresh() if event.key_number == 1: if menu == 1: driver1.direction = False driver1.reset_position() menu = adv_menu(menu) elif menu == 7: stop_all_motors() menu = adv_menu(menu) if event.key_number == 2: if menu == 1: driver1.direction = True driver1.reset_position() menu = adv_menu(menu) elif menu == 7: stop_all_motors() menu = adv_menu(menu) display.refresh() pos = encoder.position if pos != last_pos: if pos > last_pos: if menu == 2: driver2.step(-10) if menu == 3: select = (select + 1) % 2 text_area.text = mode_text[select] grid_bg[0] = mode_icons[select] if menu == 4: select = (select + 1) % len(time_text) text_area.text = time_text[select] if menu == 5: select = (select + 1) % len(shot_text) text_area.text = shot_text[select] if menu == 7: select = (select + 1) % len(running_text) text_area.text = running_text[select] grid_bg[0] = running_icons[select] else: if menu == 2: driver2.step(10) if menu == 3: select = (select - 1) % 2 text_area.text = mode_text[select] grid_bg[0] = mode_icons[select] if menu == 4: select = (select - 1) % len(time_text) text_area.text = time_text[select] if menu == 5: select = (select - 1) % len(shot_text) text_area.text = shot_text[select] if menu == 7: select = (select - 1) % len(running_text) text_area.text = running_text[select] grid_bg[0] = running_icons[select] last_pos = pos display.refresh()
Page last edited May 13, 2025
Text editor powered by tinymce.