RAMCOD is a real-time myoelectric control training platform for mechano-electric prosthetics.
It captures EMG signals from surface electrodes, filters them through a clinical-grade MATLAB pipeline, and drives a servo motor on voluntary muscle contraction — replicating the closed-loop control of a real prosthetic limb.Full simulation available — run the complete system without any hardware using Wokwi + VS Code.
- System Architecture
- Hybrid Design Philosophy
- Hardware Requirements
- Software Requirements
- Repository Structure
- Installation
- Running the Application
- How RFC2217 Connects Python to Wokwi
- Usage Walkthrough
- Signal Processing Pipeline
- MATLAB Functions Reference
- Arduino Protocol Reference
- Configuration Reference
┌───────────────────────────────────────────────────────────────────────┐
│ PHYSICAL (Mode A) │
│ [AD8232 sensors] → [Arduino UNO] ──── USB UART 230400 ────────────── │
│ or │
│ SIMULATION (Mode B) │
│ [Wokwi potentiometers] → [Virtual Arduino] ── RFC2217 TCP:4000 ────── │
│ │ │
├───────────────────────────────────────────────────────────────────────┤
│ PYTHON HOST (src/python/) │
│ serial_comm.py → auto-detects hardware OR rfc2217://localhost:4000 │
│ matlab_bridge.py → MATLAB Engine OR SciPy fallback │
│ activation.py → refractory period + alternating S1↔S2 logic │
│ gui_*.py → PyQt5 GUI + pyqtgraph real-time plots │
├───────────────────────────────────────────────────────────────────────┤
│ MATLAB (src/matlab/functions/) │
│ emg_filter.m emg_rms.m emg_calibrate.m emg_activate.m │
└───────────────────────────────────────────────────────────────────────┘
| Layer | Tool | Rationale |
|---|---|---|
| Signal processing | MATLAB | Signal Processing Toolbox; validated butter/filtfilt; industry-standard in clinical labs |
| Application & GUI | Python | Modern GUI (PyQt5); modular architecture; deployable without MATLAB licence |
| Embedded firmware | Arduino C++ | Low-cost open hardware; real closed-loop for demonstrations |
| Circuit simulation | Wokwi | Full hardware simulation; RFC2217 bridge connects to real Python GUI |
The Python host calls MATLAB functions via the MATLAB Engine API. If MATLAB is unavailable, a numerically identical SciPy fallback runs transparently. A badge in the header shows the active backend.
| Component | Specification |
|---|---|
| Arduino UNO (or compatible) | ATmega328P, 5V |
| AD8232 EMG sensor modules | 1 or 2 (system validated with 2) |
| Surface EMG electrodes | Standard ECG/EMG snap-on electrodes |
| PWM servo motor | Any 5V standard servo (SG90, MG996R…) |
| USB-A to USB-B cable | Arduino connection |
| Sensor | Signal | LO+ | LO− |
|---|---|---|---|
| Sensor 1 | A0 | D8 | D9 |
| Sensor 2 | A1 | D10 | D11 |
| Sensor 3 | A2 | D12 | D13 |
| Sensor 4 | A3 | D2 | D4 |
| Servo PWM | D7 | — | — |
Sensors 3 & 4 mirror S1/S2 in the current 2-sensor hardware build. Full 4-sensor support is preserved in the code.
Python >= 3.10
PyQt5 >= 5.15.0
pyqtgraph >= 0.13.0
pyserial >= 3.5
numpy >= 1.24.0
scipy >= 1.11.0
- MATLAB R2022a or newer + Signal Processing Toolbox
cd <MATLAB_ROOT>/extern/engines/python
python setup.py install- Wokwi VS Code extension (free licence)
- Arduino CLI
- Arduino IDE 1.8+ or Arduino CLI
- Library: Servo (bundled with Arduino IDE)
RAMCOD/
├── src/
│ ├── arduino/
│ │ └── RAMCOD_firmware.ino Production firmware (230400 baud)
│ │
│ ├── matlab/
│ │ ├── functions/
│ │ │ ├── emg_filter.m
│ │ │ ├── emg_rms.m
│ │ │ ├── emg_calibrate.m
│ │ │ └── emg_activate.m
│ │ ├── RMC_GUI_Comun.mlapp Standalone MATLAB GUI
│ │ └── RAMCOD_tests.m 17 unit tests
│ │
│ ├── python/
│ │ ├── main.py Entry point
│ │ ├── requirements.txt
│ │ └── ramcod/
│ │ ├── serial_comm.py Hardware + RFC2217 auto-detection
│ │ ├── matlab_bridge.py MATLAB Engine + SciPy fallback
│ │ ├── activation.py Servo activation controller
│ │ ├── profiles.py JSON profile persistence
│ │ ├── worker.py QThread acquisition
│ │ ├── gui_constants.py Theme, widgets, constants
│ │ ├── gui_setup.py Setup panels
│ │ └── gui_training.py Real-time training view
│ │
│ └── wokwi/
│ ├── RAMCOD_wokwi.ino Simulation firmware (115200 baud)
│ ├── diagram.json Circuit: Arduino + pots + servo + LED
│ └── wokwi.toml VS Code config + rfc2217ServerPort=4000
│
├── profiles/ Saved user calibration profiles
├── docs/
│ └── RAMCOD_Technical_Documentation.docx
├── demo/
│ └── logo.png
└── README.md
cd src/python
pip install -r requirements.txt# Install Arduino CLI: https://arduino.github.io/arduino-cli/
arduino-cli core install arduino:avr- Open VS Code → Extensions → search Wokwi
- Install Wokwi for VS Code
- Press
F1→ Wokwi: Request a new License → follow browser prompt (free)
# 1. Flash firmware
arduino-cli compile --fqbn arduino:avr:uno src/arduino/RAMCOD_firmware.ino
arduino-cli upload --fqbn arduino:avr:uno --port /dev/ttyUSB0 src/arduino/RAMCOD_firmware.ino
# 2. Run GUI — auto-detects the board on COM/ttyUSB ports
cd src/python
python main.pyThe header badge shows ● COM3 (or equivalent). Connection LED is green.
This is the most impressive demo mode: the real Python GUI connects to a virtual Arduino running in Wokwi. No physical hardware needed.
arduino-cli compile \
--fqbn arduino:avr:uno \
--output-dir src/wokwi/build \
src/wokwi/RAMCOD_wokwi.inoOpen VS Code in the project root, then:
F1 → Wokwi: Start Simulator
The Wokwi panel opens — you see the Arduino UNO, two potentiometers, and the servo. Leave this panel visible (Wokwi pauses when hidden).
cd src/python
python main.pyClick Search Board in the GUI.
- The app first scans hardware COM ports (finds none)
- Then tries
rfc2217://localhost:4000— finds the Wokwi simulation - Header badge changes to ● Wokwi Simulation in cyan
- Connection LED turns blue
- Check Electrodes → returns
1,1,1,1(LO pins wired to GND in simulation) - Calibrate → captures 10 s baseline from the potentiometers at rest (leave them at 0)
- Start Training → real-time EMG plots appear
- Turn pot1 clockwise past 70% → servo in Wokwi moves to 0° + activation flash in GUI
- Turn pot2 clockwise past 70% → servo moves to 90° + flash
Use this to quickly show the circuit to someone without installing anything.
- Go to wokwi.com/projects/new/arduino-uno
- Paste
src/wokwi/RAMCOD_wokwi.ino→ sketch.ino tab - Paste
src/wokwi/diagram.json→ diagram.json tab - Press ▶ Play
- Open Serial Monitor → send commands:
| Command | Response |
|---|---|
C |
RAMCOD — handshake confirmed |
A |
1,1,1,1 — all sensors OK |
1 |
Starts 10 s binary stream. Turn pots to activate servo. |
S |
Sine wave test stream. Send any key to stop. |
┌──────────────────────────────────────────────────────────────────┐
│ Wokwi VS Code extension │
│ ┌─────────────────────────┐ │
│ │ Virtual Arduino UNO │ │
│ │ pot1 → A0 (EMG S1) │ │
│ │ pot2 → A1 (EMG S2) │ UART at 115200 baud (simulated) │
│ │ servo ← D7 │ │ │
│ └─────────────────────────┘ │ │
│ ↕ wokwi.toml: rfc2217ServerPort = 4000 │
│ ┌─────────────────────────┐ │ │
│ │ RFC2217 TCP server │◄───────┘ │
│ │ localhost : 4000 │ │
│ └─────────────────────────┘ │
└─────────────────┬────────────────────────────────────────────────┘
│ TCP (RFC2217 — serial-over-TCP standard)
┌─────────────────▼────────────────────────────────────────────────┐
│ Python — serial_comm.py │
│ serial.serial_for_url("rfc2217://localhost:4000", baud=115200) │
│ │
│ find_board() search order: │
│ 1. Scan hardware COM/ttyUSB ports → not found │
│ 2. Try rfc2217://localhost:4000 → RAMCOD handshake OK ✔ │
│ │
│ After connection — identical API regardless of mode: │
│ .check_sensors() .start_stream() .read_sample() │
│ .send_servo() .start_sine() .close() │
└──────────────────────────────────────────────────────────────────┘
RFC2217 is an IETF standard (RFC 2217) for tunnelling serial port data over TCP. PySerial supports it natively via serial_for_url() — no extra libraries needed. The virtual serial port behaves identically to a real COM port from the application's perspective.
| Step | Action | Result |
|---|---|---|
| ① Connect | Search Board | Auto-detects hardware or Wokwi simulation |
| ② Sensors | Check Electrodes | Green LED = contact OK (or always OK in simulation) |
| ③ Calibrate | Run Calibration Wizard | 10 s rest capture → baseline RMS per sensor |
| ④ Profile | Save | Persist calibration — skip next session |
| ⑤ Train | Start Training | Real-time raw + filtered EMG plots |
| ⑥ Activate | Contract muscle / turn pot | Servo moves + activation flash in GUI |
| ⑦ Tune | Sensitivity sliders | Adjust threshold without recalibrating |
Raw ADC [0–1023]
│
▼ emg_filter.m / scipy.signal.butter + filtfilt
Butterworth bandpass 10–200 Hz, order 2, zero-phase
│
▼ emg_rms.m / numpy
RMS envelope per 50 ms window → 4 values (one per sensor)
│
▼ emg_activate.m / activation.py
RMS > sensitivity × RMS_baseline ?
│ Yes → servo command byte → Arduino
│ + 1 s refractory period
│ + S1↔S2 alternation
▼
Servo moves
| Function | Signature | Returns |
|---|---|---|
emg_filter |
(raw_data, fs, 'LowHz', 10, 'HighHz', 200, 'Order', 2) |
filtered N×4 |
emg_rms |
(filtered_window) |
rms_vals 1×4, rectified N×4 |
emg_calibrate |
(filtered_data, active_sensors) |
noise_profile struct |
emg_activate |
(rms_vals, noise_profile, sensitivity, next_cmd) |
cmd uint8, sensor int |
Run unit tests from MATLAB:
cd src/matlab
run('RAMCOD_tests.m') % 17 assertions, 4 suites| Direction | Byte | Meaning |
|---|---|---|
| PC → Board | 'C' |
Handshake → "RAMCOD\r\n" |
| PC → Board | 'A' |
Sensor check → "1,0,1,0\r\n" |
| PC → Board | '1'–'6' |
Stream N×10000 samples |
| PC → Board | 'S' |
Sine test stream |
| Board → PC | uint16 ×4 | One sample, 8 bytes, LSB first: S1 S2 S3 S4 |
| PC → Board | uint8 2–4 | Servo → Position A |
| PC → Board | uint8 6–9 | Servo → Position B |
Baud rate: 230400 (hardware) / 115200 (Wokwi simulation).
| Parameter | File | Default | Notes |
|---|---|---|---|
BAUD_HARDWARE |
serial_comm.py |
230400 | Real Arduino |
RFC2217_URL |
serial_comm.py |
rfc2217://localhost:4000 |
Wokwi endpoint |
rfc2217ServerPort |
wokwi.toml |
4000 | Must match above |
SAMPLE_RATE |
gui_constants.py |
1000 Hz | Hardware nominal |
WINDOW_MS |
gui_constants.py |
50 ms | RMS window |
SCROLL_SEC |
gui_constants.py |
10 s | Plot time window |
CALIB_SECS |
gui_constants.py |
10 s | Calibration duration |
SERVO_POS_A/B |
firmware | 0° / 90° | Servo target angles |
EMG_THRESHOLD |
RAMCOD_wokwi.ino |
700/1023 | Wokwi standalone threshold |
Developed by Alonso Martín Díez · Biomedical Engineering · UEM 2026
