← Back to blog
HardwareFirmwareR&D

I Put Two Overpowered Motors on My Wheelchair

Photo of Vladislav Mihov

Vladislav Mihov

· 6 min read

I Put Two Overpowered Motors on My Wheelchair

Manual wheelchairs are cheap, light, and everywhere. Electric wheelchairs are none of those things. A decent one runs several thousand euros, and you're buying a whole new chair, not upgrading the one you already have. So: what if the electric part was just something you bolted onto the back of a normal chair?

The base chair

Every add-on project starts with a victim. Ours: a stock manual chair, nothing special about it. Nothing on the chair itself gets touched. The drive unit comes off the chair's own frame.

Stock manual wheelchair, the base chair for the conversion
The base chair. Unmodified, off the shelf.

Before any metal got cut, the drive unit existed only as a Fusion 360 assembly. Drag it around.

drive unit frame, solid geometry, decimated for web

Cutting the frame

The frame is mostly laser-cut sheet steel, folded and welded into a cage that straddles the rear axle. The parts around the motor mount are lathe-turned, plus two 3D-printed holders. Electronics and joystick enclosures are 3D-printed too.

Laser-cut steel plates and brackets laid out on a welding table before assembly
Laser-cut plates before welding. Most of the BOM by part count.
Welder in helmet and gloves striking an arc on the frame
Welding the frame together.
Finished welded motor mount bracket with clean bead work
Motor mount bracket, welded.
Partially welded frame sections clamped in a welding jig
Side frames clamped square in the jig before final assembly.

Powertrain

  • Two 2kW motors, one per wheel
  • Flipsky dual VESC, one board with two motor outputs, wrapped in a carbon-fiber-look 3D printed enclosure
  • 10S4P battery pack, 42V nominal, up to 80A discharge
  • Arduino Uno reading a joystick and turning it into VESC commands over UART
Fully welded, powder-coated black drive unit frame
Frame welded and coated.
Close-up of motor mount with VESC controller box and wiring harness
Motor mount and VESC enclosure, mid-wiring. That's a heat gun next to it, not a soldering iron. Heat-shrink duty.
Drive unit unbolted from the chair, motors in full view.

The firmware

the control loop
const int PIN_X = A0;
const int PIN_Y = A1;

const int CENTER = 512;
const int DEADZONE = 40;

const float MAX_CURRENT_A = 12.0f;   // start here, increase later

const uint8_t ID_OTHER = 63;         // other VESC over CAN

float joyToNorm(int v) {
  int d = v - CENTER;
  if (abs(d) < DEADZONE) return 0.0f;
  float n = (float)d / 511.0f;
  if (n > 1.0f) n = 1.0f;
  if (n < -1.0f) n = -1.0f;
  return n;
}

void loop() {
  float x = joyToNorm(analogRead(PIN_X));
  float y = joyToNorm(analogRead(PIN_Y));

  // Tank mix
  float left  = y + x;
  float right = y - x;

  left  = constrain(left,  -1.0f, 1.0f);
  right = constrain(right, -1.0f, 1.0f);

  float iLeft  = left  * MAX_CURRENT_A;
  float iRight = right * MAX_CURRENT_A;

  setCurrentLocal(iLeft);
  setCurrentOther(ID_OTHER, iRight);

  delay(20); // 50 Hz continuous updates
}

MAX_CURRENT_A is capped at 12A on purpose. Better to be underpowered than to launch someone into a wall.

packet framing + CAN forward
void sendPacket(const uint8_t *payload, uint8_t len) {
  uint8_t frame[260];
  uint8_t idx = 0;
  frame[idx++] = 2;
  frame[idx++] = len;
  for (uint8_t i = 0; i < len; i++) frame[idx++] = payload[i];
  uint16_t c = crc16(payload, len);
  frame[idx++] = (c >> 8) & 0xFF;
  frame[idx++] = c & 0xFF;
  frame[idx++] = 3;
  Serial.write(frame, idx);
}

// other VESC via CAN-forward
void setCurrentOther(uint8_t can_id, float currentA) {
  int32_t mA = (int32_t)(currentA * 1000.0f);
  uint8_t p[7] = {34, can_id, 6,
    (uint8_t)((mA >> 24) & 0xFF),
    (uint8_t)((mA >> 16) & 0xFF),
    (uint8_t)((mA >> 8) & 0xFF),
    (uint8_t)(mA & 0xFF)
  };
  sendPacket(p, 7);
}

Command ID 34 is COMM_FORWARD_CAN. Wrap a normal "set current" command (ID 6) inside it, address the second VESC's CAN ID, and the first VESC routes it over the bus.

That grip on the push handle is running the loop above, fifty times a second.

Riding it

Tuning is done through the VESC app over USB. Everything else through the Arduino IDE.

First stroll around the office.
Photo of Vladislav Mihov

Written by

Vladislav Mihov

Founder, Synaptic

Vladislav leads R&D and software delivery at Synaptic, working with medical device makers and startups to ship data-driven products.