Homing OSL Joints¶
This tutorial demonstrates how to home the knee and ankle joints on the Open Source Leg (OSL) using the opensourceleg
Python library. You’ll learn how to initialize actuators and encoders, configure the robot platform, and use the built-in home()
method to calibrate the joints by driving them into physical stops and applying offsets.
Overview¶
This example shows how to:
- Initialize
DephyActuator
instances for the knee and ankle joints - Configure
AS5048B
magnetic absolute encoders via I2C - Instantiate the
OpenSourceLeg
platform with the defined components - Use the
home()
method to detect joint limits and assign calibrated offsets - Use callback functions to execute code at the homed positions
- Log joint and encoder positions to the terminal in real-time
Hardware Setup¶
- Actuators: Two Dephy Actuators are connected to
/dev/ttyACM0
and/dev/ttyACM1
. These control the knee and ankle respectively. - Encoders: Two AS5048B encoders are connected to I2C bus on the host controller.
For detailed setup instructions, refer to:
Software Setup¶
Ensure the opensourceleg
Python package is installed. Before running the full integration script, verify that each actuator and encoder is working individually. This helps isolate configuration issues.
Code Structure¶
The tutorial script is structured into seven key sections:
1. Configuration¶
import numpy as np
from opensourceleg.actuators.base import CONTROL_MODES
from opensourceleg.actuators.dephy import DephyActuator
from opensourceleg.logging.logger import Logger
from opensourceleg.robots.osl import OpenSourceLeg
from opensourceleg.sensors.encoder import AS5048B
from opensourceleg.utilities import SoftRealtimeLoop
TIME_TO_STEP = 1.0
FREQUENCY = 200
DT = 1 / FREQUENCY
GEAR_RATIO = 9 * (83 / 18)
Key parameters:
FREQUENCY
: Control loop rate (200 Hz)DT
: Timestep per loop cycle, defined as1 / FREQUENCY
GEAR_RATIO
: Compound gear ratio applied to each joint (≈41.5:1)
2. Real-time loop and Logger¶
def home_joints():
homing_logger = Logger(
log_path="./logs",
file_name="homing_joint",
)
clock = SoftRealtimeLoop(dt=DT)
This section:
- Initializes a
Logger
instance to write homing data to the./logs
directory - Starts a
SoftRealtimeLoop
for accurate timing
3. Actuator Initialization¶
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,
),
}
This section:
- Instantiates two
DephyActuator
objects for the knee and ankle - Assigns ports, frequency, gear ratio, and disables internal logging
4. Sensor Initialization¶
sensors = {
"joint_encoder_knee": AS5048B(
tag="joint_encoder_knee",
bus="/dev/i2c-2",
A1_adr_pin=True,
A2_adr_pin=False,
zero_position=0,
enable_diagnostics=False,
),
"joint_encoder_ankle": AS5048B(
tag="joint_encoder_ankle",
bus="/dev/i2c-3",
A1_adr_pin=False,
A2_adr_pin=True,
zero_position=0,
enable_diagnostics=False,
),
}
This section:
- Instantiates two
AS5048B
encoders using the I2C interface - Each encoder is tagged and assigned its A1/A2 pin configuration
- Zero position is temporarily set to 0
5. Using Callbacks for Homing Completion¶
The home()
method also supports an optional callback_functions
argument. This allows you to specify a list of functions (one per actuator) that will be called when each actuator finishes its homing routine. This is useful for custom notifications, logging, or triggering additional actions like zeroing joint encoders.
For example, to print a message when each joint completes homing:
# Define callback functions for homing completion
def knee_homing_complete():
osl.joint_encoder_knee.update()
osl.joint_encoder_knee.zero_position = osl.joint_encoder_knee.counts
print("Knee homing complete!")
def ankle_homing_complete():
osl.joint_encoder_ankle.update()
osl.joint_encoder_ankle.zero_position = osl.joint_encoder_ankle.counts + osl.joint_encoder_ankle.deg_to_counts(
30
)
print("Ankle homing complete!")
callbacks = {"knee": knee_homing_complete, "ankle": ankle_homing_complete}
If you do not wish to use callbacks, you can omit the callback_functions
argument or pass a list of None
values.
6. Initialize OSL Platform¶
This section:
- Creates an instance of
OpenSourceLeg
and passes in the actuator and sensor dictionaries
7. Run Homing Routine¶
with osl:
osl.home(
homing_voltage=2000,
homing_frequency=FREQUENCY,
homing_direction={"knee": -1, "ankle": -1},
output_position_offset={"knee": 0.0, "ankle": np.deg2rad(30.0)},
current_threshold=5000,
velocity_threshold=0.001,
callbacks=callbacks,
)
This section:
- Calls
osl.home()
with user-defined voltages, direction, and thresholds - Each actuator rotates in a specified direction until one of the following is true:
- Velocity falls below
0.001 rad/s
- Current exceeds
5000 mA
- Once stopped, the motor is zeroed based on its position, and a calibrated offset is added (30° for ankle, 0° for knee)
8. Reset Torque and Start Logging¶
osl.knee.set_control_mode(CONTROL_MODES.CURRENT)
osl.knee.set_current_gains()
osl.ankle.set_control_mode(CONTROL_MODES.CURRENT)
osl.ankle.set_current_gains()
osl.knee.set_output_torque(0)
osl.ankle.set_output_torque(0)
# homing_logger.set_stream_terminator("\r")
for t in clock:
osl.update()
homing_logger.info(
f"Time: {t:.2f}; Knee Output Position: {np.rad2deg(osl.knee.output_position):.2f}; "
f"Ankle Output Position: {np.rad2deg(osl.ankle.output_position):.2f}; "
f"Knee Encoder Position: {np.rad2deg(osl.joint_encoder_knee.position):.2f}; "
f"Ankle Encoder Position: {np.rad2deg(osl.joint_encoder_ankle.position):.2f}; "
)
This section:
- Switches both actuators to
CURRENT
control mode - Sets joint torques to 0 so they can be moved freely
- Logs the following information continuously:
- Elapsed time
- Output positions of the knee and ankle (in degrees)
- Raw encoder readings of the knee and ankle (in degrees)
Running the Script¶
-
Navigate to the tutorial directory:
-
Run the script:
-
Expected behavior:
- Both motors rotate slowly in a negative direction
- Movement stops once physical limits are detected (via current or velocity thresholds)
- Homing offsets are applied to calibrate the zero position
- Control mode switches to current control, torque is zeroed
- Joint and encoder positions print continuously in the terminal
Note: You can use Ctrl+C to terminate the loop at any time.