← Back

Myoelectric Prosthetic Hand

An underactuated single-servo prosthetic hand that grasps everyday 20–400g objects from surface EMG alone.

SolidWorks · KiCAD · C++ · Arduino · Analog Circuits

Mechanical

The prosthetic combines 3D-printed PLA with machined sheet metal. From our target stakeholders' needs, grasping and moving kitchen objects was the main objective — hence a simple pincer over a full hand. It's actuated by a single continuous-rotation motor: a worm drives two worm wheels (one per claw) to open and close the hand. Silicone pads on the claw tips increase friction and deform slightly around irregular or small objects.

CAD model of the prosthetic — worm-and-worm-wheel drive above the pincer arm with a textured silicone grip pad
CAD model — one side shown assembled; the mirrored claw is omitted for clarity.
Assembled prosthetic in motion.

Electrical

A five-stage analog circuit processes and digitizes the input surface-EMG signal. The instrumentation amplifier (in-amp) and op-amps are powered single-supply — 5 V on the positive terminal, ground on the negative — with the in-amp referenced to 1.5 V; the comparator's negative input is set to 1.67 V via a voltage divider off the 5 V rail. The signal first enters the in-amp at a gain of 10, then passes through second-order high-pass and low-pass filters with cutoffs of 26 Hz and 500 Hz respectively, filtering out low-frequency motion artifacts and high-frequency electrical noise. It then passes through a non-inverting amplifier (gain 20) to amplify the clean signal before the peak detector, which rectifies and holds it. Finally, the signal passes through a comparator, which outputs a digital 0 V or 5 V signal for Arduino processing.

Signal chain

Input

Surface EMG

electrodes

Stage 1

In-amp

gain ×10

Stage 2

Band-pass

26–500 Hz

Stage 3

Non-inv amp

gain ×20

Stage 4

Peak detector

rectify + hold

Stage 5

Comparator

→ 0 / 5 V

Output

Arduino

grip state machine

Hand-soldered perfboard — an instrumentation amplifier and four op-amps in a row, with resistors, capacitors, and jumper wires implementing the analog EMG front end
Finished perfboard — the hand-soldered analog EMG front end.
2-layer KiCAD PCB layout of the analog EMG front endPCB routing showing the two copper layers of the EMG front end
2-layer PCB layout, designed in KiCAD.

Firmware

A non-blocking Arduino loop reads the comparator's digital output from a single pin — the analog front end already thresholds the signal, so no software filtering is required — and debounces it over a 30 ms window to reject chatter as the EMG signal crosses the threshold. A sustained contraction closes the pincer and relaxing reopens it, driving a continuous-rotation servo through writeMicroseconds (1500 µs to stop, 1300 / 1700 µs for each direction). Because a continuous-rotation servo gives no position feedback, the gripper is tracked open-loop: a 0–100 estimate is integrated from elapsed time over a hand-calibrated travel duration, and the motor is stopped at each limit; otherwise it would stall against the mechanical hard stops once fully open or closed, drawing stall current and overloading the servo and worm gearing.

motor_actuation.ino
#include <Servo.h>
Servo gripperServo;
const int emgPin   = 2; //end of circuit
const int servoPin = 9; //control motor
const int CLOSE_COMMAND = 1300;  // reversed: was 1700
const int OPEN_COMMAND  = 1700;  // reversed: was 1300
const int STOP_COMMAND  = 1500;
// Calibrate by hand-timing full travel each direction, trim ~10% off
const unsigned long CLOSE_TRAVEL_MS = 1800; //need to determine
const unsigned long OPEN_TRAVEL_MS  = 1800; //need to determine
const unsigned long DEBOUNCE_MS = 30; //delay
//true = HIGH, false = LOW
bool lastEmgReading = false;
bool stableEmgState = false;
unsigned long lastChangeTime = 0;
// Assume the gripper is fully open when powered on
float gripperPosition = 100.0;
unsigned long lastPositionUpdate = 0;
void setup() {
  pinMode(emgPin, INPUT);
  gripperServo.attach(servoPin);
  gripperServo.writeMicroseconds(STOP_COMMAND);
  lastPositionUpdate = millis();
  Serial.begin(9600);
}
void loop() {
  unsigned long currentTime = millis();
  unsigned long elapsedTime =
      currentTime - lastPositionUpdate;
  lastPositionUpdate = currentTime;
  // Read and debounce comparator output
  bool reading = digitalRead(emgPin);
  if (reading != lastEmgReading) {
    lastChangeTime = currentTime;
  }
  if (currentTime - lastChangeTime >= DEBOUNCE_MS) {
    stableEmgState = reading;
  }
  lastEmgReading = reading;
  if (stableEmgState) {
    // HIGH: close the grippers
    if (gripperPosition > 0.0) {
      gripperServo.writeMicroseconds(CLOSE_COMMAND);
      gripperPosition -=
          100.0 * elapsedTime / CLOSE_TRAVEL_MS;
      if (gripperPosition <= 0.0) {
        gripperPosition = 0.0;
        gripperServo.writeMicroseconds(STOP_COMMAND);
      }
    }
    else {
      // Already fully closed
      gripperServo.writeMicroseconds(STOP_COMMAND);
    }
  }
  else {
    // LOW: open the grippers
    if (gripperPosition < 100.0) {
      gripperServo.writeMicroseconds(OPEN_COMMAND);
      gripperPosition +=
          100.0 * elapsedTime / OPEN_TRAVEL_MS;
      if (gripperPosition >= 100.0) {
        gripperPosition = 100.0;
        gripperServo.writeMicroseconds(STOP_COMMAND);
      }
    }
    else {
      // Already fully open
      gripperServo.writeMicroseconds(STOP_COMMAND);
    }
  }
  Serial.print("raw=");
  Serial.print(reading);
  Serial.print("  stable=");
  Serial.print(stableEmgState);
  Serial.print("  pos=");
  Serial.println(gripperPosition);
}