Community project
MIDI Stepper Motor Controller
Generated with AIThis guide builds a MIDI-controlled stepper motor controller that drives three NEMA 17 motors independently using MIDI note messages. Each motor's speed is controlled by MIDI note values (30 to 800 steps per second), making it suitable for kinetic sculptures, musical installations, or automated mechanical systems.
The project uses an Arduino Uno with three DRV8825 stepper drivers, an optoisolated MIDI input for noise immunity, and a 12 V power supply. This guide includes a complete wiring diagram, parts list, Arduino firmware with MIDI parsing and AccelStepper library integration, and step-by-step assembly instructions with safe power-up procedures.
Wiring diagram
Interactive · read-only
Pan and zoom to explore the wiring. Remix the project to edit it in your own workspace.
Parts list
Bill of materials| Component | Qty | Notes |
|---|---|---|
| DRV8825 Stepper DriverDRV8825 | 1 | DRV8825 bipolar stepper motor driver carrier with STEP/DIR control, current limiting, and higher motor-voltage headroom than the common A4988 carrier. |
| DRV8825 Stepper DriverDRV8825 | 1 | DRV8825 bipolar stepper motor driver carrier with STEP/DIR control, current limiting, and higher motor-voltage headroom than the common A4988 carrier. |
| DRV8825 Stepper DriverDRV8825 | 1 | DRV8825 bipolar stepper motor driver carrier with STEP/DIR control, current limiting, and higher motor-voltage headroom than the common A4988 carrier. |
| Nema 17NEMA 17, 4-wire | 1 | NEMA 17-size bipolar stepper motor for motion axes. Typical hobby modules are 42mm-frame, 4-wire/two-phase motors with 1.8 degree step angle (200 steps/rev); use with a current-limited stepper driver such as TMC2209/A4988/DRV8825 rather than direct GPIO. |
| Nema 17NEMA 17, 4-wire | 1 | NEMA 17-size bipolar stepper motor for motion axes. Typical hobby modules are 42mm-frame, 4-wire/two-phase motors with 1.8 degree step angle (200 steps/rev); use with a current-limited stepper driver such as TMC2209/A4988/DRV8825 rather than direct GPIO. |
| Nema 17NEMA 17, 4-wire | 1 | NEMA 17-size bipolar stepper motor for motion axes. Typical hobby modules are 42mm-frame, 4-wire/two-phase motors with 1.8 degree step angle (200 steps/rev); use with a current-limited stepper driver such as TMC2209/A4988/DRV8825 rather than direct GPIO. |
| 5-pin DIN MIDI Input Socket5-pin DIN female | 1 | A panel socket that receives the MIDI messages sent by an external musical instrument or controller. |
| 6N138 Optocoupler6N138 | 1 | A six-pin isolator that transfers MIDI data while keeping the musical device electrically separate from the Arduino. |
| Resistor220 Ω, 1/4 W | 1 | Through-hole resistor (current-limiting in series with an LED) |
| Resistor10 kΩ, 1/4 W | 1 | Through-hole resistor (current-limiting in series with an LED) |
| 12 V 6 A Regulated DC Wall Supply12 V, 6 A | 1 | A plug-in DC supply that provides enough separate power for three small stepper motors. |
| 100 µF 35 V Electrolytic Capacitor100 µF, 35 V | 1 | A polarized capacitor placed across the motor supply to absorb voltage spikes from the stepper drivers. |
Assembly
6 stepsKeep motor power unplugged while wiring
Place the Arduino Uno, the three DRV8825 boards, and the three motors on a non-metal surface. Leave the 12 V wall supply unplugged until every wire has been checked. Power the Arduino only from its USB cable.
- Tip: Put a small label on each driver and its matching motor: 1, 2, and 3.
- ⚠ Do not plug or unplug a motor while its driver has power — that can permanently damage the DRV8825 board.
Wire the first motor driver
On driver 1, connect VDD to Arduino 5V (logic power), GND to Arduino GND (ground), STEP to D3 (step signal), and DIR to D4 (direction signal). Connect EN, M0, M1, and M2 to Arduino GND (ground); this keeps the driver enabled and uses full steps. Connect motor 1's two wires from one coil to 1A and 1B, and the other matching pair to 2A and 2B (motor power).
- Tip: Find each coil pair with a multimeter: the two wires that show a small resistance belong together. If the motor turns the wrong way, swap either one coil pair, not both.
- Tip: Set the tiny current-limit adjustment on the driver for your motor's rated current before regular use; start low to avoid overheating.
- ⚠ Do not guess the motor coil pairs — crossed coils make the motor shudder instead of turning and can overheat it.
Wire the second and third motor drivers
For driver 2, connect VDD to Arduino 5V (logic power), GND to Arduino GND (ground), STEP to D5 (step signal), DIR to D6 (direction signal), and EN to GND (enable). Connect motor 2's two coil pairs to 1A/1B and 2A/2B (motor power). For driver 3, connect VDD to Arduino 5V (logic power), GND to Arduino GND (ground), STEP to D9 (step signal), DIR to D10 (direction signal), and EN to GND (enable). Connect motor 3's coil pairs to 1A/1B and 2A/2B (motor power).
- Tip: Keep the three driver boards separated so their adjustment screws and metal pins cannot touch.
- Tip: M0, M1, and M2 on drivers 2 and 3 may be left unconnected because their boards default to full steps; grounding them too is also acceptable if you wish to match driver 1.
- ⚠ The 5 V Arduino pin powers only the small logic side of each driver; never connect a motor or a driver VMOT pin to Arduino 5 V.
Add the 12 V motor supply and capacitor
Connect the 12 V supply positive wire to VMOT on all three drivers (motor power). Connect the 12 V supply negative wire to GND on all three drivers and to an Arduino GND pin (shared ground). Place the 100 µF capacitor close to the driver boards: its long positive lead goes to the 12 V positive connection (spike protection), and its striped negative lead goes to the 12 V negative connection (ground).
- Tip: Use a proper screw-terminal splitter or terminal block for the 12 V positive and negative branches instead of forcing several wires into one small driver terminal.
- Tip: The capacitor's stripe marks its negative lead.
- ⚠ Make sure the capacitor's positive and negative leads are not swapped — reversed electrolytic capacitors can fail violently.
- ⚠ Check the 12 V supply polarity with a meter before plugging it into the drivers.
Build the isolated MIDI input
On the 5-pin MIDI socket, connect DIN-5 to one end of the 220 Ω resistor (MIDI signal), and connect the other resistor end to the 6N138 ANODE pin (MIDI signal). Connect DIN-4 to the 6N138 CATHODE pin (MIDI return). Leave DIN-2 unconnected. On the logic side of the 6N138, connect VCC to Arduino 5V (power), GND to Arduino GND (ground), and OUT to Arduino D2 (MIDI data). Connect the 10 kΩ resistor between Arduino 5V and the 6N138 OUT pin (signal pull-up).
- Tip: Verify the DIN socket contact numbers from the molded markings or its datasheet while looking at the solder side; connector views are easy to mirror.
- Tip: Keep the MIDI socket's DIN-2 shield contact disconnected so the MIDI device remains electrically isolated from the Arduino.
- ⚠ Do not connect the MIDI socket contacts directly to an Arduino pin — the optocoupler is the small isolation part that protects the Arduino from the external MIDI device.
Make the first safe power-up
With the 12 V supply still unplugged, check every driver for a bridge between VMOT and VDD; these are different power connections and must not touch. Plug the Arduino into USB, then use Schematik's Deploy button to load the firmware. Finally, connect your MIDI device's MIDI OUT to the new MIDI socket with a standard MIDI cable, then plug in the 12 V supply.
- Tip: Start with one motor connected and a low driver current limit, then add the other two after it responds correctly.
- Tip: Send notes on MIDI channels 1, 2, and 3. Hold a note to run its corresponding motor; release it to stop.
- ⚠ Keep fingers, loose clothing, and cables away from motor shafts and anything attached to them before applying 12 V power.
Pin assignments
Board wiring reference| Pin | Connection | Type |
|---|---|---|
| EXT | motor_supply +12V → DRV8825 Stepper Driver VMOT | power |
| EXT | motor_supply +12V → DRV8825 Stepper Driver VMOT | power |
| EXT | motor_supply +12V → DRV8825 Stepper Driver VMOT | power |
| EXT | motor_supply +12V → 100 µF 35 V Electrolytic Capacitor + | power |
| EXT | motor_supply GND → DRV8825 Stepper Driver GND | ground |
| EXT | motor_supply GND → DRV8825 Stepper Driver GND | ground |
| EXT | motor_supply GND → DRV8825 Stepper Driver GND | ground |
| EXT | motor_supply GND → 100 µF 35 V Electrolytic Capacitor - | ground |
| GND | motor_supply GND | ground |
| 5V | driver_1 VDD | power |
| GPIO 3 | driver_1 STEP | data |
| GPIO 4 | driver_1 DIR | data |
| GND | driver_1 M0 | ground |
| GND | driver_1 M1 | ground |
| GND | driver_1 M2 | ground |
| EXT | driver_1 1A → Nema 17 A+ | digital |
| EXT | driver_1 1B → Nema 17 A- | digital |
| EXT | driver_1 2A → Nema 17 B+ | digital |
| EXT | driver_1 2B → Nema 17 B- | digital |
| 5V | driver_2 VDD | power |
| GPIO 5 | driver_2 STEP | data |
| GPIO 6 | driver_2 DIR | data |
| EXT | driver_2 1A → Nema 17 A+ | digital |
| EXT | driver_2 1B → Nema 17 A- | digital |
| EXT | driver_2 2A → Nema 17 B+ | digital |
| EXT | driver_2 2B → Nema 17 B- | digital |
| 5V | driver_3 VDD | power |
| GPIO 9 | driver_3 STEP | data |
| GPIO 10 | driver_3 DIR | data |
| EXT | driver_3 1A → Nema 17 A+ | digital |
| EXT | driver_3 1B → Nema 17 A- | digital |
| EXT | driver_3 2A → Nema 17 B+ | digital |
| EXT | driver_3 2B → Nema 17 B- | digital |
| EXT | midi_input DIN-5 → Resistor P1 | data |
| EXT | midi_r1 P2 → 6N138 Optocoupler ANODE | digital |
| EXT | midi_input DIN-4 → 6N138 Optocoupler CATHODE | data |
| 5V | midi_opto VCC | power |
| GND | midi_opto GND | ground |
| GPIO 2 | midi_opto OUT | digital |
| 5V | midi_pullup P1 | power |
| EXT | midi_pullup P2 → 6N138 Optocoupler OUT | digital |
| GND | driver_1 EN | ground |
| GND | driver_2 EN | ground |
| GND | driver_3 EN | ground |
Firmware
Arduino#include <AccelStepper.h>
#include <SoftwareSerial.h>
// Forward declarations
float noteToSpeed(uint8_t note);
void stopMotor(uint8_t index);
void handleMidiMessage(uint8_t status, uint8_t data1, uint8_t data2);
void readMidi();
const uint8_t MIDI_RX_PIN = 2;
const uint8_t MOTOR1_STEP_PIN = 3;
const uint8_t MOTOR1_DIR_PIN = 4;
const uint8_t MOTOR2_STEP_PIN = 5;
const uint8_t MOTOR2_DIR_PIN = 6;
const uint8_t MOTOR3_STEP_PIN = 9;
const uint8_t MOTOR3_DIR_PIN = 10;
const float MIN_SPEED = 30.0f;
const float MAX_SPEED = 800.0f;
SoftwareSerial midiSerial(MIDI_RX_PIN, 8); // RX only; pin 8 is unused TX.
AccelStepper motor1(AccelStepper::DRIVER, MOTOR1_STEP_PIN, MOTOR1_DIR_PIN);
AccelStepper motor2(AccelStepper::DRIVER, MOTOR2_STEP_PIN, MOTOR2_DIR_PIN);
AccelStepper motor3(AccelStepper::DRIVER, MOTOR3_STEP_PIN, MOTOR3_DIR_PIN);
AccelStepper *motors[3] = {&motor1, &motor2, &motor3};
int activeNote[3] = {-1, -1, -1};
uint8_t runningStatus = 0;
uint8_t dataBytes[2];
uint8_t dataCount = 0;
float noteToSpeed(uint8_t note) {
return MIN_SPEED + (MAX_SPEED - MIN_SPEED) * ((float)note / 127.0f);
}
void stopMotor(uint8_t index) {
motors[index]->setSpeed(0.0f);
activeNote[index] = -1;
}
void handleMidiMessage(uint8_t status, uint8_t data1, uint8_t data2) {
uint8_t command = status & 0xF0;
uint8_t channel = (status & 0x0F) + 1;
if (channel < 1 || channel > 3) {
return;
}
uint8_t index = channel - 1;
if (command == 0x90 && data2 > 0) {
activeNote[index] = data1;
motors[index]->setSpeed(noteToSpeed(data1));
} else if (command == 0x80 || (command == 0x90 && data2 == 0)) {
if (activeNote[index] == data1) {
stopMotor(index);
}
}
}
void readMidi() {
while (midiSerial.available() > 0) {
uint8_t value = (uint8_t)midiSerial.read();
if (value >= 0xF8) {
continue; // MIDI timing and transport bytes can occur anywhere.
}
if (value & 0x80) {
if (value >= 0x80 && value <= 0xEF) {
runningStatus = value;
dataCount = 0;
} else {
runningStatus = 0;
dataCount = 0;
}
continue;
}
if (runningStatus == 0) {
continue;
}
uint8_t command = runningStatus & 0xF0;
uint8_t needed = (command == 0xC0 || command == 0xD0) ? 1 : 2;
dataBytes[dataCount++] = value;
if (dataCount >= needed) {
if (needed == 2) {
handleMidiMessage(runningStatus, dataBytes[0], dataBytes[1]);
}
dataCount = 0;
}
}
}
void setup() {
midiSerial.begin(31250);
for (uint8_t i = 0; i < 3; i++) {
motors[i]->setMaxSpeed(MAX_SPEED);
motors[i]->setSpeed(0.0f);
motors[i]->setMinPulseWidth(3);
}
}
void loop() {
readMidi();
motor1.runSpeed();
motor2.runSpeed();
motor3.runSpeed();
}“Deploy to device” opens this project in Schematik, where you can flash it to your board over USB.
Remix this project
Make it yours in one click
Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.