Skip to content

Lockstep

Multi-axis Zaber devices are capable of synchronizing the movement of parallel axes using a feature called lockstep. The library offers a way to control these lockstep groups.

Setup

Before using lockstep, check if the device is capable of using the feature by ensuring the lockstep.numgroups setting has a non-zero value (this setting indicates how many lockstep groups the device supports; a value of 0 or a failing command indicates it is not supported).

num_groups = device.settings.get('lockstep.numgroups')
print('Number of lockstep groups possible: ', num_groups)
const numGroups = await device.settings.get('lockstep.numgroups');
console.log('Number of lockstep groups possible:', numGroups);
var numGroups = device.Settings.Get("lockstep.numgroups");
Console.WriteLine("Number of lockstep groups possible: {0}", numGroups);
double numGroups = device.getSettings().get("lockstep.numgroups");
System.out.println("Number of lockstep groups possible: " + numGroups);
numGroups = device.getSettings().get('lockstep.numgroups');
fprintf('Number of lockstep groups possible: %d', numGroups);
double numGroups = device.getSettings().get("lockstep.numgroups");
std::cout << "Number of lockstep groups possible: " << numGroups << std::endl;

Enabling

Before enabling a lockstep group, home each axis individually and align the axes to your desired offset. The offset is the distance between the axes in the lockstep group. This offset saves upon enabling a lockstep group, and the device will maintain it during movement.

Once you have set up the axes, use the following code to enable the lockstep group for axis 1 and 2. Note that you can provide more than two axes.

# get lockstep group with ID 1 from device
lockstep = device.get_lockstep(1)

# enable it
if not lockstep.is_enabled():
    lockstep.enable(1, 2)
// get lockstep group with ID 1 from device
const lockstep = device.getLockstep(1);

// enable it
if (!(await lockstep.isEnabled())) {
    await lockstep.enable(1, 2);
}
// get lockstep group with ID 1 from device
var lockstep = device.GetLockstep(1);

// enable it
if (!lockstep.IsEnabled()) {
    lockstep.Enable(1, 2);
}
// import zaber.motion.ascii.Connection;
// import zaber.motion.ascii.Device;
// import zaber.motion.ascii.Lockstep;
// import zaber.motion.Units;

// get lockstep group with ID 1 from device
Lockstep lockstep = device.getLockstep(1);

// enable it
if (!lockstep.isEnabled()) {
    lockstep.enable(1, 2);
}
% get lockstep group with ID 1 from device
lockstep = device.getLockstep(1);

% enable it
if !lockstep.isEnabled()
    lockstep.enable([1, 2]);
end
// get lockstep group with ID 1 from device
Lockstep lockstep = device.getLockstep(1);

// enable it
if (!lockstep.isEnabled()) {
    lockstep.enable(1, 2);
}

Once you enable a lockstep group, you will not be able to send movement commands to the axes in it.

Movement

With an enabled lockstep group, you can use movement commands on the lockstep instance similarly to an individual axis, although there are some specific things to note. Unit conversion is based on the resolution of the first axis of the lockstep group. For absolute movement commands, the group moves the first axis to an absolute position while the second axis maintains its offset. When you send a home command to a lockstep group, it considers both axes; the group retracts until it detects the home sensor of any axis in the group.

The example below illustrates the use of some lockstep movement commands.

# move a lockstep group so the first axis is at position 1000
lockstep.move_absolute(1000)

# move the lockstep group additional 10mm
lockstep.move_relative(10, Units.LENGTH_MILLIMETRES)

# return to home
lockstep.home()
// move a lockstep group so the first axis is at position 1000
await lockstep.moveAbsolute(1000);

// move the lockstep group additional 10mm
await lockstep.moveRelative(10, Length.mm);

// return to home
await lockstep.home();
// move a lockstep group so the first axis is at position 1000
lockstep.MoveAbsolute(1000);

// move the lockstep group additional 10mm
lockstep.MoveRelative(10, Units.Length_Millimetres);

// return to home
lockstep.Home();
// move a lockstep group so the first axis is at position 1000
lockstep.moveAbsolute(1000);

// move the lockstep group additional 10mm
lockstep.moveRelative(10, Units.LENGTH_MILLIMETRES);

// return to home
lockstep.home();
% move a lockstep group so the first axis is at position 1000
lockstep.moveAbsolute(1000);

% move the lockstep group additional 10mm
lockstep.moveRelative(10, Units.LENGTH_MILLIMETRES);

% return to home
lockstep.home();
// move a lockstep group so the first axis is at position 1000
lockstep.moveAbsolute(1000);

// move the lockstep group additional 10mm
lockstep.moveRelative(10, Units::LENGTH_MILLIMETRES);

// return to home
lockstep.home();

The @method WaitUntilIdle method can also be used on a lockstep group.

# move with wait until idle flag false
lockstep.move_relative(1000, Units.NATIVE, False)

# ...

# wait until the movement has stopped later on
lockstep.wait_until_idle()
// move with wait until idle flag false
await lockstep.moveRelative(1000, Units.NATIVE, false);

// ...

// wait until the movement has stopped later on
await lockstep.waitUntilIdle();
// move with wait until idle flag false
lockstep.MoveRelative(1000, Units.Native, waitUntilIdle: false);

// ...

// wait until the movement has stopped later on
lockstep.WaitUntilIdle();
// move with wait until idle flag false
lockstep.moveRelative(1000, Units.NATIVE, false);

// ...

// wait until the movement has stopped later on
lockstep.waitUntilIdle();
% move with wait until idle flag false
lockstep.moveRelative(1000, Units.NATIVE, false);

% ...

% wait until the movement has stopped later on
lockstep.waitUntilIdle();
// move with wait until idle flag false
lockstep.moveRelative(1000, Units::NATIVE, false);

// ...

// wait until the movement has stopped later on
lockstep.waitUntilIdle();

Lockstep Information

Once you enable a lockstep group, you can get information about it such as the group ID, the measured twist between the axes, the intended offset between the axes, whether it is busy or not, and the axes numbers in the group.

Twist and offset measurements allow unit conversion, but keep in mind that the conversion is based on the resolution of the first axis in the group.

lockstep_group_id = lockstep.lockstep_group_id
print('ID of the lockstep group: %d' % lockstep_group_id)

is_busy = lockstep.is_busy()
print('Lockstep busy status: %r' % is_busy)

lockstep_axes = lockstep.get_axes()
print('Axes: (%d, %d)' % (lockstep_axes.axis_1, lockstep_axes.axis_2))

axes_offsets = lockstep.get_offsets(Units.LENGTH_MILLIMETRES)
print('Second axis offset: %f' % axes_offsets[0])

axes_twists = lockstep.get_twists(Units.LENGTH_MILLIMETRES)
print('Second axis twist: %f' % axes_twists[0])
const lockstepGroupId = lockstep.lockstepGroupId;
console.log('ID of the lockstep group:', lockstepGroupId);

const isBusy = await lockstep.isBusy();
console.log('Lockstep busy status:', isBusy);

const lockstepAxes = await lockstep.getAxes();
console.log(`Axes: ${lockstepAxes.axis1}, ${lockstepAxes.axis2}`);

const axesOffsets = await lockstep.getOffsets(Length.mm);
console.log('Second axis offset:', axesOffsets[0]);

const axesTwists = await lockstep.getTwists(Length.mm);
console.log('Second axis twist:', axesTwists[0]);
var lockstepGroupId = lockstep.LockstepGroupId;
Console.WriteLine("ID of the lockstep group: {0}", lockstepGroupId);

var isBusy = lockstep.IsBusy();
Console.WriteLine("Lockstep busy status: {0}", isBusy);

var lockstepAxes = lockstep.GetAxes();
Console.WriteLine("Axes: {0}, {1}", lockstepAxes.Axis1, lockstepAxes.Axis2);

var axesOffsets = lockstep.GetOffsets(Units.Length_Millimetres);
Console.WriteLine("Second axis offset: {0}", axesOffsets[0]);

var axesTwists = lockstep.GetTwists(Units.Length_Millimetres);
Console.WriteLine("Second axis twist: {0}", axesTwists[0]);
int lockstepGroupId = lockstep.getLockstepGroupId();
System.out.println("ID of the lockstep group: " + lockstepGroupId);

boolean isBusy = lockstep.isBusy();
System.out.println("Lockstep busy status: " + isBusy);

LockstepAxes lockstepAxes = lockstep.getAxes();
System.out.println("Axes: " + lockstepAxes.getAxis1() + " " + lockstepAxes.getAxis2());

double[] axesOffsets = lockstep.getOffsets(Units.LENGTH_MILLIMETRES);
System.out.println("Second axis offset: " + axesOffsets[0]);

double[] axesTwists = lockstep.getTwists(Units.LENGTH_MILLIMETRES);
System.out.println("Second axis twist: " + axesTwists[0]);
lockstepGroupId = lockstep.getLockstepGroupId();
fprintf('ID of the lockstep group: %d\n', lockstepGroupId);

isBusy = lockstep.isBusy();
fprintf('Lockstep busy status: %d\n', isBusy);

lockstepAxes = lockstep.getAxes();
fprintf('Axes: %d %d\n', lockstepAxes.getAxis1(), lockstepAxes.getAxis2());

axesOffsets = lockstep.getOffsets(Units.LENGTH_MILLIMETRES);
fprintf('Second axis offset: %d\n', axesOffsets(1));

axesTwists = lockstep.getTwists(Units.LENGTH_MILLIMETRES);
fprintf('Second axis twist: %f\n', axesTwists(1));
int lockstepGroupId = lockstep.getLockstepGroupId();
std::cout << "ID of the lockstep group: " << lockstepGroupId << std::endl;

bool isBusy = lockstep.isBusy();
std::cout << "Lockstep busy status: " << isBusy << std::endl;

LockstepAxes lockstepAxes = lockstep.getAxes();
std::cout << "Axes: " << lockstepAxes.getAxis1() << " " << lockstepAxes.getAxis2() << std::endl;

std::vector<double> axesOffsets = lockstep.getOffsets(Units::LENGTH_MILLIMETRES);
std::cout << "Second axis offset: " << axesOffsets[0] << std::endl;

std::vector<double> axesTwists = lockstep.getTwists(Units::LENGTH_MILLIMETRES);
std::cout << "Second axis twist: " << axesTwists[0] << std::endl;

Disabling

Lockstep groups remain enabled through power cycles. The group will persist until you disable it or the device is reset to factory settings.

lockstep.disable()
await lockstep.disable();
lockstep.Disable();
lockstep.disable();
lockstep.disable();
lockstep.disable();

Reference

For the full lockstep command reference please visit:

For more details of when to use a lockstep group, and more details on setting up the axes, visit the technical article: