Triamec > Produkte > Software > TAM SDK

TAM SDK – Automation goes .NET

Das TAM SDK ist ein Software Development Kit, um Windows-Applikationen für ein Triamec Motion-System zu erstellen. Es basiert auf dem leistungsfähigen Microsoft .NET Framework und erstellt eine Hardware-Abstraktion der am Tria-Link angeschlossenen Geräte. Somit ist die Kommunikation mit den Tria-Link Teilnehmern transparent und sehr einfach.

Das Microsoft .NET Framework ist inhärent nicht echtzeitfähig. Trotzdem können verschiedene Echtzeit-Applikationen problemlos realisiert werden, dank zwei speziellen Fähigkeiten des Triamec Automation and Motion (TAM) Systems: Die Tria-Link Host-Adapter Karten (TL) verfügen über Echtzeit-Tabellen. Vorberechnete Bahnen können damit einfach abgefahren werden. Ferner verfügen die Triamec Geräte über einen frei programmierbaren Echtzeit-Prozessor, womit zeitkritische Arbeiten wie Überwachungen, Achskopplungen (Portale), spezielle Reaktionen (Touch-Downs) usw. realisiert werden können.

Funktionen des TAM API

Das Microsoft .NET Framework-basierte TAM SDK (rot) bietet die Protokoll-Ebene des Tria-Link sowie darüber die TAM System-Ebene. Eine typische Anwendung (blau) setzt auf die Schnittstelle der TAM-Ebene auf und verwendet ausserdem Plugin-Module und Grafik-Komponenten für das Benutzer-Interface (TAM UI). Mit dem Tama-Compiler werden Echtzeit-Programme übersetzt, die auf den Geräten ausgeführt werden.

Triamec TAM SDK
Triamec TAM SDK

Übersicht

  • Transparente Tria-Link-Kommunikation
  • Anwendungen in allen Microsoft®.NET-Sprachen: Visual C#, Visual C++ und Visual Basic
  • Ausführliches Help-System und IntelliSense-Unterstützung
  • Zyklischer Datenaustausch mit Triggern
  • Zustandsbeobachtung und Events
  • Persistenz von Konfigurationen
  • Motion-Befehle (in Anlehnung an PLCopen)
  • Bedienung der Echtzeit-Tabellen in den TL-Adapterkarten
  • Steuerung der Tama-Programme
  • Datenakquisition bis 50kHz
  • Plugin-Module

Programmieren gegen das TAM SDK

Für das Erstellen einer Applikation stehen umfangreiche Schnittstellen zur Verfügung, siehe Beispiele unten. Ein Leitfaden ist hier verfügbar:

Installation

Das TAM SDK und einige Beispiel Visual Studio® Solutions werden mit dem TAM SDK auf dem PC installiert.

TAM Beispielprogramme

In der nachfolgenden Sektion können Sie drei TAM Beispielprogramme anschauen und einen Einblick in die TAM Programmierung erhalten. Diese und weitere Beispiele mit kompletten Solutions werden mit dem TAM SDK installiert und werden im TAM System Explorer mit dem Menü Help | Developer Samples lokalisiert.

Beispiel 1: Hello World: System aufsetzen, Bedienung Drive und Achsen, Move
Beispiel 2: GearUp: Realisierung eines elektronischen Getriebes mit Tama.
Beispiel 3: Acquisition: System aufsetzen, Bedienung Drive, Signal aufzeichnen

  • Hello World
  • GearUp
  • Acquisition

Beispielprogramm Hello World

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   
          
	}
}

Beispielprogramm GearUp

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   

	}
}

Beispielprogramm Acquisition

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   
          
	}
}