Lab 7: Model-based Control and Trotting Gait Implementation

Goal

In the previous labs we learned how to compute the position of the foot from the motor angles (forward kinematics) and how to go the other way around, from a desired foot position to the motor angles (inverse kinematics), and we made a single leg follow a triangular trajectory. In this lab we bring everything together: we will make the whole Pupper walk forward by implementing a trotting gait.

A gait is nothing more than a coordinated, periodic trajectory played on every leg with the right timing between them. We will build the reference trajectories for all four legs, interpolate between them in time, solve the inverse kinematics for each leg, and command the motors so that the robot trots in place and then forward.

Stanford Pupper trotting - demo video

This lab is the first one where the whole robot moves under your code. Keep a hand near the power switch, give Pupper some clear space on the floor, and always start with small stride lengths and low speeds before pushing performance.

Part 0: Setup

There is nothing new to clone for this lab. We build directly on top of the code you already wrote in the previous lab, so keep working in your existing lab 6 project:

cd ~/lab_3_fall_2025
code .

You already have a working single-leg pipeline: forward kinematics, the gradient-descent inverse kinematics, and the triangular trajectory for one leg. In this lab you will extend that same code to drive all four legs and to produce a walking trot.

Docker users can keep using the same simulation image as before (no need to redownload or rebuild it) and run:

./run.sh 3

Part 1: Review what you already have

Open your solution and re-read the InverseKinematics class. Make sure the following, written in the previous labs, still work:

  • the rotation_x, rotation_y, rotation_z and translation helpers (lab 5 forward kinematics);
  • the single-leg forward kinematics — the front-right leg fr_leg_fk(theta) is your reference implementation;
  • the per-leg inverse-kinematics solver (cost function, numerical gradient, hyperparameters and gradient-descent loop) from lab 6;
  • the single-leg triangular trajectory.

Everything below extends this existing code — you are not starting from a blank file.

Part 2: Forward Kinematics for All Four Legs

So far your code only worked with one leg at a time. To walk, we need to extend it to all four legs, each attached to the body at a different position and orientation.

TODO 1: Starting from your working front-right implementation fr_leg_fk, add forward kinematics for the three remaining legs, using fr_leg_fk and your lab 5 diagrams as a reference:

  • fl_leg_fk(theta) — front-left
  • br_leg_fk(theta) — back-right
  • bl_leg_fk(theta) — back-left

Each leg is mounted at a different body offset, so pay attention to the sign of the coordinates and to the orientation of each hip relative to the body frame:

  • RF (right-front): offset (0.06, -0.09, 0)
  • LF (left-front): offset (0.06, 0.09, 0)
  • RB (right-back): offset (-0.11, -0.09, 0)
  • LB (left-back): offset (-0.11, 0.09, 0)

Answer the following in your report:

  1. Compare the provided fr_leg_fk with your single-leg forward kinematics from lab 5. What is different, and why were those modifications necessary once we care about the leg's position on the body?
  2. What are Pupper's degrees of freedom? Is the robot underactuated? Justify your answer.
  3. Why do underactuated systems make control harder?

Part 3: Understanding the Trotting Gait

A trot is a symmetric gait in which the two diagonal leg pairs move together: the front-right and back-left legs swing forward while the front-left and back-right legs stay on the ground (stance), then the roles swap. This keeps the robot's support polygon balanced around the center of mass and is one of the most stable and energy-efficient gaits for a quadruped.

Each leg's foot (end-effector) follows a closed loop in space made of two phases:

  • Stance phase — the foot is on the ground and moves backward relative to the body, pushing the body forward.
  • Swing phase — the foot lifts off, moves forward through the air, and touches down again to start a new stance.

We describe this loop with a small set of reference end-effector positions per leg. Instead of the three vertices you used in lab 6, we now use six reference positions per leg so we can shape both the stance (feet on the ground) and the swing (lifting and reaching forward):

  • touch_down (0.05, 0.0, -0.14) — foot lands in front
  • three stand positions on the ground, moving backward
  • liftoff (-0.05, 0.0, -0.14) — foot leaves the ground at the back
  • mid_swing (0.0, 0.0, -0.05) — foot at the top of the swing arc

Reference swing/stance states for one leg

The trot is obtained by playing this same per-leg loop with a phase offset of half a cycle between the two diagonal pairs: when FR+BL are at the start of their swing, FL+BR are halfway through, i.e. in the middle of their stance.

Part 4: Define the Gait Trajectories

TODO 2: Fill in the reference end-effector positions for each leg. These arrays define the triangle/loop that each foot will track:

  • rf_ee_triangle_positions
  • lf_ee_triangle_positions
  • rb_ee_triangle_positions
  • lb_ee_triangle_positions

Use the six reference positions described in Part 3. Remember that the diagonal pairs (RF/LB and LF/RB) must be offset by half a period so the robot trots instead of hopping.

Answer the following in your report:

  1. List three other gaits Pupper could perform (e.g. walk, pace, bound, gallop) and briefly describe the leg coordination of each.
  2. What obstacles (hardware, stability, actuator limits, control frequency) might prevent you from implementing those gaits on Pupper?

Part 5: Write a New Function for the Alternative Triangle Pattern

In lab 6 you interpolated a simple 3-vertex triangle for a single leg. That is not enough for a trot: we now need a richer loop with distinct stance and swing phases, played on all four legs with the correct diagonal timing.

TODO 3: Write a new function — do not overwrite your lab 6 triangle — that produces this alternative triangle/loop pattern. Give it a signature such as interpolate_triangle(t, leg_index): given a normalized time t and a leg index, it returns the interpolated end-effector target for that leg.

  • Use the reference positions you defined in Part 4 for all four legs.
  • Implement linear interpolation between consecutive reference positions based on t.
  • Do not use np.interp — write your own weighted-sum interpolation so you understand exactly what happens between the reference points.
  • Apply the half-cycle phase offset between the diagonal pairs (RF/LB vs. LF/RB) so the legs alternate instead of moving all together.
  • Make sure the trajectory loops smoothly: after the last reference position it must return to the first.

The main loop then calls, at each control step: your new interpolation function → inverse_kinematics_single_leg (per leg) → send the resulting joint angles to the PD controller.

The cache_target_joint_positions method precomputes the joint angles for a full gait cycle (the interpolation runs over t from 0 to 1 in steps of 0.02, i.e. 50 samples). Solving the IK for every leg at every control tick in real time would be too expensive on the Raspberry Pi 5, so we solve it once per cycle and replay the cached angles. Keep this in mind when you reason about performance later.

Part 6: Run and Test your implementation

  • From ~/lab_3_fall_2025, launch the system:
ros2 launch lab_3.launch.py
  • Observe the robot: it should trot in place and then move forward. The IK timer runs at ~100 Hz and the PD control timer at ~200 Hz.

Answer the following in your report:

  1. Record a video of Pupper performing the trotting gait.
  2. This is a purely heuristic, feed-forward controller: the trajectory is pre-programmed and there is no real-time sensor feedback. What are the pitfalls of this approach? What do you expect to happen if you push the robot sideways while it is trotting?
  3. What challenges would you face deploying a full model-based controller or Model Predictive Control (MPC) on a Raspberry Pi 5, given its limited compute?

Part 7: Analyze and Improve Performance

  • Experiment with different trajectory shapes by editing the reference positions.
  • Adjust the ik_timer_period and pd_timer_period and observe the trade-off between smoothness and CPU load.
  • Modify the per-leg ee_positions offsets to shift the center of mass and see how it affects stability.

Answer the following in your report:

  1. Implement a fast and a slow walking gait and record a video of each.
  2. Report on: how the trajectory shape affects gait quality, how the timer periods affect performance, and how the center of mass influences walking efficiency and stability.

Part 8: Make Pupper Faster and Race

Now optimize for speed. You can tune the timer frequencies, the stride length, and the end-effector positions defined in the InverseKinematics class after initialization.

  1. Document your optimization attempts — what worked, what did not, and why.
  2. Time Pupper completing a 10-foot (≈3 m) course and compete against the other groups.
  3. Submit a video of your timing run and report your fastest time.

Resources

rasb/lab/07.txt · Last modified: 2026/07/10 20:18 by atoader
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0