Control a Stepper Motor with EEG, MATLAB, and Arduino
This tutorial is based on the work of Shanuj Bansal, who presents the video series below, and Aleksandar Haber, who published the code repository: Low-Cost Brain Computer Interface by Using OpenBCI Cyton Board, MATLAB, and Arduino. All code referenced here belongs to its authors. Please see the original repository and the four-part video series below for the full walkthrough:
This tutorial shows how to build a low-cost bio-signal interface that turns eye blinks into motor movement. The Cyton board streams data from a single electrode to MATLAB over serial, MATLAB detects blinks in the signal and produces an on/off control value, and an Arduino uses that value to reverse the direction of a stepper motor.
Eye blinks appear as large, easy-to-detect peaks in a frontal electrode's signal. Strictly speaking they are an ocular artifact rather than brain activity, which makes them a reliable first control signal for a BCI project.
Video: Part 1 of the series, which introduces the headset and shows blink detection in the OpenBCI GUI.
What do I need?
- An OpenBCI Cyton Board and its USB dongle
- A way to wear the electrodes. The author uses the Ultracortex Mark IV headset, with one active electrode on Cyton channel 2 and reference electrodes clipped to the ears. Any setup that holds one electrode and a reference in place will work.
- MATLAB with these add-on toolboxes:
- Communications Toolbox - both EEG scripts call
de2bito decode the Cyton's packets. - Instrument Control Toolbox - the scripts use MATLAB's legacy
serial/fopeninterface. Use a MATLAB release that still supports it, or port the scripts to the newerserialportinterface.
- Communications Toolbox - both EEG scripts call
- An Arduino board and the Arduino IDE
- A stepper motor, a stepper motor driver, a power supply for the motor, and a breadboard
- A potentiometer (optional, used in the video to set motor speed)
Helpful Background Skills
- Basic MATLAB scripting
- Arduino or C-based programming
- Familiarity with the Cyton Data Format
How it works
- Acquire - MATLAB opens the Cyton dongle's serial port directly, with no OpenBCI GUI involved. It sends
vto soft-reset the board, thenbto start the binary data stream (andsto stop it). See the Cyton SDK for the full command list. - Process - MATLAB reads packets, decodes the 24-bit value for channel 2 (see the Cyton Data Format), and converts counts to volts using the default gain of 24x. Each loop it averages a batch of packets, removes the linear trend (
detrend), smooths with a 2-sample moving mean (movmean), and runs a 2-sample moving variance (movvar). A blink shows up as a sharp variance peak; otherwise the variance stays near zero. - Decide - The variance is compared to a threshold chosen by eye, producing a 0 (no blink) or 1 (blink).
- Actuate - MATLAB sends the value to the Arduino over a second serial port, and the Arduino reverses the stepper motor's direction.
Step 1 - Get the code
Download or clone the author's repository. It contains:
| File | Purpose |
|---|---|
conn_ard.m | Tests the MATLAB-to-Arduino link by sending a motor command |
matlab_cython.m | Streams and plots EEG from the Cyton, with no motor control |
final_project.m | The complete loop: EEG in, motor command out |
sketch_nov26a.ino | Arduino sketch that receives the commands (see the note below) |
At the time of writing, sketch_nov26a.ino in the author's repository is a saved GitHub web page (it starts with <!DOCTYPE html>), not Arduino code, so the Arduino IDE cannot compile it. A corrected sketch has been proposed to the author's repository. Until that is merged, open the file and confirm it starts with Arduino code (a comment block, then const int numChar), not HTML. If it doesn't, use the original sketch from Aleksandar Haber's matlab_arduino_interface repository. That version overflows its receive buffer when it is sent the long command that final_project.m produces, so fix index<=4 to index<numChar-1 in record().
Step 2 - Upload the Arduino sketch
Open the sketch in the Arduino IDE, select your board and port under Tools, and upload it.
The sketch listens on the serial port at 9600 baud for short text commands ending in >, for example 1123>. It reads the command and replies with the step count, followed by a carriage return, so you can check the link from the Serial Monitor (9600 baud, Carriage return line ending). Sending 0345> should return 345:
- The first character is the direction flag. In the video,
1reverses the motor and0leaves it unchanged. - The next three characters are meant for a step count, but the author notes this isn't implemented yet. The final project fills them with the placeholder
123. >marks the end of the command.
The sketch only parses the command and echoes it back. The code that drives the motor goes in its execute() function, where dir and distance hold the parsed values. In the video, the motor driver's pulse input is wired to pin 7 and its direction input to pin 6, with an optional potentiometer on A0 setting the pulse delay (and so the speed). Add your own driver code and check the wiring against your driver before powering the motor.
Step 3 - Set your serial ports
The example scripts use macOS-style port names. Replace them with your own:
- Arduino port - the one shown in the Arduino IDE under Tools > Port (for example
COM3on Windows or/dev/cu.usbmodem101on macOS). The scripts use 9600 baud. - Cyton port - the Cyton dongle's port (see Cyton Getting Started). The scripts use 115200 baud.
Step 4 - Test each half separately
- Run
conn_ard.mand confirm the motor responds. - Run
matlab_cython.mand confirm you see the raw, detrended, and filtered EEG plots.
Step 5 - Run the full loop
As published, final_project.m sends the Arduino the entire converted_signal vector, not just the current decision:
ard_str=[num2str(converted_signal),'123>'];
The Arduino reads only the first character of that string, which belongs to the oldest sample rather than the current one, so the unchanged script does not give per-blink control. Before running it, change that line to send only the latest decision:
ard_str=sprintf('%d123>',converted_signal(end));
Then place your electrode and ear references and run final_project.m. Each blink should reverse the motor's direction. Verify it: blink a few times and confirm the motor reverses on each blink and not otherwise. If it doesn't, display ard_str in MATLAB immediately before it is sent to check the outgoing commands, and see the threshold note below. To test the Arduino separately in the Serial Monitor, first stop the script and close MATLAB's Arduino serial connection; the two programs cannot use that port simultaneously.
The threshold in final_project.m is a fixed value the author picked by eye from the variance plot for their own setup. Expect to adjust it for your electrode placement, skin contact, and environment. Run matlab_cython.m first and look at the variance plot while blinking to choose a value.
The author recommends running the final script section by section from the MATLAB editor, so the Arduino serial connection isn't closed before you're finished.
Video Tutorials
The author's four-part series explains the code line by line:
Troubleshooting
- Port errors in MATLAB - Close any other program using the port (including the OpenBCI GUI) and run
instrfindallto find and close stale connections. - No EEG data - Check the dongle's switch position and that the Cyton is powered on. See Troubleshooting.
- Motor does not move - Run
conn_ard.mon its own to isolate the Arduino side.