gwordal

Lesson 4 of 5 · 24 min

Gaits and timing

With inverse kinematics you can place one foot anywhere. Walking is the art of choosing where each of the four feet is at every instant, so that the body keeps moving forward without falling. That choice is called a gait: a repeating pattern in which each foot alternates between stance (on the ground, pushing the body) and swing (in the air, moving forward to the next landing spot).

Drag to orbit
Move the gait speed and stride sliders. Watch which legs are in the air together.

Describing a gait with two numbers

Any periodic gait can be described by a few quantities:

  • Period T: the time of one full cycle for one foot.
  • Duty factor beta: the fraction of the cycle a foot spends on the ground, beta = t_stance / T.
  • Phase offset of each leg: how far into the cycle that leg is compared to a reference leg, as a fraction from 0 to 1.

The duty factor tells you how many feet are down on average: 4 x beta. At beta = 0.75 it is 3 feet, at beta = 0.5 it is 2 feet.

The three gaits to build

GaitDuty factorFront leftFront rightRear leftRear rightFeet down
Walk (crawl)0.750.000.500.750.253 always
Trot0.550.000.500.500.002 (sometimes 4)
Bound0.400.000.000.500.502 front or 2 rear, with a flight phase

Read the table row by row. In the walk, the feet land one after another around the body: front left, rear right, front right, rear left, each a quarter of a cycle apart. In the trot, diagonal pairs move together. In the bound, the two front legs act as one, then the two rear legs.

The duty factor and speed are linked. The foot must travel one stride length S backwards relative to the body during stance, so the body speed is:

v = S / t_stance = S / (beta x T)

Worked example with S = 40 mm:

  • Walk, T = 1.2 s, beta = 0.75: t_stance = 0.9 s, so v = 40 / 0.9 = 44 mm/s.
  • Trot, T = 0.6 s, beta = 0.5: t_stance = 0.3 s, so v = 40 / 0.3 = 133 mm/s.

The trot is three times faster for the same stride. The swing foot in the trot has 0.5 x 0.6 = 0.3 s to lift and land. At a 50 Hz control tick, that is 15 updates for the swing, enough for a smooth arc.

Static stability and the support polygon

Place the feet at the corners of a rectangle 200 mm long and 120 mm wide, with the centre of mass above the middle. Use coordinates (x forward, y left) in millimetres:

  • Front left (100, 60), front right (100, -60)
  • Rear left (-100, 60), rear right (-100, -60)

In a walk, lift the front left foot. The remaining three feet form a triangle: front right, rear left, rear right. One side of that triangle is the line from rear left to front right, and it runs exactly through the origin. The centre of mass projected on the floor sits right on the edge, with zero stability margin.

The cure is to shift the body into the triangle before the lift. The stability margin is the perpendicular distance from the centre of mass to the nearest edge. Moving the body 25 mm toward the rear right foot, away from the lifted leg, gives a margin of about 25 mm, a comfortable amount on a robot this size.

The trot has no support polygon at its core: two diagonal feet make only a line, and the robot relies on forward momentum and a short stance time to stay up. That is why a duty factor slightly above 0.5 helps. A value of 0.55 means all four feet are briefly down together, 0.05 x 0.6 s = 30 ms per transition, giving a safety moment at each swap.

Foot trajectory

A foot path needs a stance part (straight back along the ground) and a swing part (an arc forward). Use a half sine for the lift so the foot leaves and lands with zero vertical speed:

const float STRIDE = 40.0;    // stride length, mm
const float LIFT   = 25.0;    // swing height, mm
const float HEIGHT = 120.0;   // standing height, mm

// phase is 0..1, duty is the stance fraction
void footTarget(float phase, float duty, float &x, float &z) {
  if (phase < duty) {                 // stance: foot slides backward
    float u = phase / duty;
    x = STRIDE / 2 - STRIDE * u;
    z = HEIGHT;
  } else {                            // swing: foot arcs forward
    float u = (phase - duty) / (1.0 - duty);
    x = -STRIDE / 2 + STRIDE * u;
    z = HEIGHT - LIFT * sin(PI * u);
  }
}

Non-blocking timing with millis()

You could write the gait with delay(), but then nothing else can run during a step: no IMU reads, no serial commands, no sensors. The fix is the pattern from the Arduino course: compare millis() with a stored time, and only act when a tick has passed.

const unsigned long TICK_MS = 20;       // 50 Hz control loop
unsigned long nextTick = 0;
unsigned long gaitStart = 0;

float periodMs = 600.0;                 // one gait cycle, trot
float duty = 0.55;
//                     FL   FR   RL   RR
float offset[4] = {0.0, 0.5, 0.5, 0.0};

void setup() {
  // ... pwm.begin() etc. from lesson 2
  gaitStart = millis();
  nextTick  = gaitStart;
}

void loop() {
  unsigned long now = millis();
  if ((long)(now - nextTick) >= 0) {    // safe across millis() rollover
    nextTick += TICK_MS;                // no drift: add, do not reset
    float t = fmod((now - gaitStart) / periodMs, 1.0);

    for (int leg = 0; leg < 4; leg++) {
      float phase = fmod(t + offset[leg], 1.0);
      float x, z;
      footTarget(phase, duty, x, z);
      LegAngles a = legIK(x, 0.0, z);
      writeLeg(leg, a);                 // applies sign, offset, PCA9685
    }
  }
  // Other tasks run here every pass: read IMU, parse serial, etc.
}

Two details matter. The nextTick += TICK_MS line adds a fixed step instead of using now, so timing errors do not accumulate. And the subtraction cast to long stays correct when millis() wraps around after about 49.7 days.

Check yourself

A trot has stride length 50 mm, period 0.8 s and duty factor 0.5. What is the body speed?

Check yourself

Why does a crawl walk with the body centred over a rectangle of feet still tip when a leg lifts?