Skip to content

Frames and rotations ​

Every number a robot uses for position or orientation is relative to some coordinate frame. This page covers frames, rotation matrices, roll–pitch–yaw angles and gimbal lock, quaternions, and homogeneous transforms, then shows how they fit together in the Strido transform (tf) tree.

In Strido

  • Base frame: REP-103 (x forward, y left, z up), with the origin at the centre of the four hip-axis intersections. It is defined in hardware/kinematics.yaml (base_frame), together with the rotation from the CAD (STEP) frame.
  • Frame tree: ros2/strido_description/urdf/strido.urdf.xacro defines base_link, imu_link, lidar_link, camera_link, camera_optical_frame and, for each leg, *_hip, *_thigh, *_calf and *_foot.
  • Orientation: the firmware runs a Mahony filter and outputs a quaternion (firmware/strido_esp32s3/src/main.cpp). mcu_bridge publishes it as /imu/data in imu_link.
  • Leg transforms in code: foot_position in packages/strido-core/src/strido/leg.py.

Frames ​

A frame is an origin plus three orthogonal unit axes. A point p has no coordinates on its own; it only has coordinates in a frame. We write Ap for "the coordinates of p in frame A". All frames here are right-handed: x^×y^=z^.

ROS fixes the conventions in REP-103. Body frames use x forward, y left and z up. Units are metres and radians. Strido follows this. Its base_link origin is the centre of the four hip-axis intersections, so the hips sit at (±0.120,±0.039,0)m (legs_base in hardware/kinematics.yaml).

The CAD model uses a different frame. hardware/kinematics.yaml stores the rotation that maps STEP coordinates into base coordinates:

Rbase←STEP=[0−10100001]

This is a +90∘ turn about z: the STEP y axis points backwards, so STEP −y becomes base +x.

Rotation matrices ​

The orientation of frame B relative to frame A is a 3×3 matrix ARB. Its columns are B's unit axes written in A coordinates. A vector converts as Av=ARBBv. Because the columns are orthonormal:

R⊤R=I,detR=+1,R−1=R⊤

Rotation matrices with these properties form the group SO(3). The three elementary rotations are:

Rx(θ)=[1000cos⁡θ−sin⁡θ0sin⁡θcos⁡θ],Ry(θ)=[cos⁡θ0sin⁡θ010−sin⁡θ0cos⁡θ],Rz(θ)=[cos⁡θ−sin⁡θ0sin⁡θcos⁡θ0001]

Rotations combine by multiplication, ARC=ARBBRC. The order matters, because rotations do not commute: RxRy≠RyRx in general.

Roll, pitch, yaw and gimbal lock ​

A rotation has three degrees of freedom, but its matrix has nine numbers. Euler angles describe it with three successive elementary rotations. ROS and URDF use roll–pitch–yaw (RPY): roll ϕ about x, pitch θ about y, yaw ψ about z, applied about the fixed axes in that order. That gives:

R=Rz(ψ)Ry(θ)Rx(ϕ)

RPY is easy to read. The Strido camera is mounted with rpy="0 0.261799 0", a 15∘ downward pitch. A body "rolled 5∘" is easy to picture.

RPY angles have a singularity. At θ=±90∘ the roll and yaw axes line up, and only ϕ±ψ is defined. One degree of freedom has gone. This is gimbal lock. Near it, a small change in orientation can cause a large jump in the angles, and the angle rates

ϕ˙, θ˙, ψ˙

blow up. That is because the matrix that maps body angular velocity to (ϕ˙,θ˙,ψ˙) contains 1/cos⁡θ. No set of three angles avoids this everywhere; it is a topological property of SO(3). A walking robot rarely pitches 90∘, but a fall can. This is one reason estimators store orientation as a quaternion.

Quaternions ​

A unit quaternion q=(w,x,y,z) with w2+x2+y2+z2=1 encodes a rotation by angle α about the unit axis n^:

q=(cos⁡α2, n^sin⁡α2)

Rotations compose by the quaternion (Hamilton) product. q and −q give the same rotation. The representation has no singularity, needs four numbers instead of nine, and can be renormalised cheaply after numerical drift. The equivalent rotation matrix is:

R(q)=[1−2(y2+z2)2(xy−wz)2(xz+wy)2(xy+wz)1−2(x2+z2)2(yz−wx)2(xz−wy)2(yz+wx)1−2(x2+y2)]

The Strido firmware gets q from orientation.getQuaternion and sends it to the Pi. The learned policy does not use the raw angles. It uses the direction of gravity seen from the body, Bg=R(q)⊤(0,0,−1)⊤ (see packages/strido-sim/src/strido_sim/rl/env.py). This vector is smooth for every orientation and has no gimbal lock. See state estimation for how the filter produces q.

Homogeneous transforms ​

A pose combines a rotation and a translation. A 4×4 homogeneous transform packs both together:

ATB=[ARBAtB0⊤1],[Ap1]=ATB[Bp1]

Here AtB is the origin of B expressed in A. Transforms chain by multiplication, ATC=ATBBTC. The inverse is:

BTA=(ATB)−1=[R⊤−R⊤t0⊤1]

Each URDF joint is exactly one such transform. The fixed <origin xyz rpy> comes first. For a revolute joint, it is followed by a rotation about <axis> by the joint angle.

The Strido tf tree ​

Rendering diagram…

robot_state_publisher computes the leg transforms from the joint angles. The sensor frames are fixed. For example, imu_link sits at (0.042,−0.004,−0.011)m with its axes aligned to base_link. camera_optical_frame uses rpy="-π/2 0 -π/2" so that its z axis points along the view direction, which is the optical convention in REP-103. odom → base_link comes from gait_node, which integrates the commanded body velocity. It is not measured odometry (see the docstring of ros2/strido_control/strido_control/gait_node.py).

For the front-left leg, the URDF chain is:

baseTfoot=Trans(0.1197,0.039,0)Rotx(q1)⏟FL_hip_jointTrans(0,0.0383,0)Roty(q2)⏟FL_thigh_jointTrans(0,0,−0.130)Roty(q3)⏟FL_calf_jointTrans(0,0,−0.124)⏟FL_foot_joint

Worked example: abducting the front-left leg ​

In the stand pose (stand_pose() in strido/gait.py, body height 0.17m), the front-left joint angles are q1=0, q2=46.5∘ and q3=−96.0∘. The thigh and calf rotations place the foot at phip=(0, 0.0383, −0.170)m relative to the hip-joint origin. The 0.0383m is the lateral offset to the leg plane, and the foot sits straight below the hip.

Now abduct by q1=10∘ (cos=0.9848, sin=0.1736):

Rx(10∘)phip=[00.9848⋅0.0383−0.1736⋅(−0.170)0.1736⋅0.0383+0.9848⋅(−0.170)]=[00.0672−0.1608]m

Adding the hip origin (0.1197,0.039,0) gives the foot at (0.1197, 0.1062, −0.1608)m in base_link. foot_position("FL", (0.1745, 0.8121, -1.6761)) returns the same point. The same rotation as a quaternion is (cos⁡5∘,sin⁡5∘,0,0)=(0.9962,0.0872,0,0).

Check yourself ​

  1. Why is ARB−1 just the transpose?
  2. The IMU frame is aligned with base_link but offset by a few centimetres. Does the offset change the measured angular velocity? Does it change the measured acceleration?
  3. Why does the policy observe the gravity vector rather than roll, pitch and yaw?
  4. Write camera_opticalTbase in terms of the URDF transforms.
Answers
  1. The columns are orthonormal, so R⊤R=I.
  2. Angular velocity is the same everywhere on a rigid body, so the offset does not change it. Linear acceleration does change: an offset point sees extra ω×(ω×r)+ω˙×r terms.
  3. It is continuous and has no singularity. Yaw is also unobservable from an IMU without a magnetometer, and walking does not need it.
  4. (baseTcameracameraToptical)−1, with each factor built from the joint's xyz and rpy.

Further reading ​

MIT licence. Mechanical design derived from Yertle by Jerome Graves. CAD, simulation and native tests are not hardware evidence — see Verification.