TAM SDK – Automation goes .NET

TAM SDK is a software development kit to create Windows applications for a Triamec motion system. It is based on the powerful Microsoft .NET Framework and creates a hardware abstraction of the devices connected to the Tria-Link. This makes communication with the Tria-Link participants transparent and very easy.

The Microsoft .NET Framework is inherently not real-time capable. Nevertheless, various real-time applications can be realized without problems, thanks to two special features of the Triamec Automation and Motion (TAM) system: The Tria-Link Host Adapter Boards (TL) have real-time tables. Pre-calculated paths can be traced easily with these tables. Furthermore, the Triamec devices have a freely programmable real-time processor, which allows time-critical tasks such as monitoring, axis coupling (portals), special reactions (touch-downs), etc. to be realized.

Functions of the TAM API

The Microsoft .NET Framework-based TAM SDK (red) provides the protocol layer of the Tria-Link and above it the TAM system layer. A typical application (blue) is based on the TAM layer interface and also uses plug-in modules and graphic components for the user interface (TAM UI). The Tama compiler is used to compile real-time programs that are executed on the devices.

Triamec TAM SDK
Triamec TAM SDK

Overview

  • Transparent Tria-Link Communication
  • Applications in Microsoft®.NET Visual C#
  • Extensive help system and IntelliSense support
  • Cyclic data exchange with triggers
  • Condition monitoring and events
  • Persistence of configurations
  • Motion commands (based on PLCopen)
  • Operation of the real-time tables in the TL adapter boards
  • Control of the Tama programs
  • Data acquisition up to 50kHz
  • Plugin modules

Programming against the TAM SDK

Extensive interfaces are available for creating an application, see examples below. A guide is available here:

Installation

The TAM SDK and some sample Visual Studio® solutions are installed on the PC with the TAM SDK.

TAM sample programs

In the following section you can view three sample TAM programs and get an insight into TAM programming. These and other examples with complete solutions are installed with the TAM SDK and are found in the TAM System Explorer with the menu "Help | Developer Samples".

Example 1: Hello World: System setup, Operation Drive and axes, Move
Example 2: GearUp: Realization of an electronic gearbox with Tama.
Example 3: Acquisition: Set up system, operation drive, record signal

  • Hello World

    Hello World Example Program

    using System; using System.Windows.Forms; using Triamec.Tam.Rlid4; using Triamec.Tam.Samples.Properties; using Triamec.Tam.UI; using Triamec.TriaLink; namespace Triamec.Tam.Samples { /// <summary> /// The main form of the TAM "Hello World!" application. /// </summary> internal partial class HelloWorldForm : Form { #region Fields private TamTopology topology; private ITamDrive drive; private TamAxis axis; private float velocityMaximum; private TamExplorerForm tamExplorerForm; #endregion Fields #region Hello world code /// <summary> /// Prepares the TAM system. /// </summary> /// <exception cref="TamException">Startup failed.</exception> /// <remarks> /// <list type="bullet"> /// <item>Creates a TAM topology,</item> /// <item>boots the Tria-Link,</item> /// <item>searches for a TS151 servo-drive,</item> /// <item>loads and applies a TAM configuration.</item> /// </list> /// </remarks> private void Startup() { // Create the root object representing the topology of the TAM hardware. // Note that we must dispose this object at the end in order to clean up resources. topology = new TamTopology("Tutorial"); // Add the local TAM system on this PC to the topology. var system = topology.AddLocalTamSystem(null); // Get the (first) Tria-Link on the (first) PCI Adapter of the local TAM system. var link = system[0][0]; // Boot the Tria-Link so that it learns about connected stations. link.Initialize(); // Caution! // Verify that the content of file HelloWorldTamConfiguration.xml // has been edited to apply to your hardware environment. // You can harm your hardware with inappropriate configuration values. #if DEBUG System.Diagnostics.Debugger.Break(); #endif // Find the (first) TS151 drive in the Tria-Link. // Iterate over the stations one by one // because the Tria-Link booting does not guarantee a particular order. foreach (var station in link) { if (station.HardwareIdDetails.ProductType == ProductType.FromName("TS151")) { // found a drive to work with drive = station.Devices[0] as ITamDrive; break; // out of foreach loop } } if (drive == null) throw new TamException("Drive not found."); // Load a TAM configuration using a GUI. // Alternatively, an instance of the Triamec.Tam.Configuration.Deserializer class can be // instantiated, giving more possibilities. LoadSurveyor.Load(Settings.Default.TamConfigurationPath, topology, true, true); // Get its first (and only) axis of the found drive. axis = drive.Axes[0]; // Get the register layout of the drive // and cast it to the RLID-specific register layout. Register register = drive.Register as Register; // Navigate to the sub-tree of registers for the first axis. Axis axisRegister = register.Axes[0]; // Read and cache the original velocity maximum value, // which was applied from the configuration file. velocityMaximum = axisRegister.Parameters.PathPlanner.VelocityMaximum.Read(); } /// <exception cref="TamException">Enabling failed.</exception> private void EnableDrive() { // Set the drive operational, i.e. switch the power section on. drive.SwitchBridgePower(BridgePowerSwitch.On); // Enable the axis controller. axis.Control(AxisControlCommands.Enable); } /// <exception cref="TamException">Disabling failed.</exception> private void DisableDrive() { // Disable the axis controller. axis.Control(AxisControlCommands.Disable); // Switch the power section off. drive.SwitchBridgePower(BridgePowerSwitch.Off); } /// <summary> /// Moves in the specified direction. /// </summary> /// <param name="sign">A positive or negative value indicating the direction of the motion.</param> /// <exception cref="TamException">Moving failed.</exception> private void MoveAxis(int sign) { // Move a distance with dedicated velocity. axis.MoveRelative(Math.Sign(sign) * 0.5 * Math.PI, velocityMaximum * velocityTrackBar.Value * 0.01f); } #endregion Hello world code // rest of application } }
  • GearUp

    GearUp Example program

    using System; using System.Windows.Forms; using Triamec.Tam.Samples.Properties; using Triamec.Tam.Subscriptions; using Triamec.Tam.UI; using Triamec.Tama; using Triamec.TriaLink; using RegisterMaster = Triamec.Tam.Rlid4.Register; using RegisterSlave = Triamec.Tam.Rlid5.Register; namespace Triamec.Tam.Samples { /// <summary> /// The main form of the TAM "Gear Up!" application. /// </summary> internal partial class GearUpForm : Form { #region Constants /// <summary> /// The time to wait for <see cref="TamRequest"/> to complete, in milliseconds. /// </summary> public const int RequestTimeout = 1000; #endregion Constants #region Fields private TamTopology topology; private TamSystem system; private TamLink link; private ITamDrive masterDrive; private TamAxis masterAxis; private RegisterMaster masterRegisterRoot; private ITamDrive slaveDrive; private TamAxis slaveAxis; private RegisterSlave slaveRegisterRoot; private ISubscription subscription; private float velocityMaximum; private TamExplorerForm tamExplorerForm; #endregion Fields #region Gear up code /// <summary> /// Prepares the TAM system. /// </summary> /// <returns>Returns a <see langword="null"/> reference if the preparation succeeded; /// otherwise, the exception representing the failure.</returns> /// <exception cref="TamException">Startup failed.</exception> /// <remarks> /// <list type="bullet"> /// <item>Creates a TAM topology,</item> /// <item>boots the Tria-Link,</item> /// <item>loads and applies a TAM configuration,</item> /// <item>searches for two servo-drives.</item> /// </list> /// </remarks> private void Startup() { // Create the root object representing the topology of the TAM hardware. // Note that we must dispose this object. this.topology = new TamTopology(string.Empty); // Add the local TAM system on this PC to the topology. // Note that this step requires a valid license for the TAM SDK. this.system = topology.AddLocalTamSystem(string.Empty); // Get the (first) Tria-Link on the (first) PCI Adapter of the local TAM system. this.link = this.system[0][0]; // Boot the Tria-Link so that it learns about connected stations. this.link.Initialize(); // Caution! // Verify that the content of file GearUpTamConfiguration.xml // has been edited to apply to your hardware environment. // You can harm your hardware with inappropriate configuration values. #if DEBUG System.Diagnostics.Debugger.Break(); #endif // Load the TAM configuration, showing progress in a dialog window. LoadSurveyor.Load(Settings.Default.TamConfigurationPath, this.topology, true, true); // Find the two drives in the Tria-Link. // Iterate over the stations one by one // because the Tria-Link booting does not guarantee a particular order. foreach (TamStation station in this.link) { if (station.HardwareSerialNumber == Settings.Default.SerialNumberMaster) { // found the master drive to work with this.masterDrive = (ITamDrive)station.Devices[0]; // Get its first (and only) axis of the found drive. // Note: this must be done after loading the TAM configuration. // setting the motor base configuration this.masterAxis = this.masterDrive.Axes[0]; // Get the register tree of the master drive. this.masterRegisterRoot = (RegisterMaster)this.masterDrive.Register; } else if (station.HardwareSerialNumber == Settings.Default.SerialNumberSlave) { // found the slave drive to work with this.slaveDrive = (ITamDrive)station.Devices[0]; // Get its first (and only) axis of the found drive. // Note: this must be done after loading the TAM configuration. // setting the motor base configuration this.slaveAxis = this.slaveDrive.Axes[0]; // Get the register tree of the slave drive. this.slaveRegisterRoot = (RegisterSlave)this.slaveDrive.Register; } } // Assert that we found the two drives if (this.masterAxis == null) { throw new TamException("Failed to find the master drive."); } if (this.slaveAxis == null) { throw new TamException("Failed to find the slave drive."); } // Download the Tama program // Note that alternatively, the Tama program may also be saved with the TAM configuration. var tamaManager = this.slaveDrive.TamaManager; tamaManager.TamaAssemblyPath = TamaProgram.GetBinaryFilename(typeof(ElectronicGearing)); tamaManager.DoDownload(); // Read and cache the original velocity maximum value, // which was applied from the configuration file. this.velocityMaximum = this.masterRegisterRoot.Axes[0].Parameters.PathPlanner.VelocityMaximum.Read(); } /// <exception cref="TamException">Enabling failed.</exception> private void EnableDrives() { // set the master drive operational, i.e. switch the power section on this.masterDrive.SetOperational(); // set the slave drive operational, i.e. switch the power section on this.slaveDrive.SetOperational(); // reset any axis error and enable the axis controller this.slaveAxis.Control(AxisControlCommands.ResetErrorAndEnable); // reset any axis error and enable the axis controller this.masterAxis.Control(AxisControlCommands.ResetErrorAndEnable); } /// <exception cref="TamException">Disabling failed.</exception> private void DisableDrives() { // disable the master axis controller this.masterAxis.Control(AxisControlCommands.Disable); // on the master drive, switch the power section off this.masterDrive.SwitchBridgePower(BridgePowerSwitch.Off); // disable the slave axis controller this.slaveAxis.Control(AxisControlCommands.Disable); // on the slabe drive, switch the power section off this.slaveDrive.SwitchBridgePower(BridgePowerSwitch.Off); } /// <summary> /// Couples the slave axis with the master axis. /// </summary> /// <exception cref="TamException">Coupling failed.</exception> private void Couple() { // reset positions in order to avoid discontinuity in the slave's position control this.masterAxis.SetPosition(0); this.slaveAxis.SetPosition(0); // reset gear to ½ this.slaveRegisterRoot.Tama.Variables.GenPurposeVar0.Write(0.5f); // enable electronic gearing function this.slaveDrive.TamaManager.IsochronousVM.EnableAndVerify(); // publish the path planner output of the master axis Publisher publisher = new Publisher( // timestamp implicitly included this.masterRegisterRoot.Axes[0].Signals.PathPlanner.Position, this.masterRegisterRoot.Axes[0].Signals.PathPlanner.Velocity, this.masterRegisterRoot.Axes[0].Signals.PathPlanner.Acceleration); // store the master motion signals to the path planner input of the slave axis Subscriber subscriber = new Subscriber( this.slaveRegisterRoot.Axes[0].Signals.PathPlanner.PathValuesTimestamp, this.slaveRegisterRoot.Axes[0].Commands.PathPlanner.Xnew, this.slaveRegisterRoot.Axes[0].Commands.PathPlanner.Vnew, this.slaveRegisterRoot.Axes[0].Commands.PathPlanner.Anew); // set up and enable subscription this.subscription = masterDrive.Station.Link.SubscriptionManager.Subscribe(publisher, subscriber); this.subscription.Enable(); // set slave into coupled move slaveAxis.CoupleIn(false); } /// <summary> /// Decouples the slave axis from the master axis. /// </summary> /// <exception cref="TamException">Decoupling failed.</exception> private void Decouple() { // stop coupled move of slave axis slaveAxis.Stop(); // stop subscription if (this.subscription != null) { this.subscription.Unsubscribe(); this.subscription.Dispose(); this.subscription = null; } } /// <summary> /// Moves in the specified direction. /// </summary> /// <param name="sign">A positive or negative value indicating the direction of the motion.</param> /// <exception cref="TamException">Moving failed.</exception> private void MoveAxis(int sign) { this.masterDrive.Axes[0].MoveRelative(Math.Sign(sign) * 3 * Math.PI, this.velocityMaximum * this.velocityTrackBar.Value * 0.01f); } #endregion Gear up code // rest of application } }
  • Acquisition

    Acquisition Example Program

    using System; using System.Threading; using System.Windows.Forms; using System.Windows.Forms.DataVisualization.Charting; using Triamec.Acquisitions; using Triamec.Tam.Acquisitions; using Triamec.Tam.Registers; using Triamec.Tam.Samples.Properties; using Triamec.Tam.UI; using Triamec.TriaLink; using Axis = Triamec.Tam.Rlid4.Axis; namespace Triamec.Tam.Samples { sealed partial class AcquisitionForm : Form { #region Fields TamTopology _topology; ITamDrive _drive; TamAxis _axis; ITamAcquisition _acquisition; ITamVariable<double> _positionVariable, _positionErrorVariable; ITamTrigger _trigger; #endregion Fields #region Constructor /// <summary> /// Initializes a new instance of the <see cref="AcquisitionForm"/> class. /// </summary> public AcquisitionForm() { InitializeComponent(); } #endregion Constructor #region Acquisition demo code /// <summary> /// Prepares the TAM system. /// </summary> /// <remarks> /// <list type="bullet"> /// <item><description>Creates a TAM topology,</description></item> /// <item><description>boots the Tria-Link,</description></item> /// <item><description>searches for a TS151 servo-drive,</description></item> /// <item><description>loads and applies a TAM configuration,</description></item> /// <item><description>creates the acquisition.</description></item> /// </list> /// </remarks> /// <exception cref="TamException">Startup failed.</exception> void Startup() { // Create the root object representing the topology of the TAM hardware. // Note that we must dispose this object at the end in order to clean up resources. _topology = new TamTopology("Tutorial"); // Add the local TAM system on this PC to the topology. var system = _topology.AddLocalTamSystem(null); // Get the (first) Tria-Link on the (first) PCI Adapter of the local TAM system. var link = system[0][0]; // Boot the Tria-Link so that it learns about connected stations. // Note that you may use the more robust Initialize() when the Tria-Link adapter isn't an observer // (see link.Adapter.Role). link.Identify(); // Find the (first) TS151 drive in the Tria-Link. // Iterate over the stations one by one // because the Tria-Link booting does not guarantee a particular order. foreach (var station in link) { if (station.HardwareIdDetails.ProductType == ProductType.FromName("TS151")) { // found a drive to work with _drive = station.Devices[0] as ITamDrive; break; // out of foreach loop } } if (_drive == null) throw new TamException("Drive not found."); // Load a TAM configuration using a GUI. // Alternatively, an instance of the Triamec.Tam.Configuration.Deserializer class can be // instantiated, giving more possibilities. LoadSurveyor.Load(Settings.Default.TamConfigurationPath, _topology, true, true, this); // Get its first (and only) axis of the found drive. _axis = _drive.Axes[0]; Axis axisRegister = (Axis)_drive.Axes[0].Register; // Create two acquisition variables for position and position error. // Specify 0 for the sampling time, which will be rounded to the lowest possible sampling time. ITamReadonlyRegister posReg = axisRegister.Signals.PositionController.ActualPosition; _positionVariable = posReg.CreateVariable(TimeSpan.FromSeconds(0)); ITamReadonlyRegister errorReg = axisRegister.Signals.PositionController.PositionError; _positionErrorVariable = errorReg.CreateVariable(TimeSpan.FromSeconds(0)); // As soon as multiple variables are to be recorded synchronized, create an acquisition object. // Otherwise, you may use the Acquire methods of the variable itself. _acquisition = TamAcquisition.Create(_positionVariable, _positionErrorVariable); } /// <exception cref="TamException">Enabling failed.</exception> void EnableDrive() { // Prepare for the use of the WaitForTermination method. _drive.AddStateObserver(this); // Set the drive operational, i.e. switch the power section on. _drive.SwitchOn().WaitForTermination(); // Enable the axis controller. _axis.Control(AxisControlCommands.Enable).WaitForTermination(); } /// <exception cref="TamException">Disabling failed.</exception> void DisableDrive() { // Disable the axis controller. _axis.Control(AxisControlCommands.Disable).WaitForTermination(); // Switch the power section off. _drive.SwitchOff().WaitForTermination(); // Counter part for AddStateObserver. _drive.RemoveStateObserver(this); } /// <summary> /// Plots one data series. /// </summary> static void Fill(Series series, ITamVariable<double> variable, double scaling) { DataPointCollection points = series.Points; points.SuspendUpdates(); points.Clear(); foreach (double value in variable) { points.AddY(value * scaling); } points.ResumeUpdates(); } /// <summary> /// Called when an acquisition completes. /// </summary> /// <remarks>Must be called on the main thread.</remarks> void OnAcquired(bool hasMore, AcquisitionException ex) { // don't plot anymore if the form is already closed if (!Visible) return; if (ex == null) { // plot Fill(chart.Series["Position"], _positionVariable, 1); Fill(chart.Series["Position Error"], _positionErrorVariable, 1E3); // repeat recording Acquire(); } else { MessageBox.Show(ex.Message, "Failure during acquisition", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, 0); } } /// <summary> /// Start an acquisition. /// </summary> /// <remarks>Must be called on the main thread.</remarks> void Acquire() { // Start asynchronously in order to not block the main thread. // pass a delegate to the method OnAcquired called when acquire completes. // The delegate will be called using the synchronization context of this thread. _acquisition.AcquireAsync(TimeSpan.FromMilliseconds(trackBarDuration.Value), _trigger, new AcquireFuture(OnAcquired)); } /// <summary> /// Recreates the trigger from a new value. /// </summary> /// <remarks>Must be called on the main thread.</remarks> void RefreshTrigger() { // Create a hardware trigger on velocity with raising edge on the level dictated by the trigger level // track bar. _trigger = new TamTrigger(((Axis)_axis.Register).Signals.PathPlanner.Velocity, PublicationCommand.RaisingEdge, (float)trackBarTriggerLevel.Value); } /// <summary> /// Does some work with a drive. /// </summary> void Worker() { #region Preparation // create topology, boot system, find drive Startup(); if (_drive == null) return; EnableDrive(); // Call the next two methods on the GUI thread as required. // The ThreadStart delegate is set to an anonymous method constructed from a lambda expression. BeginInvoke(new ThreadStart(() => { RefreshTrigger(); Acquire(); })); #endregion Preparation #region Work // move forth and back // stop moving when the form is closed while (Visible) { // command moves and wait until the moves are completed _axis.MoveRelative(1).WaitForTermination(); _axis.MoveRelative(-1).WaitForTermination(); // ensure _terminate is fresh Thread.MemoryBarrier(); } #endregion Work #region Tear down DisableDrive(); _acquisition.Dispose(); _topology.Dispose(); #endregion Tear down } #endregion Acquisition demo code // rest of application } }