Servo Setup and Test
Overview
This document is intended to guide UR+ partners and Certified Systems Integrators (CSI) in the setup of a single servo drive for external rotary axis. The intended application is to drive a rotary part positioner in coordinated motion for welding.
Steps
The steps listed below, in order, are covered in this document.
Install the MotionPlus URCap
Set up the servo drive and motor with appropriate power
Connect the servo drive to the UR Controller
Power up the servo drive
Start the MotionPlus service
Modify the ServoConfig script
Run ServoTests 1,2, 3 and 4
Install the MotionPlus URCap
The MotionPlus EtherCAT master is contained in the MotionPlus URCap. It communicates with the the UR Controller over shared memory to send external axis positions from the controller to the EtherCAT drives at 2ms intervals - following the 500Hz clock cycle of the UR controller.
Install the URCap using a USB thumbdrive inserted into the teach pendant. For more information on installing URCaps in general, please refer to this page: Installing a URCap or this page specifically for MotionPlus URCap installation.
Tested Servos
We have tested the following servo drives for MotionPlus compatibility in our lab:
Festo Servo Drive CMMT-AS/CMMT-ST
HCFA Y7S
Innovance SV660N
Mitsubishi J4 and J5 Series
Nanotec PD4
Oriental Motor AZD-AED, AZXD-SED
Yaskawa Sigma7
EtherCAT and USB Ethernet Adapters
Note
We no longer recommend using an USB-Ethernet adapter for EtherCAT due to communication stability problems. Only the use of the control box primary ethernet port is supported for EtherCAT. Do not connect your EtherCAT cable to a network switch - it should be connected directly from the control box to the first EtherCAT servo drive on the fieldbus.
EtherCAT requires very stable communications with low latency and minimum jitter. That is not easily obtained with a USB Ethernet Adapter. For this reason, we only support EtherCAT communication over the primary ethernet port (RJ45 connector).
The MotionPlus URCap is hardwired to communicate via EtherCAT on the eth1
interface, while Polyscope is hardwired to communicate TCP/IP on the eth0
interface. MotionPlus requires the underlying hardware to be swapped: eth1
uses the builtin ethernet port and eth0
uses a USB-Ethernet Adapter. Please refer to to the Ethernet Device Swap document.
The supported USB Ethernet Adapters for handling TCP/IP on eth0
are:
Setup the Servo Drive
Verify that your servo drive supports the necessary protocol. The drive should be capable of;
EtherCAT communication
DC - Distributed Clock
Cia402 Motion Control Profile
CSP - Cyclic Synchronous Positioning (mode 8)
Adjustable cycle time to 2ms (most drives offer this with options for 1ms, 2ms, 4ms)
E-Stop input, optional but recommended. Should be wired in with the UR Control E-Stop safety I/O.
Once you verify that the drive meets this criteria, check for the following capabilities of your servo motor:
Motor has a positioner encoder (absolute or relative). If relative, homing will be required.
Motor has brakes to stop the rotary positioner from moving when power is removed.
Your servo drive should be securely mounted to a fixed base for testing. The shaft (with or without gearbox) should be able to turn freely. Do not attach any load to the drive for these early tests. A qualified technician should connect the drive to its power source, ensure appropriate grounding, and provide the appropriate DC power source for the drive electronics. UR does not provide support or guidance in the installation and wiring of servo drives - please consult with your distributor or servo drive manufacturer.
Connect to the Servo Drive
The UR Control Box (series 5.x) should be connected to the servo drive using an ethernet cable. This cable can be a standard Cat5e cable with RJ45 connectors, or the cable provided by the servo drive manufacturer. Do not connect EtherCAT through a network switch. It should be connected directly to the first servo drive in the EtherCAT chain.
Power Up the Servo Drive
Apply the mains (AC) and the DC power to the servo drive. The Servo Drive should come up, ready to connect. If the servo drive display shows a fault, this must be resolved before proceeding further. For example, some servos require that you clear the encoder backup fault if you have had the drive stored and without power for some time.
Configure the Servo Drive
You will need to have your manfuacturer’s configuration software to set up the servo drive. The typical parameters to set are:
Cycle Time (should be set to 2ms)
Position Base Unit (should be set to radians for rotary and meters for linear axes)
Feed Constant
Gear Ratio
Encoder Resolution
For more information on the specific EtherCAT parameters to set in your configuration software please refer to Servo Configuration
Warning
In order to avoid overflow of the position value (int32), we recommend that you set up your servo drive position units in radians for rotary and in meters for linear axes. Using units of encoder counts can cause overflow in set-ups with high gear ratio (>100:1).
Start the MotionPlus Service
In Polyscope, on the installation page, click on the side tab for MotionPlus. Select the network device from the drop down list, choosing eth1
as your device of choice. Click the [Start] button to begin the MotionPlus service. This starts the service but does not start the EtherCAT master. That is done using a URScript command in the upcoming steps.
Modify the MotionPlus Servo Config Script
See the example script.
Note
It is assumed that you have used your manufacturer’s software to configure units for position in this previous section: Configure Your Servo Drive
You must first copy and modify this script to match your particular servo drive.
Set the value for your encoder resolution (counts per revolution). It’s important to get this right as it affects the positioning and speed of the motor turning
Set the value for the feed constant to 2*PI for a rotary axis if not already set. You may have to changes the sign (+/-) later to ensure that the motor turns in the proper direction.
Set the axis type to revolute if not already set.
Save your changes to the servoconfig script.
Servo Tests Program Setup
Create a new program
Turn off the ‘Loop forever’ option
Add the servo config script
Add the servo test scripts 1, 2, 3 and 4 from the following sections
Turn off scripts 2, 3 and 4. We will only run one test at a time, disabling the others while we run.
Servo Test 1 - Basic Servo Move
See the example script.
This first test will check to see if the ethercat communications are working and that the servo motor can rotated one turn in each direction, with a speed of 1 revolution per second.
Power up the robot from the Teach Pendent (main screen, lower left button)
Run the program
You should see the motor rotate one turn counter clockwise (view facing the end of the motor shaft our gear output), then rotate two turns clockwise, and finally rotate back to the starting position.
The speed should be 1 revolution per second.
If the motor rotates in the opposite direction, then edit the your servo script and change the sign for the feedconstant to -2*PI.
If the motor rotates faster or slower than expected it is possible that you have the wrong setting for encoder counts-per-revolution. Check your servo motor manual and consult with your servo manufacturer.
If the servo drive issues a fault or the MotionPlus displays and error, please consult the MotionPlus TroubleShooting (document)[servotrouble.md] for diagnostic techniques.
Servo Test 2 - Homing
See the example script.
Once you have completed Test #1, you are ready to test homing. MotionPlus (release 1.0) only supports homing at the current position. This effectively resets the internal encoder count to zero immediately without moving the motor. More advanced homing techniques with limits switches are coming in a later update to MotionPlus.
Power up the robot from the Teach Pendent (main screen, lower left button)
Disable Servo Test1 and enable Servo Test 2
Run the program
The homing test is in two parts. The first is without homing, then the same motion executed with homing.
For the first part, you should see the motor perform three revolutions - rewinding to zero after each turn.
In the second part, the program will home to zero between revolutions, so there should be no rewind - just three revolutions in the same direction with a pause of 1 sec between each one.
Servo Test 3 - Calibration
See the example scripts for single-axis and dual-axis calibration.
In order to perform coordinated motion, we need to calibrate the pose of the rotary axis with respect to the robot base pose. This is done through a calibration procedure. Follow these steps:
Power on the robot
Attach a pointing tool (see image below) for the calibration procedure.
Use the built-in Polyscope TCP calibration to create a TCP for the pointing tool.
Set the TCP for the pointer as default.
Add an installation variable named CAL_AX1_ROTARY and set the value to p[0,0,0,0,0,0]. This global installation variable will store the calibration pose for the rotary axis.
On the program tab, disable Servo Test 2 and enable Servo Test 3
Run the program
This program will instruct you to teach a single point in four different positions on the circumference of a circle around the motor shaft center. You will need to identify a single feature that rotates around the axis as the teachable point. The axis will start at position 0, and then increment 90 degrees for each additional point to teach. The four sampled points are used to calculate the position and orientation of the axis of rotation.
The result is the pose update of the global variable CAL_AX1_ROTARY, used in the next step for testing coordinated motion.
Servo Test 4 - Coordinated Motion
See the example script.
This final step will test motion of the robot TCP and the rotary axis together. This test is quite simple - the robot TCP, close to the rotary axis will synchronize with the motion of the axis - appearing to remain fixed to the rotary direction. In this test, we command the robot TCP to frame track with the axis, then we turn the axis. We do not directly command the robot position. This is a visual verification to check if your servo config is also correct and that you have calibrated the axis accurately.
Power on the robot
Disable Servo Test 3 and enable Servo Test 4
Using Free Drive, bring the robot TCP close but not touching the motor shaft or attached flange. A distance of 5-10cm is suggested.
Adjust the speed slider on the Teach Pendant down to 10%
Be ready to stop the program immediately if the robot moves in the wrong direction - i.e. does not follow the axis rotation direction.
Run the program
First, the servo should turn a quarter turn in the positive direction (90 degrees), back to the starting position, then a quarter turn in the negative direction (-90 degrees), then back to start.
Next, with tracking on, this motion is repeated, but this time with the TCP pointer tracking the rotary motion.
Important
If the robot moves in the wrong direction with tracking on, then the calibration routine from the previous step was not successful and needs to be performed again with careful attention being made to the order in which the calibration points are taught according to the right-hand rule.
Script Examples
EtherCAT Drive Config Script
This script should be placed first within a program.
# EtherCAT Single Axis Config Script
# This script provides the MotionPlus configuration for the EtherCAT drive, servo motor, gearing and encoder.
# NOTE: You must first create the following global variable in the installation tab under "General"
# CAL_AX1_ROTARY p[0,0,0,0,0,0]
# This will be used to store the calibrated poses calculated here
PI = acos(-1)
AXIS_TYPE1 = 0 # 0:rotary, 1:linear
VELOCITY_LIMIT1 = 0.7*2 # rad/s
ACCELERATION_LIMIT1 = 0.1*100 # rad/s^2
ENCODER_RESOLUTION1 = <ENCODER>
FEED_CONSTANT1 = 2 * PI # make sure +Z points out of motor
GEAR_RATIO1 = <GEAR>
ZERO_OFFSET1 = 0
def run_axis_config():
# stop EtherCAT master first, otherwise, reset_world_model may throw an error
ethercat_clear_error()
ethercat_stop(True)
reset_world_model()
# after calibration, change this to use CAL_AX1_ROTARY
axis1_position = p[0,0,0,0,0,0]
#axis1_position = CAL_AX1_ROTARY
axis_group_add("positioner", p[0,0,0,0,0,0], "base")
axis_group_add_axis("positioner", "axis1", "", axis1_position, AXIS_TYPE1, VELOCITY_LIMIT1, ACCELERATION_LIMIT1)
ethercat_config_axis("axis1", 1, ENCODER_RESOLUTION1, GEAR_RATIO1, FEED_CONSTANT1, ZERO_OFFSET1)
ethercat_set_parameter("dc_enable", True)
ethercat_start(10)
if (ethercat_is_axis_in_fault_state("axis1")):
textmsg("FAULT in: ", "axis1")
if(ethercat_reset_axis_fault("axis1")):
textmsg("Axis 1 FAULT cleared")
else:
return False
end
end
ethercat_enable_axis("axis1")
return True
end
run_config() #run the axis configuration
Basic Move Rotary Script
# Basic Move - Slow turn of the servo
if not run_axis_config():
popup("configuration failed to run", title="Basic Move", warning=False, error=True, blocking=True)
halt
end
popup("Ready for Basic Move?", title="Servo Test1", blocking=True)
popup("Moving axis to ZERO", title="Servo Test1", blocking=True)
axis_group_movej("positioner", [0], 0.1, 0.1)
popup("Moving axis to +90, -90, ZERO", title="Servo Test1", blocking=True)
axis_group_movej("positioner", [d2r(90)], 0.4, 0.4)
sleep(0.5)
axis_group_movej("positioner", [d2r(-90)], 0.4, 0.4)
sleep(0.5)
axis_group_movej("positioner", [d2r(0)], 0.4, 0.4)
sleep(0.5)
Homing Script
# Test 2 - Homing
if not run_axis_config():
popup("configuration failed to run", title="Axis Homing", warning=False, error=True, blocking=True)
halt
end
popup("Ready for Homing?", title="Servo Test2", blocking=True)
popup("Moving axis to ZERO", title="Servo Test2", blocking=True)
axis_group_movej("positioner", [0], 0.1, 0.1)
popup("Home axis to ZERO position?", title="Home Axis", blocking=True)
homing_method = 35 #home to position method. you may need method 37 for your servo
if ethercat_home_axis("axis1", homing_method,0,[],[]) == False:
popup("Homing failed", title="Home Axis", blocking=True, error=True)
end
popup("Turn axis 3 times without homing", title="Test2-Homing", blocking=True)
# we expect the axis to go back to zero after each rotation
i = 0
while i < 3:
axis_group_movej("positioner", [d2r(0)], 0.4, 0.4)
sleep(0.5)
axis_group_movej("positioner", [d2r(360)], 0.4, 0.4)
sleep(0.5)
i = i + 1
end
popup("Turn 3 times with homing after each turn", title="Test2-Homing", blocking=True)
# we expect that the servo will just rotate 3 times, resetting to zero after each rotation
i = 0
while i < 3:
axis_group_movej("positioner", [d2r(0)], 0.4, 0.4)
sleep(0.5)
axis_group_movej("positioner", [d2r(360)], 0.4, 0.4)
sleep(0.5)
ethercat_home_axis("axis1", homing_method,0,[],[])
i = i + 1
end
Single Axis Calibration Script
Image: Sample four points at 0,90,180 and 270 to calculate the axis of rotation. The example script rotates 90 degrees after each point is sampled.
The calibration of a single rotary axis is performed by resampling a single point at four different positions around the axis of rotation. These sampled points are used to calculate the frame that is at the rotation center, with the z-axis of the frame aligned with the axis of rotation, pointing out from the motor shaft. This basic script will prompt you to freedrive to the target point at different positions, sample the points, then run the calibration function (calibrate_rotary_axis) and store the result to the global variable CAL_AX1_ROTARY.
# Single Rotary Axis Calibration
def move_axis(group_name, axis1_position):
msg1 = group_name
msg1 = str_cat(msg1, " will now move to [")
msg1 = str_cat(msg1, to_str(axis1_position))
msg1 = str_cat(msg1, "] degrees")
popup(msg1, "Axis is about to move!", warning=True, error=False, blocking=True)
ethercat_enable_axis("axis1")
axis_group_movej(group_name, [d2r(axis1_position)], 1, 1)
ethercat_disable_axis("axis1")
end
def teachCalPointAxis(position1, ref_frame):
move_axis("positioner", position1)
freedrive_mode()
msg = "Freedrive to the calibration point, then click Continue"
popup(msg, "Axis Calibration", blocking=True)
p = get_pose("tcp", ref_frame)
end_freedrive_mode()
return p
end
#-----------------------------------------------------------
# Provide a function to configure your base ethercat setup
#-----------------------------------------------------------
if not run_axis_config():
popup("configuration failed to run", title="Axis Calibration", warning=False, error=True, blocking=True)
halt
end
# Teach the pose of axis1 relatitve to the robot base.
# The robot base should not move
popup("Ready to Calibrate Axis1?", title="Axis Calibration", warning=False, error=False, blocking=True)
p1 = teachCalPointAxis( 0.0, "base")
p2 = teachCalPointAxis( 90.0, "base")
p3 = teachCalPointAxis(180.0, "base")
p4 = teachCalPointAxis(270.0, "base")
move_axes("positioner", 0)
CAL_AX1_ROTARY = calibrate_rotary_axis([p1, p2, p3, p4],[])[0]
popup("Axis1 pose saved in the installation variable CAL_AX1_ROTARY", "Calibration")
# Teach the pose of axis2 relatitve to axis1
# Axis1 should not move
popup("Completed Single rotary axis calibration.", "Calibration")
Dual Axis Calibration Script
The calibration of dual rotary axes, where axis2 is mounted to axis1, is performed the same way as a single axis. The first axis (axis1) must remain stationary while axis2 is sampled and calibrated. This basic script will prompt you to freedrive to the target point at different positions, sample the point, then run the calibration function (calibrate_rotary_axis) and store the results to global variables CAL_AX1_ROTARY and CAL_AX2_ROTARY.
# Dual Axis Configuration
# NOTE: You must first create the following global variables in the installation tab under "General"
# CAL_AX1_ROTARY p[0,0,0,0,0,0]
# CAL_AX2_ROTARY p[0 ,0,0,0,0,0]
# This will be used to store the calibrated poses calculated here
PI = acos(-1)
AXIS_TYPE1 = 0 # 0:rotary, 1:linear
VELOCITY_LIMIT1 = 0.7*2 # rad/s
ACCELERATION_LIMIT1 = 0.1*100 # rad/s^2
ENCODER_RESOLUTION1 = <ENCODER1>
FEED_CONSTANT1 = 2 * PI # make sure +Z points out of motor
GEAR_RATIO1 = <GEAR1>
ZERO_OFFSET1 = 0
AXIS_TYPE2 = 0 # 0:rotary, 1:linear
VELOCITY_LIMIT2 = 0.7*2 # rad/s
ACCELERATION_LIMIT2 = 0.1*100 # rad/s^2
ENCODER_RESOLUTION2 = <ENCODER2>
FEED_CONSTANT2 = 2 * PI # make sure +Z points out of motor
GEAR_RATIO2 = <GEAR2>
ZERO_OFFSET2 = 0
def run_dualaxis_config():
# stop EtherCAT master first, otherwise, reset_world_model may throw an error
ethercat_clear_error()
ethercat_stop(True)
reset_world_model()
axis_group_add("positioner", p[0,0,0,0,0,0], "base")
# after calibration, change these to use CAL_AX1_ROTARY and CAL_AX2_ROTARY
axis1_position = p[0,0,0,0,0,0]
axis2_position = p[0,0,0,0,0,0]
#axis1_position = CAL_AX1_ROTARY
#axis2_position = CAL_AX2_ROTARY
# configured as independent at first
axis_group_add_axis("positioner", "axis1", "", axis1_position, AXIS_TYPE1, VELOCITY_LIMIT1, ACCELERATION_LIMIT1)
axis_group_add_axis("positioner", "axis2", "axis1", axis2_position, AXIS_TYPE2, VELOCITY_LIMIT2, ACCELERATION_LIMIT2)
ethercat_config_axis("axis1", 1, ENCODER_RESOLUTION1, GEAR_RATIO1, FEED_CONSTANT1, ZERO_OFFSET1)
ethercat_config_axis("axis2", 2, ENCODER_RESOLUTION2, GEAR_RATIO2, FEED_CONSTANT2, ZERO_OFFSET2)
ethercat_set_parameter("dc_enable", True)
ethercat_start(10)
if (ethercat_is_axis_in_fault_state("axis1")):
textmsg("FAULT in: ", "axis1")
if(ethercat_reset_axis_fault("axis1")):
textmsg("Axis 1 FAULT cleared")
else:
return False
end
end
ethercat_enable_axis("axis1")
if (ethercat_is_axis_in_fault_state("axis2")):
textmsg("FAULT in: ", "axis2")
if(ethercat_reset_axis_fault("axis2")):
textmsg("Axis2 FAULT cleared")
else:
return False
end
end
ethercat_enable_axis("axis2")
return True
end
# Dual Rotary Axis Calibration
def move_axes(group_name, axis1_position, axis2_position):
msg1 = group_name
msg1 = str_cat(msg1, " will now move to [")
msg1 = str_cat(msg1, to_str(axis1_position))
msg1 = str_cat(msg1, ", ")
msg1 = str_cat(msg1, to_str(axis2_position))
msg1 = str_cat(msg1, "] degrees")
popup(msg1, "Axes are about to move!", warning=True, error=False, blocking=True)
ethercat_enable_axis("axis1")
ethercat_enable_axis("axis2")
axis_group_movej(group_name, [d2r(axis1_position), d2r(axis2_position)], 1, 1)
ethercat_disable_axis("axis2")
ethercat_disable_axis("axis1")
end
def teachCalPointAxis(position1, position2, ref_frame):
move_axes("positioner", position1, position2)
freedrive_mode()
msg = "Freedrive to the calibration point, then click Continue"
popup(msg, "Axis Calibration", blocking=True)
p = get_pose("tcp", ref_frame)
end_freedrive_mode()
return p
end
#-----------------------------------------------------------
# Provide a function to configure your base ethercat setup
#-----------------------------------------------------------
if not run_dualaxis_config():
popup("dual configuration failed to run", title="Axis Calibration", warning=False, error=True, blocking=True)
halt
end
# Teach the pose of axis1 relatitve to the robot base.
# The robot base should not move
popup("Ready to Calibrate Axis1?", title="Axis Calibration", warning=False, error=False, blocking=True)
p1 = teachCalPointAxis( 0.0, 0.0, "base")
p2 = teachCalPointAxis( 90.0, 0.0, "base")
p3 = teachCalPointAxis(180.0, 0.0, "base")
p4 = teachCalPointAxis(270.0, 0.0, "base")
move_axes("positioner", 0, 0)
CAL_AX1_ROTARY = calibrate_rotary_axis([p1, p2, p3, p4],[])[0]
popup("Axis1 pose saved in the installation variable CAL_AX1_ROTARY", "Calibration")
axis_group_update_axis("axis1", CAL_AX1_ROTARY)
# Teach the pose of axis2 relatitve to axis1
# Axis1 should not move
popup("Ready to Calibrate Axis2?", title="Axis Calibration", warning=False, error=False, blocking=True)
p1 = teachCalPointAxis(0.0, 0.0, "axis1")
p2 = teachCalPointAxis(0.0, 90.0, "axis1")
p3 = teachCalPointAxis(0.0,180.0, "axis1")
p4 = teachCalPointAxis(0.0,270.0, "axis1")
move_axes("positioner", 0, 0)
CAL_AX2_ROTARY = calibrate_rotary_axis([p1, p2, p3, p4],[])[0]
popup("Axis2 pose saved in the installation variable CAL_AX2_ROTARY", "Calibration")
axis_group_update_axis("axis2", CAL_AX2_ROTARY)
popup("Completed Dual Axis Calibration", "Calibration")
Coordinated Motion Script
# Coordinated Motion with Single Axis Positioner
if not run_axis_config():
popup("configuration failed to run", title="Coordinated Motion", warning=False, error=True, blocking=True)
halt
end
popup("Ready for Coordinated Motion?", title="Axis Testing", blocking=True)
popup("Moving axis to HOME", title="Servo Test 4", blocking=True)
axis_group_movej("positioner", [0], 0.1, 0.1)
popup("Moving axis to +90, -90, HOME", title="Axis Testing", blocking=True)
axis_group_movej("positioner", [d2r(90)], 0.4, 0.4)
sleep(0.5)
axis_group_movej("positioner", [d2r(-90)], 0.4, 0.4)
sleep(0.5)
axis_group_movej("positioner", [d2r(0)], 0.4, 0.4)
sleep(0.5)
#set this value to clear any obstacle mounted to the shaft
#prevents the tool from colliding with the axis
distanceFromAxis = 0.06
popup("Moving TCP to position near axis", title="Axis Testing", blocking=True)
APPROACH_TRANS = p[0, 0, -distanceFromAxis, 0, 0, 0]
# place a frame with Z axis pointing down (dr2(180)) and then some distance up from the axis
add_frame("positioner_base", p[CAL_AX1_ROTARY[0], CAL_AX1_ROTARY[1], CAL_AX1_ROTARY[2], 0, d2r(180), 0], "base")
movel(struct(pose=APPROACH_TRANS,frame="positioner_base"))
popup("Tracking axis rotation...", title="Axis Testing", blocking=True)
frame_tracking_enable("axis1")
popup("Moving axis to +15, -15, HOME", title="Axis Testing", blocking=True)
axis_group_movej("positioner", [d2r(15)], 0.4, 0.4)
sleep(0.5)
axis_group_movej("positioner", [d2r(-15)], 0.4, 0.4)
sleep(0.5)
axis_group_movej("positioner", [d2r(0)], 0.4, 0.4)
sleep(0.5)