Trajectory Generation, step 4
Moving all joints together
Scale one shared profile so every joint starts and stops together, as fast as the slowest joint allows.
Builds on Position, velocity & acceleration, from the free Foundations.
Write and run this step in the simulator with ProFour joints, four finish times
Suppose every joint runs its own time-optimal trapezoid. A joint with only a short way to go finishes early, then waits while the others are still moving. The arm arrives in stages, and the gripper follows a path that bends as the mix of moving joints changes. Controllers avoid this by synchronising the joints. They all start together, stop together, and stay on the straight line from to in joint space.
One profile for every joint
Drive every joint with the same progress variable , which runs from 0 to 1:
Joint then moves at and accelerates at . To keep every joint within its limits, has to obey
So is just your Step 3 trapezoid from 0 to 1, run under these two scaled limits. The slowest joint sets the pace, and the velocity limit and the acceleration limit can come from different joints.
Streaming made easy
From this step on, arm.stream(fn, duration) runs the setpoint loop for you:
arm.stream(lambda t: sync_profile(q0, qf, t), T)
It sends fn(0), fn(0.01), … every 10 ms, up to the first tick at or after duration. Because your profile clamps , the last setpoint lands exactly on .
Your task
Implement sync_duration(q0, qf) and sync_profile(q0, qf, t) using arm.vmax and arm.amax. The starter provides trapezoid and trapezoid_duration from Step 3. For now it gives each joint its own trapezoid: in the Plots tab, each joint's progress curve reaches 1 at a different time.
For each move, the graders check that:
- every joint stops within 30 ms of the others;
- the commands stay within 0.01 rad of the straight line;
- the limits hold;
- the move takes at most 3% longer than the optimum.