TAM API – Automation goes .NET

TAM API Logo

Das TAM API ist die Schnittstelle, um Applikationen für ein Triamec Motion-System zu erstellen. Sie 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 API (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 API
Triamec TAM API

Übersicht

  • Transparente Tria-Link-Kommunikation
  • Anwendungen in allen Microsoft®.NET Visual C#
  • 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 100kHz
  • Plugin-Module

Programmieren gegen das TAM API

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

Installation

Das TAM API und einige Beispiel Visual Studio® Solutions werden mit der TAM Software 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 Software 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

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

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

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