Clench your fist and the gripper closes on the cube.
Controlling a robot arm with a joystick, keyboard, or phone app works, but it always occupies your hands. What if the robot simply reacted to your muscles?
In this project, a reusable dry-electrode surface EMG (sEMG) band worn on the forearm drives the adaptive gripper on an Elephant Robotics myCobot 280 for Arduino. Clench your fist and the gripper closes; relax your hand and it opens.
Best of all, there is no PC in the control loop. A single Arduino UNO R4 WiFi samples the muscle sensor at 1, 000 Hz, strips away power-line hum using a 13-stage digital filter, tracks your personal resting baseline automatically, renders a live signal bar on its built-in 12×8 LED matrix, and sends gripper commands directly to the robot arm over hardware serial (Serial1).
Whenever your brain tells a muscle to contract, motor nerves fire the muscle fibers in tiny, rapid electrical bursts. Electromyography (EMG) measures the sum of those electrical potentials. The harder you clench, the more muscle fibers fire at once, and the larger the resulting voltage becomes—which is why clench strength can be measured and turned into a control signal.
Not all EMG sensors work the same way:
Dry electrodes are ideal for robotics projects because you can strap them on in seconds without sticky gels or skin prep. You also have a choice between envelope modules (which output a pre-smoothed DC voltage) and raw-signal modules. Our sensor outputs the raw amplified signal (×1000 gain, centered around ~1.6 V), which means we can design our own digital filters in software to reject interference cleanly.
What does a raw surface EMG signal actually look like?
•Tiny: Only microvolts (µV) to a few millivolts (mV) on the skin surface—hence the onboard ×1000 instrumentation amplifier.
•Fast: Most useful muscle energy sits between 20 Hz and 450 Hz, which is why we sample at 1, 000 Hz (satisfying the Nyquist limit).
•Noisy: Because dry electrodes have higher contact impedance than wet gel pads, your body acts like an antenna for 50 Hz / 60 Hz mains hum and its harmonics, alongside low-frequency motion artifacts. Without proper filtering and strap tension, power-line hum can easily swamp the actual muscle signal.
To detect a fist clench, the sensor sits on the inner forearm over the finger flexor muscles—the muscle belly that firms up when you curl your fingers or squeeze your hand. It is the exact same principle used in clinical myoelectric prosthetic hands, scaled down to a weekend workbench build.
The myCobot 280 for Arduino is designed to mate directly with UNO-form-factor boards, and the Arduino UNO R4 WiFi turns out to be a huge upgrade over the classic ATmega328P UNO for real-time biosignal processing. Every key feature of the R4 pulls its weight in this build:
The whole setup: EMG module, UNO R4 WiFi (its LED matrix is the clench meter) and the band on the forearm.
Step 1: Connect the EMG Sensor to the Arduino UNO R4 WiFiOn the UNO R4 WiFi: the EMG module on 5V (red), GND (black) and A0 (yellow). The green wire on the other GND goes to the arm base.
Plug the electrode lead cable into the sensor module and strap the dry electrodes snugly onto your inner forearm over the flexor muscle belly.
Step 2: Connect the Arduino UNO R4 WiFi to the myCobot BaseOn the UNO R4 WiFi: the two serial wires on D0 (RX) and D1 (TX).
On the myCobot 280 base: the same two wires on RX and TX, and the green wire on GND.
⚠️ IMPORTANT — Wire TX→TX and RX→RX (Straight Through)! Almost every UART tutorial tells you to cross TX and RX (TX→RX), but do not cross them here. The myCobot 280 base already crosses the TX and RX lines internally behind its connector. Wire D1 (TX) straight to TX, D0 (RX) straight to RX, and always include the shared GND wire or serial commands will fail.
Step 3: Power Up Sequence (Critical: Unplug Your Laptop Charger!)•Plug the myCobot 280 into its dedicated DC power supply (the Arduino cannot power the arm's servos).
•Power the Arduino UNO R4 WiFi cleanly:
–If connected to a laptop via USB: You must unplug the laptop's AC charger (and even better, turn off / unplug the charger brick at the wall) so the laptop runs strictly on battery. Laptop switching power supplies inject massive 50/60 Hz mains leakage and high-frequency switching noise directly through the USB ground into your skin and the EMG sensor, which will swamp the signal.
•Power the robot arm on first, then power or reset the Arduino. On boot, the firmware sends a powerOn() command and queries the arm's Atom firmware version over Serial1.
Step 4: Flash the Firmware1.In the Arduino IDE, open Tools → Board → Boards Manager and install Arduino UNO R4 Boards.
2.Open firmware/emg_gripper/emg_gripper.ino from the project repository. The configured MyCobotBasic library files are already bundled inside the sketch folder—no external library installation is required.
3.Check the GRIP_TYPE constant near the top of emg_gripper.ino so it matches your gripper (1 = Adaptive, 2 = 5-Finger, 3 = Parallel, 4 = Flexible).
4.Select Arduino UNO R4 WiFi and your COM port, then click Upload.
Step 5: First Test•Check the LED Matrix: With your arm relaxed on the table, 1 column on the R4's LED matrix should stay lit (showing the board is alive and tracking your rest floor). Clench your fist firmly—the bar should sweep across and light up 8 to 12 columns.
•Check the Gripper: Clench your fist and hold for a fraction of a second—the gripper closes. Relax your hand—the gripper opens.
•Quick Troubleshooting:
1.If the LED bar is pegged high at rest or barely responds when you clench, unplug your laptop's power adapter (and cut power to the charger) and tighten the forearm strap so all metal contacts press firmly against the skin.
2.If the LED bar fills up on a clench but the gripper doesn't move, verify the arm's DC power brick is switched on and double-check that D1 (TX) goes to TX and D0 (RX) goes to RX.
5. How It Works: From Microvolts to Robot MotionTo keep the story readable, the four code excerpts below are simplified to highlight one core idea each—see the full source repository linked at the bottom for all state definitions and edge-case guards.
(1)Designing the Filter (Python → C Header)
My resting signal was initially dominated by 50 Hz mains hum and its harmonics (100 Hz, 150 Hz, all the way up to 450 Hz). A single narrow 50 Hz notch filter barely made a dent. Adding a 20–450 Hz 4th-order Butterworth bandpass (which also strips out the sensor's ~1.6 V DC offset and motion drift) plus wide notch filters (Q=5) across all 9 harmonics boosted the clench-to-rest signal ratio from ~5× to ~28×:
# Simplified — see emg/filters.py for full implementation
from scipy import signal
import numpy as np
fs, nyq = 1000, 500
sections = [signal.butter(4, [20/nyq, 450/nyq], btype="bandpass", output="sos")]
for k in range(1, 10): # 50, 100, ... 450 Hz harmonics
b, a = signal.iirnotch(50*k / nyq, Q=5) # wide notches: mains drifts ±0.5 Hz
sections.append(signal.tf2sos(b, a))
sos = np.vstack(sections) # exported to meter_filter.h
(If you live in a 60 Hz region, simply change 50 to 60 in config.yaml and regenerate the header).(2)Running the Filter at 1, 000 Hz on the Arduino
The sos coefficient matrix is exported into meter_filter.h as 13 second-order sections (biquads). Inside the UNO R4's 1 kHz loop, each new ADC reading passes through the cascade using Transposed Direct Form II, followed by a fast exponential moving average to extract the muscle activity envelope:
// Simplified — see firmware/emg_gripper/emg_gripper.ino
// 13 cascaded biquads, transposed direct form II
float f = (float)sample - firstSample;
for (int i = 0; i < METER_SOS_N; i++) {
const float *c = METER_SOS[i]; // b0, b1, b2, a1, a2
float o = c[0]*f + state[i][0];
state[i][0] = c[1]*f - c[3]*o + state[i][1];
state[i][1] = c[2]*f - c[4]*o;
f = o;
}
envelope += 0.01f * (fabsf(f) - envelope); // smoothed muscle activity level(3) Self-Calibrating Adaptive "Rest" Floor
A fixed threshold in millivolts or ADC counts breaks as soon as you adjust the strap or your skin warms up. Instead, the firmware continuously tracks the quietest moment over the last 0.5 seconds (quietest) and measures how many multiples above your own resting floor you currently are:
// Simplified — see firmware/emg_gripper/emg_gripper.ino
// Track the quietest moment in the last 0.5 s
if (quietest < restLevel) restLevel += 0.10f * (quietest - restLevel); // fall fast toward quiet
else restLevel = fminf(quietest, restLevel * 1.002f); // rise very slowly (~5%/s)
float ratio = envelope / restLevel; // ~1.0 = relaxed, 6.0+ = clenchBecause restLevel falls quickly when your arm relaxes but rises at most by ×1.002 per tick (~5% per second), holding a long, hard clench won't drag your baseline up and cause the gripper to let go prematurely.
(4)Hysteresis + Hold Time = Chatter-Free Gripper Control
Finally, updateGripper() converts that ratio into open/close actions using two separate thresholds (hysteresis) and a 150 ms hold timer:
// Simplified — see firmware/emg_gripper/emg_gripper.ino
void updateGripper(float ratio) {
bool wantFlip = gripClosed ? (ratio <= 3.0f) // relax -> open below 3x
: (ratio >= 6.0f); // clench -> close above 6x
if (!wantFlip) { gripSince = 0; return; }
if (gripSince == 0) gripSince = millis();
if (millis() - gripSince < 150) return; // must hold 150 ms: no chatter
gripClosed = !gripClosed;
myCobot.setGripperState(gripClosed ? 1 : 0, 70, GRIP_TYPE);
}Closing requires crossing 6× rest, while opening requires dropping below 3× rest, and the new state must hold steady for 150 ms. Hovering near a threshold or accidentally bumping the sensor cable cannot make the gripper chatter. And because setGripperState() transmits just 9 bytes at 1 Mbaud (~80 µs) without waiting for a slow reply, the 1, 000 Hz sampling loop never misses a beat.
6. Onboard 12×8 LED Matrix MeterYou don't need a laptop to get the electrode placement right. The UNO R4 WiFi's built-in 12×8 LED matrix renders ratio = envelope / restLevel on a logarithmic scale at 25 Hz (where 1 column is rest and all 12 columns represent 50× rest). A logarithmic scale gives crisp visual feedback for both light finger twitches and full-force clenches. We also keep at least 1 column lit at all times so you never wonder whether the board has power.
7. Lessons Learned & TroubleshootingWe hit (and solved) several real-world hardware and signal traps building this system. If you build your own EMG controller, keep these in mind:
(1)Unplug the laptop power supply (and cut AC power to the charger!). A plugged-in laptop charger is one of the biggest sources of EMG interference. Switching power supplies couple 50/60 Hz mains hum and high-frequency common-mode noise straight into the USB ground—and onto your skin. Always unplug the laptop's power brick and run on battery (or better yet, switch off/unplug the charger from the wall outlet completely, or power the UNO R4 from a standalone USB power bank).
(2)Strap tightness is everything. With a loose forearm strap, 75% of our raw signal power was 50 Hz mains hum, and a hard clench only raised the signal to 1.8× rest—completely unusable for control. Simply pulling the elastic strap snug reduced raw mains hum to 15% and raised the unfiltered clench ratio to ~6× before a single line of filter code even ran.
8. Current Limitations & What's Next: From 1 Channel to Full 6-DOF ControlWhy Only the Gripper? (The Single-Channel Limitation)Right now, this build uses a single-channel surface EMG sensor. A single pair of electrodes over the forearm sums together the electrical activity of whichever muscles sit underneath it into one 1D number (how hard you are contracting). Because of crosstalk between neighboring forearm muscles and lacks of spatial separation, a single channel cannot reliably distinguish between different finger movements, wrist rotations, or arm directions—it only gives us one clean, reliable degree of control: clench vs. relax to open and close the gripper.
Meanwhile, the myCobot 280 is a 6-DOF (six degrees of freedom) robot arm. Moving its end-effector freely in 3D space requires controlling six Cartesian axes—translation along X, Y, Z and rotation around Rx, Ry, Rz (plus the gripper itself). You simply cannot map one 1D muscle amplitude to 6 independent spatial axes without awkward mode-switching.
Future UpgradesTo unlock the rest of the robot arm, here is where the project goes next:
•Multi-Channel EMG Array for 6-DOF Control (X, Y, Z, Rx, Ry, Rz): By placing multiple EMG channels around the forearm and upper arm (e.g., wrist flexors, wrist extensors, pronator/supinator, biceps, and triceps—or using an 8-channel armband), a pattern-recognition classifier (such as LDA or a lightweight neural network) can decode distinct gestures and muscle synergies and map them directly to the arm's 6-DOF Cartesian movements (X, Y, Z, Rx, Ry, Rz).
•Proportional Grip Control: Even on a single channel, map the logarithmic envelope / restLevel ratio directly to setGripperValue(0..100) so squeezing gently closes the fingers partway and squeezing harder grips tighter.
•Wireless EMG Armband: Use the UNO R4 WiFi's onboard ESP32-S3 (or a small BLE wearable node) to stream the muscle signals over Wi-Fi/BLE so your arm isn't tethered by a wire to the robot base.
9. Credits & Links•Project Code Repository:
github.com/vhp8rc7p/hackster/tree/main/emg-control-mycobot
•Elephant Robotics:
MyCobotBasic Arduino Library& myCobot 280 documentation.
Interested on the product ?Click to find out more details on myCobot 280 for Arduino
Share your innovative Elephant Robotics projects with our community and join our User Case Initiative today:https://shop.elephantrobotics.com/












Comments