Torque Trajectory Control Tutorial¶
This opensourceleg.control
module provides functiionality for doing torque control. This tutorial demonstrates how to control the Open Source Leg (OSL) using torque trajectories for both the knee and ankle joints.
Warnings:¶
- This example is not meant to be used as a walking controller. The goal of this example is to provide a reference for how a torque trajectory can be loaded and commanded.
- While runnig this script make sure to have load on the actuators.
- Please be cautious while changing mass parameters.
Overview¶
The script implements a torque control system that:
- Loads pre-defined torque trajectories for knee and ankle joints
- Applies these trajectories in a cyclic manner
- Logs the performance data
- Generates visualization plots
Prerequisites¶
- OpenSourceLeg hardware setup
-
Python environment with required dependencies:
- numpy
- matplotlib
-
Access to torque trajectory files:
ankle.pkl
knee.pkl
Command Line Arguments¶
The script accepts the following command line arguments:
--mass
: User mass in kg (default: 1.0)--stride-time
: Stride time in seconds (default: 1.0)--frequency
: Control loop frequency in Hz (default: 200.0)
Key Parameters¶
USER_MASS
: The mass of the user in kg (configurable via command line)STRIDE_TIME
: The stride time in seconds (configurable via command line)FREQUENCY
: The control loop frequency in Hz (configurable via command line)TRAJECTORY_LEN
: Fixed at 150 points. This is the length of the torque trajectories in the ankle.pkl and knee.pkl files.GEAR_RATIO
: Set to 9 * (83/18)
Hardware Configuration¶
The script configures two DephyActuators and two AS5048B encoders:
-
Actuators:
- Knee actuator (port=
/dev/ttyACM0
) - Ankle actuator (port=
/dev/ttyACM1
)
- Knee actuator (port=
Both configured with:
- Specified gear ratio
- User-defined frequency
- Dephy logging disabled
-
Encoders:
- Knee joint encoder (bus=1, A1=True, A2=False)
- Ankle joint encoder (bus=1, A1=False, A2=True)
Functions¶
Get Torque¶
from opensourceleg.utilities import SoftRealtimeLoop
ANKLE_TRAJECTORY_PATH = "./ankle.pkl"
KNEE_TRAJECTORY_PATH = "./knee.pkl"
def get_torque(t: float, data: list, user_mass: float, stride_time: float, trajectory_len: int) -> int:
"""Calculate the torque setpoint for a given time point in the trajectory.
Args:
t (float): Current time in seconds
data (list): List containing the torque trajectory data points
user_mass (float): The mass of the user in kg
stride_time (float): The stride time in seconds
trajectory_len (int): The length of the trajectory
Returns:
int: Torque setpoint scaled by user mass
The function:
1. Calculates walking_time by taking modulo of current time with stride_time
Calculates the torque setpoint for a given time point:
t
: Current time in secondsdata
: List containing torque trajectory data pointsuser_mass
: Mass of the user in kgstride_time
: Stride time in secondstrajectory_len
: Length of the trajectory
Returns torque setpoint scaled by user mass
Plot Data¶
"""
walking_time = t % stride_time
index = int(walking_time * trajectory_len)
return user_mass * data[index]
def plot_data(plotting_data: list) -> None:
try:
import matplotlib.pyplot as plt
except ImportError:
print("matplotlib is not installed, skipping plot")
return
# plot the data using matplotlib
plt.figure(figsize=(12, 10))
# Create three subplots for better visualization
# Ankle Torque
plt.subplot(3, 1, 1)
plt.plot(plotting_data[0], plotting_data[1], label="Ankle Torque Set Point")
plt.plot(plotting_data[0], plotting_data[3], label="Ankle Joint Torque")
plt.xlabel("Time (s)")
plt.ylabel("Torque (Nm)")
plt.title("Ankle Torque Trajectory")
plt.legend()
plt.grid(True)
# Knee Torque
plt.subplot(3, 1, 2)
plt.plot(plotting_data[0], plotting_data[2], label="Knee Torque Set Point")
plt.plot(plotting_data[0], plotting_data[4], label="Knee Joint Torque")
plt.xlabel("Time (s)")
plt.ylabel("Torque (Nm)")
plt.title("Knee Torque Trajectory")
plt.legend()
plt.grid(True)
# Joint Positions
plt.subplot(3, 1, 3)
plt.plot(plotting_data[0], np.rad2deg(plotting_data[5]), label="Knee Position")
plt.plot(plotting_data[0], np.rad2deg(plotting_data[6]), label="Ankle Position")
plt.xlabel("Time (s)")
plt.ylabel("Joint Position (deg)")
plt.title("Joint Positions")
Generates three plots:
- Ankle torque (setpoint vs. actual)
- Knee torque (setpoint vs. actual)
- Joint positions (knee and ankle in degrees)
Saves the plot as plot.png
Operation Flow¶
-
Initialization:
- Parses command line arguments
- Sets up logging with specified frequency
- Configures actuators and sensors
- Loads trajectory data from pickle files
-
Control Sequence:
- Homes the OSL
- Sets control mode to CURRENT for both actuators
- Sets current gains
- Waits for user input
- Executes torque trajectory
- Logs performance data
-
Visualization:
- Generates plots after completion
- Saves plots to "plot.png"
Usage¶
-
Ensure trajectory files (
ankle.pkl
andknee.pkl
) are in the working directory. We have provided a sample trajectory file in thetutorials/control/torque_trajectory/
directory for both the ankle and knee joints. These trajectories are output of an high-level controller (not provided here) for level walking. The torque trajectories present the joint torque setpoints for one gait cycle, and the units are in Nm/kg. -
Run the script with optional arguments:
- Press Enter when prompted to start the walking trajectory
- The system will execute the trajectory and generate plots upon completion
Safety Notes¶
- This example is not meant to be used as a walking controller
- Please be cautious while changing mass parameters
- Ensure all connections are secure before operation
- Keep emergency stop accessible during operation
Output¶
The script generates:
- Real-time logs in the
./logs
directory with filename "torque_trajectory" -
A plot file (
plot.png
) showing:- Torque trajectories for both joints
- Actual vs. commanded torques
- Joint position data in degrees
Full Script for this tutorial¶
"""
This example demonstrates how torque trajectories can be loaded and commanded on the Open Source Leg (OSL).
This is not a walking controller.
Authors: Senthur Raj Ayyappan <senthura@umich.edu>, Varun Satyadev Shetty <vashetty@umich.edu>
Neurobionics Lab
Robotics Department
University of Michigan
March 25th, 2025
"""
import argparse
import pickle
import numpy as np
from opensourceleg.actuators import CONTROL_MODES
from opensourceleg.actuators.dephy import DephyActuator
from opensourceleg.logging import Logger
from opensourceleg.robots.osl import OpenSourceLeg
from opensourceleg.sensors.encoder import AS5048B
from opensourceleg.utilities import SoftRealtimeLoop
ANKLE_TRAJECTORY_PATH = "./ankle.pkl"
KNEE_TRAJECTORY_PATH = "./knee.pkl"
def get_torque(t: float, data: list, user_mass: float, stride_time: float, trajectory_len: int) -> int:
"""Calculate the torque setpoint for a given time point in the trajectory.
Args:
t (float): Current time in seconds
data (list): List containing the torque trajectory data points
user_mass (float): The mass of the user in kg
stride_time (float): The stride time in seconds
trajectory_len (int): The length of the trajectory
Returns:
int: Torque setpoint scaled by user mass
The function:
1. Calculates walking_time by taking modulo of current time with stride_time
2. Converts walking_time to trajectory index based on trajectory_len
3. Returns torque value at that index scaled by user_mass
"""
walking_time = t % stride_time
index = int(walking_time * trajectory_len)
return user_mass * data[index]
def plot_data(plotting_data: list) -> None:
try:
import matplotlib.pyplot as plt
except ImportError:
print("matplotlib is not installed, skipping plot")
return
# plot the data using matplotlib
plt.figure(figsize=(12, 10))
# Create three subplots for better visualization
# Ankle Torque
plt.subplot(3, 1, 1)
plt.plot(plotting_data[0], plotting_data[1], label="Ankle Torque Set Point")
plt.plot(plotting_data[0], plotting_data[3], label="Ankle Joint Torque")
plt.xlabel("Time (s)")
plt.ylabel("Torque (Nm)")
plt.title("Ankle Torque Trajectory")
plt.legend()
plt.grid(True)
# Knee Torque
plt.subplot(3, 1, 2)
plt.plot(plotting_data[0], plotting_data[2], label="Knee Torque Set Point")
plt.plot(plotting_data[0], plotting_data[4], label="Knee Joint Torque")
plt.xlabel("Time (s)")
plt.ylabel("Torque (Nm)")
plt.title("Knee Torque Trajectory")
plt.legend()
plt.grid(True)
# Joint Positions
plt.subplot(3, 1, 3)
plt.plot(plotting_data[0], np.rad2deg(plotting_data[5]), label="Knee Position")
plt.plot(plotting_data[0], np.rad2deg(plotting_data[6]), label="Ankle Position")
plt.xlabel("Time (s)")
plt.ylabel("Joint Position (deg)")
plt.title("Joint Positions")
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.savefig("plot.png")
plt.show()
if __name__ == "__main__":
# Set up argument parser
parser = argparse.ArgumentParser(description="Torque trajectory control for Open Source Leg")
parser.add_argument("--mass", type=float, default=1.0, help="User mass in kg (default: 1.0)", required=False)
parser.add_argument(
"--stride-time", type=float, default=1, help="Stride time in seconds (default: 1)", required=False
)
parser.add_argument(
"--frequency", type=float, default=200.0, help="Control loop frequency in Hz (default: 200.0)", required=False
)
# Parse arguments
args = parser.parse_args()
# Set global parameters from arguments
USER_MASS = args.mass
STRIDE_TIME = args.stride_time
FREQUENCY = args.frequency
# Fixed parameters
TRAJECTORY_LEN = 150
GEAR_RATIO = 9 * (83 / 18)
torque_logger = Logger(log_path="./logs", file_name="torque_trajectory")
clock = SoftRealtimeLoop(dt=1 / FREQUENCY)
actuators = {
"knee": DephyActuator(
tag="knee",
port="/dev/ttyACM0",
gear_ratio=GEAR_RATIO,
frequency=FREQUENCY,
dephy_log=False,
),
"ankle": DephyActuator(
tag="ankle",
port="/dev/ttyACM1",
gear_ratio=GEAR_RATIO,
frequency=FREQUENCY,
dephy_log=False,
),
}
sensors = {
"joint_encoder_knee": AS5048B(
tag="joint_encoder_knee",
bus=1,
A1_adr_pin=True,
A2_adr_pin=False,
zero_position=0,
enable_diagnostics=False,
),
"joint_encoder_ankle": AS5048B(
tag="joint_encoder_ankle",
bus=1,
A1_adr_pin=False,
A2_adr_pin=True,
zero_position=0,
enable_diagnostics=False,
),
}
osl = OpenSourceLeg(
tag="osl",
actuators=actuators,
sensors=sensors,
)
plotting_data = [
[],
[],
[],
[],
[],
[],
[],
] # Time, ankle_sp, knee_sp, ankle_torque, knee_torque, knee_pos, ankle_pos
with open(ANKLE_TRAJECTORY_PATH, "rb") as f:
ankle_data = pickle.load(f) # noqa: S301
with open(KNEE_TRAJECTORY_PATH, "rb") as f:
knee_data = pickle.load(f) # noqa: S301
with osl:
osl.home()
osl.knee.set_control_mode(CONTROL_MODES.CURRENT)
osl.knee.set_current_gains()
osl.knee.set_output_torque(0)
osl.ankle.set_control_mode(CONTROL_MODES.CURRENT)
osl.ankle.set_current_gains()
osl.ankle.set_output_torque(0)
input("Press Enter to start walking")
for t in clock:
osl.update()
ankle_torque_sp = get_torque(t, ankle_data, USER_MASS, STRIDE_TIME, TRAJECTORY_LEN)
knee_torque_sp = get_torque(t, knee_data, USER_MASS, STRIDE_TIME, TRAJECTORY_LEN)
osl.ankle.set_output_torque(ankle_torque_sp)
osl.knee.set_output_torque(knee_torque_sp)
torque_logger.info(
f"t: {t}, ankle_torque_sp: {ankle_torque_sp}, knee_torque_sp: {knee_torque_sp}, "
f"ankle_output_torque: {osl.ankle.output_torque}, "
f"knee_output_torque: {osl.knee.output_torque} "
f"knee_position: {osl.knee.output_position}, ankle_position: {osl.ankle.output_position}"
)
plotting_data[0].append(t)
plotting_data[1].append(ankle_torque_sp)
plotting_data[2].append(knee_torque_sp)
plotting_data[3].append(osl.ankle.output_torque)