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 board's CIRCUITPY drive, replacing any existing files or directories with the same names, and adding any new ones that are necessary.
# SPDX-FileCopyrightText: 2024 John Park for Adafruit Industries # # SPDX-License-Identifier: MIT """ Drum Track Sequencer Feather RP2040, Motor FeatherWing, stepper motor, four reflection sensors, USB MIDI out """ import asyncio import busio import board from adafruit_motorkit import MotorKit from adafruit_motor import stepper import keypad import usb_midi # Tempo setup BPM = 100 # user set value tempo_table = { # motor speed seems non-linear, so we'll use a lookup table 110: 0.0004, 100: 0.001, 90: 0.002, 80: 0.003, 75: 0.004, 65: 0.005, 60: 0.006, 50: 0.008 } def get_nearest_tempo(given_bpm): nearest_table_item = min(tempo_table.keys(), key=lambda k: abs(k - given_bpm)) return tempo_table[nearest_table_item] motor_pause = get_nearest_tempo(BPM) i2c=busio.I2C(board.SCL, board.SDA, frequency=400_000) # Motor setup kit = MotorKit(i2c=i2c) motor_run=True # Sensor setup optical_pins = (board.D6, board.D9, board.D10, board.D12) optical_sensors = keypad.Keys(optical_pins, value_when_pressed=False, pull=True) # MIDI setup midi = usb_midi.ports[1] midi_notes = (36, 37, 38, 39) # typical drum voice notes def play_drum(note): midi_msg_on = bytearray([0x99, note, 120]) # 0x90 noteOn ch1, 0x99 noteOn ch10 midi_msg_off = bytearray([0x89, note, 0]) midi.write(midi_msg_on) midi.write(midi_msg_off) async def check_sensors(): while True: optical_sensor = optical_sensors.events.get() if optical_sensor: if optical_sensor.pressed: track_num = optical_sensor.key_number # print("tripped", track_num) play_drum(midi_notes[track_num]) await asyncio.sleep(0.008) # don't check sensors constantly or motor speed reduced async def run_motor(): while True: kit.stepper1.onestep( direction=stepper.BACKWARD, style=stepper.DOUBLE ) await asyncio.sleep(motor_pause) # motor speed-- smaller numbers are faster async def main(): motor_task = asyncio.create_task(run_motor()) sensor_task = asyncio.create_task(check_sensors()) await asyncio.gather(motor_task, sensor_task) asyncio.run(main())
The code's job is to run the stepper motor at a specific speed and read the four optical sensor inputs. When an input is triggered a MIDI note is sent over USB MIDI.
This code checks the sensor inputs while the stepper motor rotates at a tempo-defined speed. By combining asynchronous tasks, then it efficiently checks sensors and controls the motor simultaneously.
Libraries
First, the following libraries are imported:
import asyncio import busio import board from adafruit_motorkit import MotorKit from adafruit_motor import stepper import keypad import usb_midi
Constants
the BPM
variable is an integer for storing the tempo. It is user defined and works along with the tempo_table
dictionary to set the stepper motor step delay.
BPM = 100 # user set value tempo_table = { 110: 0.0004, 100: 0.001, 90: 0.002, 80: 0.003, 75: 0.004, 65: 0.005, 60: 0.006, 50: 0.008 }
def get_nearest_tempo(given_bpm): nearest_table_item = min(tempo_table.keys(), key=lambda k: abs(k - given_bpm)) return tempo_table[nearest_table_item] motor_pause = get_nearest_tempo(BPM)
i2c = busio.I2C(board.SCL, board.SDA, frequency=400_000) # Motor setup kit = MotorKit(i2c=i2c) motor_run = True # Sensor setup optical_pins = (board.D6, board.D9, board.D10, board.D12) optical_sensors = keypad.Keys(optical_pins, value_when_pressed=False, pull=True) # MIDI setup midi = usb_midi.ports[1] midi_notes = (36, 37, 38, 39) # typical drum voice notes
Play Drum Function
This function will send the note on and note off messages when a drum track beat is triggered.
def play_drum(note): midi_msg_on = bytearray([0x99, note, 120]) # 0x90 noteOn ch1, 0x99 noteOn ch10 midi_msg_off = bytearray([0x89, note, 0]) midi.write(midi_msg_on) midi.write(midi_msg_off)
async Functions
We need the motor control and sensor checking to happen at the same time, otherwise there would be a mess of tempo shifting! The asyncio library allows for asynchronous functions to be defined and then run effectively at the same time without getting in each others way. You can find out much more here.
async def check_sensors(): while True: optical_sensor = optical_sensors.events.get() if optical_sensor: if optical_sensor.pressed: track_num = optical_sensor.key_number play_drum(midi_notes[track_num]) await asyncio.sleep(0.008) # don't check sensors constantly or motor speed reduced
async def run_motor(): while True: kit.stepper1.onestep( direction=stepper.BACKWARD, style=stepper.DOUBLE ) await asyncio.sleep(motor_pause) # motor speed-- smaller numbers are faster
Then, the async main()
function is created and run.
async def main(): motor_task = asyncio.create_task(run_motor()) sensor_task = asyncio.create_task(check_sensors()) await asyncio.gather(motor_task, sensor_task) asyncio.run(main())
Text editor powered by tinymce.