Self-Balancing Robot in Matter.js with PID

This article provides a practical guide to creating an active two-wheeled self-balancing robot in the Matter.js 2D physics engine. You will learn how to model the inverted pendulum chassis and wheel base, read the robot's physical orientation, implement a Proportional-Integral-Derivative (PID) control algorithm, and apply dynamic corrective torques during the simulation loop to maintain equilibrium.

Modeling the Robot Structure

In a 2D physics environment like Matter.js, a two-wheeled self-balancing robot is viewed from the side, effectively modeled as a single wheel supporting an inverted pendulum body.

To construct this assembly:

  1. Wheel: Create a circular dynamic body using Matter.Bodies.circle. Ensure it has adequate friction so it can push off the ground without slipping uncontrollably.
  2. Chassis: Create a tall, thin rectangular dynamic body using Matter.Bodies.rectangle. Place its center of mass above the wheel.
  3. Axle Constraint: Bind the wheel and the chassis together using Matter.Constraint.create. Set the anchor point at the center of the wheel and the base of the chassis, with a stiffness of 1 and length of 0 to act as a revolute (pin) joint.
const wheel = Matter.Bodies.circle(400, 500, 30, {
    friction: 0.9,
    restitution: 0
});

const chassis = Matter.Bodies.rectangle(400, 420, 20, 140, {
    friction: 0.1,
    density: 0.002
});

const axle = Matter.Constraint.create({
    bodyA: wheel,
    bodyB: chassis,
    pointA: { x: 0, y: 0 },
    pointB: { x: 0, y: 60 },
    stiffness: 1,
    length: 0
});

Matter.Composite.add(engine.world, [wheel, chassis, axle]);

Implementing the PID Controller

Active balancing relies on calculating how far the chassis is tilted away from its vertical balance point and applying an opposing torque to the wheel to drive the base under the falling center of mass.

The PID algorithm computes the control output \(u(t)\) based on three terms:

let targetAngle = 0; // Upright vertical orientation
let integral = 0;
let lastError = 0;

const Kp = 0.8;
const Ki = 0.001;
const Kd = 12.0;

function computePID(currentAngle, deltaTime) {
    const error = targetAngle - currentAngle;
    integral += error * deltaTime;
    const derivative = (error - lastError) / deltaTime;
    lastError = error;

    return (Kp * error) + (Ki * integral) + (Kd * derivative);
}

Applying Corrective Torques in the Engine Loop

Hook into the physics engine update cycle using Matter.Events.on(engine, 'beforeUpdate', callback). During each tick, sample the chassis angle, calculate the required torque, and apply equal and opposite rotational effects to the wheel and chassis to adhere to Newton's third law.

Matter.Events.on(engine, 'beforeUpdate', (event) => {
    const deltaTime = engine.timing.lastDelta || 16.67;
    const currentAngle = chassis.angle;

    // Calculate corrective torque
    const controlSignal = computePID(currentAngle, deltaTime);

    // Clamp maximum torque to prevent simulation instability
    const maxTorque = 0.15;
    const torque = Math.max(-maxTorque, Math.min(maxTorque, controlSignal));

    // Apply torque: accelerating the wheel forward pushes the chassis backward
    wheel.torque = torque;
    chassis.torque = -torque;
});

Tuning and Stability Considerations