Geard is a project for running ML inference on biosignal data (EEG/EMG) with a split architecture: device code runs on your laptop (or on the Arduino Uno Q's MPU), while the Arduino Uno Q MCU runs the firmware for analog input and motor control.
| Location | Role |
|---|---|
| Device (laptop or MPU) | Python code for data handling, analysis, and model inference. |
arduino-q/ |
Code targeting the Arduino Uno Q: MCU sketch + MCP (Python) in runtime/. |
training/ |
Scripts used to train and evaluate models on data collected from the device. |
Firmware and MCP live in arduino-q/runtime/.
- Training — Collect EEG/EMG at 100 Hz; save CSV on Ctrl+C. Configure via variables at the top of
MCP.py(interval, output path, preprocess, etc.). - Runtime — 100 Hz control loop: EMG → steering (BLDC Motor 1), EEG → throttle/brake → velocity → speed wheel (PWM). Set
RUN_MODE = "runtime"inMCP.pyor runpython MCP.py --mode runtime.
#define MODE MODE_TRAINING— Data collection;readAnalogChannels()also prints to Serial.#define MODE MODE_MAIN— Production; no Serial printing. Use for runtime control.
Top-of-file options: RUNTIME_RING_BUFFER_SIZE (default 300), EEG_MODE (amp or ML), motor pin defines. RPCs: readAnalogChannels, getRecentWindow, getConfig, setSteering, setSpeedWheel.
- Training: Edit
COLLECT_INTERVAL_SEC,OUTPUT_CSV,PREPROCESS,PRINT_SAMPLES,SOCKET_PATHat the top. Runpython MCP.py(optional-o file.csv). Stop with Ctrl+C to write CSV. - Runtime: Set
RUN_MODE = "runtime"or use--mode runtime. Loop runs at 100 Hz: one sample per tick, steering from (EMG1−EMG2) normalized, throttle/brake from EEG (amp moving average or ML model stub), thensetSpeedWheel(velocity).
See Wiring and Directions below for hardware and step-by-step usage.
All analog and digital pins refer to the Arduino Uno Q (MCU). Use 3.3V logic for analog inputs unless your sensors are 5V-tolerant and the board allows it.
| Function | Pin | Notes |
|---|---|---|
| EEG channel 1 | A0 | Analog in, 12-bit ADC |
| EEG channel 2 | A1 | Analog in, 12-bit ADC |
| EMG channel 1 | A2 | Analog in, 12-bit ADC |
| EMG channel 2 | A3 | Analog in, 12-bit ADC |
| Motor 1 (steering) Phase A | 9 | PWM, SimpleFOC shield |
| Motor 1 Phase B | 5 | PWM, SimpleFOC shield |
| Motor 1 Phase C | 6 | PWM, SimpleFOC shield |
| Motor 1 Enable | 8 | Digital out, HIGH = driver on |
| Speed wheel | 3 | PWM, DC or second motor |
Arduino Uno Q (MCU)
+----------------------------------------------------------+
| Analog inputs (12-bit, 0-3.3V) |
| A0 <---- EEG 1 (signal) A1 <---- EEG 2 (signal) |
| A2 <---- EMG 1 (signal) A3 <---- EMG 2 (signal) |
| GND ---- sensor grounds 3.3V ---- (if needed) |
+----------------------------------------------------------+
| Motor 1 (steering) - SimpleFOC shield |
| D9 ---> Phase A D5 ---> Phase B D6 ---> Phase C |
| D8 ---> Enable (HIGH = on) |
| Motor power: use shield VIN/Vbat, not 5V from MCU |
+----------------------------------------------------------+
| Speed wheel |
| D3 (PWM) ---> DC motor driver or ESC input |
| GND --------> driver ground |
+----------------------------------------------------------+
- Connect each EEG electrode amplifier output to the corresponding analog pin (A0, A1).
- Reference and ground: common reference to GND; ensure amplifier output is within 0–3.3 V (or use voltage divider / level shifter if needed).
- Typical setup: dry or gel electrodes → amplifier (e.g. instrumentation amp, bandpass) → A0/A1. Do not connect raw electrodes directly to the MCU.
- Connect each EMG amplifier (envelope or raw, after appropriate gain) to A2 and A3.
- Same as EEG: keep signals within 0–3.3 V; use a common GND with the Arduino and the amplifier supply.
- Pinout: Pins 9, 5, 6 → shield Phase A, B, C; Pin 8 → Enable (HIGH = driver on). This matches the typical SimpleFOC shield layout for Arduino (e.g. 3-phase driver using D9, D5, D6 and Enable on D8).
- Firmware: The current MCU code uses open-loop PWM (one or two phases driven by magnitude/direction), not the SimpleFOC library. So it is compatible with the same pins as the SimpleFOC board and will drive a BLDC connected through that shield, but it does not use FOC or the official SimpleFOC API. For sensorless FOC or full library features, integrate the SimpleFOC library (e.g.
BLDCMotor+BLDCDriver3PWM) and call it fromsetSteering(). - Power: Use the shield’s motor supply (VIN/Vbat), not the Arduino 5 V rail, for the motor.
- Pin 3 (PWM) → input of a DC motor driver or ESC (e.g. throttle input). PWM duty = normalized speed (0–1).
- Ground the driver/ESC logic ground to Arduino GND.
- Wire EEG (A0, A1) and EMG (A2, A3) as above. Optionally connect motors for later; they stay off during collection if you do not call
setSteering/setSpeedWheel. - In
arduino-q/runtime/MCU.cppset#define MODE MODE_TRAINING, then build and flash the sketch. - In
arduino-q/runtime/MCP.pysetRUN_MODE = "training"(default). EditCOLLECT_INTERVAL_SEC,OUTPUT_CSV,PREPROCESS,PRINT_SAMPLES,SOCKET_PATHas needed. - Run MCP from
arduino-q/runtime/:python MCP.py. Optional:python MCP.py -o my_data.csv - Let it run; stop with Ctrl+C. CSV is written to
OUTPUT_CSV(or-opath).
- Use the CSV(s) from step 1 with
training/(e.g.Model.py,train.py,eval.py). Preprocessing inarduino-q/pipelines/preprocessing.pycan be reused for consistency.
- Wire Motor 1 (steering) to pins 8, 9, 5, 6 and speed wheel to pin 3 as in the schematic.
- In
MCU.cppset#define MODE MODE_MAIN. SetEEG_MODEtoEEG_MODE_AMPorEEG_MODE_MLas needed. Reflash. - In
MCP.pysetRUN_MODE = "runtime"(or run with--mode runtime). AdjustRUNTIME_INTERVAL_SEC,EMG_NORM_WINDOW,EEG_AMP_WINDOW, velocity/friction constants if desired. - Run:
python MCP.py --mode runtime. Steering follows (EMG1 − EMG2) normalized; throttle/brake from EEG (amp or ML); speed wheel reflects internal velocity. Stop with Ctrl+C.
- Amp (
EEG_MODE_AMP): Throttle when EEG amplitude is above moving average, brake when below (dead zone inMCP.py:EEG_DEADZONE). - ML (
EEG_MODE_ML): MCP callsgetRecentWindow()and will run a PyTorch model for binary throttle/brake once the model is loaded in code.
Scripts and model code for training and evaluation, run on the laptop using data collected via arduino-q/runtime/.
| File | Purpose |
|---|---|
Model.py |
PyTorch model definition (e.g. for biosignal classification/regression). |
train.py |
Training script: load data, train the model, save checkpoints. |
eval.py |
Evaluation script: load a trained model and compute metrics on held-out data. |
Data is gathered with the Arduino running runtime (with MODE set to MODE_TRAINING) and the laptop recording the RPC/bridge stream; the same preprocessing used in arduino-q (e.g. pipelines/preprocessing.py) can be applied before or during training.
Code that runs on the laptop (device), not on the Arduino:
analysis.py— Top-level analysis pipeline; usespipelines.preprocessingto preprocess data before further analysis or inference.pipelines/preprocessing.py— Preprocessing for 2D sensor×time data (e.g. high-pass, low-pass, FFT stubs). Used for both data preparation and the runtime inference path.
- Data collection — Set
MODEtoMODE_TRAININGinarduino-q/runtime/MCU.cpp, flash it to the Arduino Uno Q, runpython MCP.pyfromarduino-q/runtime/; stop with Ctrl+C to save CSV. - Training — Use
training/train.py(andModel.py) on the collected data; evaluate withtraining/eval.py. - Runtime — Set
MODEtoMODE_MAINinarduino-q/runtime/MCU.cpp, reflash; setRUN_MODE = "runtime"or runpython MCP.py --mode runtimefor the 100 Hz control loop (steering + speed wheel).