Benutzer mit den meisten Antworten
I/O Modul, SPS ähnlich ansteuern

Frage
-
Hallo,ich suche ein gutes Beispiel mit einem I/O Modul, SPS, 12V 25 Ausgänge und 25 Eingänge
Diese sollen mir zuverlässig bei Zustandsänderungen einen Event auslösen, ähnlich einem Button Click.
Stichworte - Threading? BackgroundThread?
Hat jemand Erfahrung? Wie macht man das geschickt?Ich denke Threading Probleme evtl.?10 ms Reaktionszeit würden ausreichen.
Viele Grüße Ulrich
Antworten
-
Hallo Ulrich,
ich habe eine Klasse Eingang definiert:
public enum Schaltpunkt { Low, High } public class Eingang : ModelBase.ModelBase { public event EventHandler EingangEvent; #region Fields private bool gesetzt;
#endregion #region Properties public int Nummer { get; private set; } public int Eingangswert { get; private set; } public string Bezeichnung { get; private set; } public Schaltpunkt Schaltreaktion { get; private set; } public DateTime Zeitstempel { get; set; } public int Wartezeit { get; set; } public bool Gesetzt { get { return gesetzt; } set { gesetzt = value; // Prüfen ob Wartezeit abgelaufen if (Zeitstempel.AddMilliseconds(Wartezeit) < DateTime.Now) { if (value) { Schaltzyklen++; Zeitstempel = DateTime.Now; EingangFired(); OnChanged("Median"); } } OnChanged(string.Empty); } } #endregion public Eingang() { } public Eingang(int Nummer, string Bezeichnung, int StationID, int AnlageID, Schaltpunkt Schaltreaktion = Schaltpunkt.High, int MinimalGesetztZeit = 50, int Wartezeit = 5000, bool Endpunkt = false) { this.Nummer = Nummer; this.StationID = StationID; this.AnlageID = AnlageID; this.Eingangswert = (int)Math.Pow(2, Nummer); this.Bezeichnung = Bezeichnung; this.Schaltreaktion = Schaltreaktion; this.MinimalGesetztZeit = MinimalGesetztZeit; this.Wartezeit = Wartezeit; this.Endpunkt = Endpunkt; } void TaktZeitstempel_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset) { OnChanged(); } } public void EingangFired() { if (EingangEvent != null) { EingangEvent(this, new EventArgs()); } } }
Und eine weitere Klasse für die EingangsCollection:
public class EingangsCollection : ModelBase.ModelBase { public enum QuancomCards { USBOPTOREL8 = (int)qlib.qlib32.USBOPTOREL8, USBOPTOREL16 = (int)qlib.qlib32.USBOPTOREL16, USBOPTOREL32 = (int)qlib.qlib32.USBOPTOREL32 } public ObservableCollection<Model.Eingang> Eingänge { get; set; } public Timer AktualisierungsTimer { get; private set; } private bool verbindung; public bool Verbindung { get { return verbindung; } set { verbindung = value; OnChanged(); } } public EingangsCollection() { Eingänge = new ObservableCollection<Model.Eingang>(); AktualisierungsTimer = new Timer(25); AktualisierungsTimer.Elapsed += AktualisierungsTimer_Elapsed; AktualisierungsTimer.Start(); } async void AktualisierungsTimer_Elapsed(object sender, ElapsedEventArgs e) { await System.Threading.Tasks.Task.Factory.StartNew(() => { var read = ReadInputs(); SetInputs(read); }); } private void SetInputs(uint QuancomValue) { foreach (var eingang in Eingänge) { eingang.Gesetzt = IsBitSet(QuancomValue, eingang.Nummer); } } private bool IsBitSet(uint n, int position) { return (n & (1 << position)) != 0; } public uint ReadInputs() { uint result = 0; try { uint handler = qlib.qlib32.QAPIExtOpenCard((uint)QuancomCards.USBOPTOREL8, (uint)0); Verbindung = (handler != 0); if (handler != 0) { qlib.CARDDATAS lpcd = new qlib.CARDDATAS(); result = qlib.qlib32.QAPIExtReadDI32(handler, 0, 0); qlib.qlib32.QAPIExtCloseCard(handler); } } catch (Exception) { } return result; } }
In der CollectionKlasse wird alle 25ms ein Wert der Quancom Box abgerufen und anschließend mittels Bit-Shifting geprüft welcher Eingang geschaltet ist. Die Box liefert den Wert 1, wenn Eingang 1 geschaltet ist, 2 wenn Eingang 2 geschaltet und 3 wenn Eingang 1 und 2 geschaltet ist usw.. Daher muss man anhand des Werts prüfen welche Eingänge nun geschaltet sind und welche wiederum nicht.
- Als Antwort markiert Ulrich Stippe Mittwoch, 27. Januar 2016 17:10
Alle Antworten
-
Hallo Ulrich,
ich arbeite im Moment mit dem USB-IO Controller von der Firma Quancom.
Deine Anforderungen sollten Hardwaremäßig damit realisierbar sein. Für die Erfassung arbeite ich mit mit mehreren Threads und reagiere auf entsprechende Signaländerungen. Wichtig ist hierbei zu beachten, dass bei einer so "kleinen" Reaktionszeit, manchmal Events mehrfach ausgelöst werden ohne dass man dies möchte. Ich habe dann immer eine Wartezeit pro Eingang hinterlegt.
-
Hallo,
ich habe vor einiger Zeit sehr viel mit Etikettierern + Applikatoren gearbeitet. Da diese in einem Maschinenumfeld sind, war auch immer eine Kommunikation mit dieser (und die Ansteuerung des Applikators) notwendig.
Angefangen hat das mit einer digitalen I/O Karte der Fa. Meilhaus, diese löst auch entsprechende Events aus. Letztendlich war das für mich aber nicht sehr überzeugen, das es aus meiner Sich Bastelkram ist und man einen Tower-PC benötigte.
Ich bin dann umgestiegen auf Buskoppler der Fa. Wago, Beckhoff, Phoenix, Adam... mit Ethernet- Schnittstelle. Ich habe mir dazu eine kleine Modbus-TCP - Lib geschrieben, dadurch bin ich relativ herstellerunabhängig.
Die SPS-Funktionalität läuft dann natürlich in einem BackgroundWorker. Die Signalwechsel habe ich zum Beispiel auf diese Art erfasst (ups, noch VB.NET):
Private bPosEdge2 As Boolean = True Private bHvPosEdge2 As Boolean Private bNegEdge2 As Boolean = False Private bHvNegEdge2 As Boolean Private bDorOpend2 As Boolean Private Sub ReadyToQuitt2() Try bPosEdge2 = DigInputsA(AdrIn.DI_03_TUERKREIS) And Not bHvPosEdge2 ' Positive Flanke bei Signalwechsel erzeugen bHvPosEdge2 = DigInputsA(AdrIn.DI_03_TUERKREIS) bNegEdge2 = Not DigInputsA(AdrIn.DI_03_TUERKREIS) And Not bHvNegEdge2 ' Negative Flanke bei Signalwechsel erzeugen bHvNegEdge2 = Not DigInputsA(AdrIn.DI_03_TUERKREIS) ...
Catch ex As Exception
... End Try End Sub
Wenn relativ viel reine SPS-Funktionalität anfällt nehme ich mittlerweile Buscontroller (Beckhoff), diese bieten eine kleine SPS, die diese Dinge viel schneller und effizienter erledigt.
Gruß
Stefan
-
Ich bin dann umgestiegen auf Buskoppler der Fa. Wago, Beckhoff, Phoenix, Adam... mit Ethernet- Schnittstelle. Ich habe mir dazu eine kleine Modbus-TCP - Lib geschrieben, dadurch bin ich relativ herstellerunabhängig.
Hallo Stefan,
ist ja sehr interessant. Hast Du da dann einen eigenen Treiber geschrieben, wenn Du die SPS en wählen kannst?
Könntest evtl. etwas mehr offen legen.
Danke im Voraus.
Viele Grüße Ulrich
-
Die Quancom Box gibt es auch als TCPIP Variante :-)
Für Bastelkram bin ich auch nicht zu begeistern, aber bei uns steht in jedem Fertigungssegment mindestens ein PC herum. Teilweise "nur" für SAP Rückmeldung. Ergo wird dieser 99,9% am Tag nicht benutzt. Da sehe ich die Anbindung an eine normale IO-Karte nicht unbedingt als Bastelkram.
@Ulrich: Was meinst du mit Treiber? Die meisten Komponenten verfügen in der Regel über standardisierte Schnittstellen welche du entsprechend ansprechen kannst (RS232, USB, TCPIP,...). Alles was du umsetzen musst ist die Anbindung an diese Schnittstelle und wie du die Informationen in deiner Anwendung verarbeitest.
-
Hallo Ulrich,
Quancomm liefert eine eigene Library mit, die man im Projekt, um die Hardware anzusprechen, einsetzen kann. Es gibt auch SDK von Quancomm. Man braucht keinen eigenen Treiber zu entwickeln. Ich denke, dass es fast bei allen Hardware-Liieferanten so ist.
Grüße
-
@Ulrich: Was meinst du mit Treiber? Die meisten Komponenten verfügen in der Regel über standardisierte Schnittstellen welche du entsprechend ansprechen kannst (RS232, USB, TCPIP,...). Alles was du umsetzen musst ist die Anbindung an diese Schnittstelle und wie du die Informationen in deiner Anwendung verarbeitest.
ja, ich meine mit Treiber die Auswertung.
Ausgang setzen, Eingang abfragen ist ja nur statisch, sicher nicht das Problem.
Meist will man dann ja einen Workflow programmieren, sprich ich muss es eventbasiert gestalten.
Da gilt es halt die Zustandsänderungen zur Laufzeit zu erfragen.
Welche Umsetzung bietet sich hier an?
Viele Grüße Ulrich -
Hallo,
mit Bastelkram meinte ich die Sub-D Lösung der Meilhaus-Karte.
Bei ModbusTCP brauchst du keinen Treiber. Ich habe hier einiges gefunde:
und hier:
Das ist die Klasse aus einem meiner Projekte.
using System; using System.Collections; using System.Text; using System.IO; using System.Net; using System.Net.Sockets; using System.Threading; namespace ModbusTCP { /// <summary> /// Modbus TCP common driver class. This class implements a modbus TCP master driver. /// It supports the following commands: /// /// Read coils /// Read discrete inputs /// Write single coil /// Write multiple cooils /// Read holding register /// Read input register /// Write single register /// Write multiple register /// /// All commands can be sent in synchronous or asynchronous mode. If a value is accessed /// in synchronous mode the program will stop and wait for slave to response. If the /// slave didn't answer within a specified time a timeout exception is called. /// The class uses multi threading for both synchronous and asynchronous access. For /// the communication two lines are created. This is necessary because the synchronous /// thread has to wait for a previous command to finish. /// /// </summary> public class Master { // ------------------------------------------------------------------------ // Constants for access private const byte fctReadCoil = 1; private const byte fctReadDiscreteInputs = 2; private const byte fctReadHoldingRegister = 3; private const byte fctReadInputRegister = 4; private const byte fctWriteSingleCoil = 5; private const byte fctWriteSingleRegister = 6; private const byte fctWriteMultipleCoils = 15; private const byte fctWriteMultipleRegister = 16; private const byte fctReadWriteMultipleRegister = 23; /// <summary>Constant for exception illegal function.</summary> public const byte excIllegalFunction = 1; /// <summary>Constant for exception illegal data address.</summary> public const byte excIllegalDataAdr = 2; /// <summary>Constant for exception illegal data value.</summary> public const byte excIllegalDataVal = 3; /// <summary>Constant for exception slave device failure.</summary> public const byte excSlaveDeviceFailure = 4; /// <summary>Constant for exception acknowledge.</summary> public const byte excAck = 5; /// <summary>Constant for exception slave is busy/booting up.</summary> public const byte excSlaveIsBusy = 6; /// <summary>Constant for exception gate path unavailable.</summary> public const byte excGatePathUnavailable = 10; /// <summary>Constant for exception not connected.</summary> public const byte excExceptionNotConnected = 253; /// <summary>Constant for exception connection lost.</summary> public const byte excExceptionConnectionLost = 254; /// <summary>Constant for exception response timeout.</summary> public const byte excExceptionTimeout = 255; /// <summary>Constant for exception wrong offset.</summary> private const byte excExceptionOffset = 128; /// <summary>Constant for exception send failt.</summary> private const byte excSendFailt = 100; // ------------------------------------------------------------------------ // Private declarations private static ushort _timeout = 500; private static ushort _refresh = 10; private static bool _connected = false; private Socket tcpAsyCl; private byte[] tcpAsyClBuffer = new byte[2048]; private Socket tcpSynCl; private byte[] tcpSynClBuffer = new byte[2048]; // ------------------------------------------------------------------------ /// <summary>Response data event. This event is called when new data arrives</summary> public delegate void ResponseData(ushort id, byte function, byte[] data); /// <summary>Response data event. This event is called when new data arrives</summary> public event ResponseData OnResponseData; /// <summary>Exception data event. This event is called when the data is incorrect</summary> public delegate void ExceptionData(ushort id, byte function, byte exception); /// <summary>Exception data event. This event is called when the data is incorrect</summary> public event ExceptionData OnException; // ------------------------------------------------------------------------ /// <summary>Response timeout. If the slave didn't answers within in this time an exception is called.</summary> /// <value>The default value is 500ms.</value> public ushort timeout { get { return _timeout; } set { _timeout = value; } } // ------------------------------------------------------------------------ /// <summary>Refresh timer for slave answer. The class is polling for answer every X ms.</summary> /// <value>The default value is 10ms.</value> public ushort refresh { get { return _refresh; } set { _refresh = value; } } // ------------------------------------------------------------------------ /// <summary>Shows if a connection is active.</summary> public bool connected { get { return _connected; } } // ------------------------------------------------------------------------ /// <summary>Create master instance without parameters.</summary> public Master() { } // ------------------------------------------------------------------------ /// <summary>Create master instance with parameters.</summary> /// <param name="ip">IP adress of modbus slave.</param> /// <param name="port">Port number of modbus slave. Usually port 502 is used.</param> public Master(string ip, ushort port) { connect(ip, port); } // ------------------------------------------------------------------------ /// <summary>Start connection to slave.</summary> /// <param name="ip">IP adress of modbus slave.</param> /// <param name="port">Port number of modbus slave. Usually port 502 is used.</param> public void connect(string ip, ushort port) { try { IPAddress _ip; if (IPAddress.TryParse(ip, out _ip) == false) { IPHostEntry hst = Dns.GetHostEntry(ip); ip = hst.AddressList[0].ToString(); } // ---------------------------------------------------------------- // Connect asynchronous client tcpAsyCl = new Socket(IPAddress.Parse(ip).AddressFamily, SocketType.Stream, ProtocolType.Tcp); tcpAsyCl.Connect(new IPEndPoint(IPAddress.Parse(ip), port)); tcpAsyCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, _timeout); tcpAsyCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, _timeout); tcpAsyCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1); // ---------------------------------------------------------------- // Connect synchronous client tcpSynCl = new Socket(IPAddress.Parse(ip).AddressFamily, SocketType.Stream, ProtocolType.Tcp); tcpSynCl.Connect(new IPEndPoint(IPAddress.Parse(ip), port)); tcpSynCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, _timeout); tcpSynCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, _timeout); tcpSynCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1); _connected = true; } catch (System.IO.IOException error) { _connected = false; throw (error); } } // ------------------------------------------------------------------------ /// <summary>Stop connection to slave.</summary> public void disconnect() { Dispose(); } // ------------------------------------------------------------------------ /// <summary>Destroy master instance.</summary> ~Master() { Dispose(); } // ------------------------------------------------------------------------ /// <summary>Destroy master instance</summary> public void Dispose() { if (tcpAsyCl != null) { if (tcpAsyCl.Connected) { try { tcpAsyCl.Shutdown(SocketShutdown.Both); } catch { } tcpAsyCl.Close(); } tcpAsyCl = null; } if (tcpSynCl != null) { if (tcpSynCl.Connected) { try { tcpSynCl.Shutdown(SocketShutdown.Both); } catch { } tcpSynCl.Close(); } tcpSynCl = null; } } internal void CallException(ushort id, byte function, byte exception) { if ((tcpAsyCl == null) || (tcpSynCl == null)) return; if (exception == excExceptionConnectionLost) { tcpSynCl = null; tcpAsyCl = null; } if (OnException != null) OnException(id, function, exception); } // ------------------------------------------------------------------------ /// <summary>Read coils from slave asynchronous. The result is given in the response function.</summary> /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param> /// <param name="startAddress">Address from where the data read begins.</param> /// <param name="numInputs">Length of data.</param> public void ReadCoils(ushort id, ushort startAddress, ushort numInputs) { WriteAsyncData(CreateReadHeader(id, startAddress, numInputs, fctReadCoil), id); } // ------------------------------------------------------------------------ /// <summary>Read coils from slave synchronous.</summary> /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param> /// <param name="startAddress">Address from where the data read begins.</param> /// <param name="numInputs">Length of data.</param> /// <param name="values">Contains the result of function.</param> public void ReadCoils(ushort id, ushort startAddress, ushort numInputs, ref byte[] values) { values = WriteSyncData(CreateReadHeader(id, startAddress, numInputs, fctReadCoil), id); } // ------------------------------------------------------------------------ /// <summary>Read discrete inputs from slave asynchronous. The result is given in the response function.</summary> /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param> /// <param name="startAddress">Address from where the data read begins.</param> /// <param name="numInputs">Length of data.</param> public void ReadDiscreteInputs(ushort id, ushort startAddress, ushort numInputs) { WriteAsyncData(CreateReadHeader(id, startAddress, numInputs, fctReadDiscreteInputs), id); } // ------------------------------------------------------------------------ /// <summary>Read discrete inputs from slave synchronous.</summary> /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param> /// <param name="startAddress">Address from where the data read begins.</param> /// <param name="numInputs">Length of data.</param> /// <param name="values">Contains the result of function.</param> public void ReadDiscreteInputs(ushort id, ushort startAddress, ushort numInputs, ref byte[] values) { values = WriteSyncData(CreateReadHeader(id, startAddress, numInputs, fctReadDiscreteInputs), id); } // ------------------------------------------------------------------------ /// <summary>Read holding registers from slave asynchronous. The result is given in the response function.</summary> /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param> /// <param name="startAddress">Address from where the data read begins.</param> /// <param name="numInputs">Length of data.</param> public void ReadHoldingRegister(ushort id, ushort startAddress, ushort numInputs) { WriteAsyncData(CreateReadHeader(id, startAddress, numInputs, fctReadHoldingRegister), id); } // ------------------------------------------------------------------------ /// <summary>Read holding registers from slave synchronous.</summary> /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param> /// <param name="startAddress">Address from where the data read begins.</param> /// <param name="numInputs">Length of data.</param> /// <param name="values">Contains the result of function.</param> public void ReadHoldingRegister(ushort id, ushort startAddress, ushort numInputs, ref byte[] values) { values = WriteSyncData(CreateReadHeader(id, startAddress, numInputs, fctReadHoldingRegister), id); } // ------------------------------------------------------------------------ /// <summary>Read input registers from slave asynchronous. The result is given in the response function.</summary> /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param> /// <param name="startAddress">Address from where the data read begins.</param> /// <param name="numInputs">Length of data.</param> public void ReadInputRegister(ushort id, ushort startAddress, ushort numInputs) { WriteAsyncData(CreateReadHeader(id, startAddress, numInputs, fctReadInputRegister), id); } // ------------------------------------------------------------------------ /// <summary>Read input registers from slave synchronous.</summary> /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param> /// <param name="startAddress">Address from where the data read begins.</param> /// <param name="numInputs">Length of data.</param> /// <param name="values">Contains the result of function.</param> public void ReadInputRegister(ushort id, ushort startAddress, ushort numInputs, ref byte[] values) { values = WriteSyncData(CreateReadHeader(id, startAddress, numInputs, fctReadInputRegister), id); } // ------------------------------------------------------------------------ /// <summary>Write single coil in slave asynchronous. The result is given in the response function.</summary> /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param> /// <param name="startAddress">Address from where the data read begins.</param> /// <param name="OnOff">Specifys if the coil should be switched on or off.</param> public void WriteSingleCoils(ushort id, ushort startAddress, bool OnOff) { byte[] data; data = CreateWriteHeader(id, startAddress, 1, 1, fctWriteSingleCoil); if (OnOff == true) data[10] = 255; else data[10] = 0; WriteAsyncData(data, id); } // ------------------------------------------------------------------------ /// <summary>Write single coil in slave synchronous.</summary> /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param> /// <param name="startAddress">Address from where the data read begins.</param> /// <param name="OnOff">Specifys if the coil should be switched on or off.</param> /// <param name="result">Contains the result of the synchronous write.</param> public void WriteSingleCoils(ushort id, ushort startAddress, bool OnOff, ref byte[] result) { byte[] data; data = CreateWriteHeader(id, startAddress, 1, 1, fctWriteSingleCoil); if (OnOff == true) data[10] = 255; else data[10] = 0; result = WriteSyncData(data, id); } // ------------------------------------------------------------------------ /// <summary>Write multiple coils in slave asynchronous. The result is given in the response function.</summary> /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param> /// <param name="startAddress">Address from where the data read begins.</param> /// <param name="numBits">Specifys number of bits.</param> /// <param name="values">Contains the bit information in byte format.</param> public void WriteMultipleCoils(ushort id, ushort startAddress, ushort numBits, byte[] values) { byte numBytes = Convert.ToByte(values.Length); byte[] data; data = CreateWriteHeader(id, startAddress, numBits, (byte)(numBytes + 2), fctWriteMultipleCoils); Array.Copy(values, 0, data, 13, numBytes); WriteAsyncData(data, id); } // ------------------------------------------------------------------------ /// <summary>Write multiple coils in slave synchronous.</summary> /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param> /// <param name="startAddress">Address from where the data read begins.</param> /// <param name="numBits">Specifys number of bits.</param> /// <param name="values">Contains the bit information in byte format.</param> /// <param name="result">Contains the result of the synchronous write.</param> public void WriteMultipleCoils(ushort id, ushort startAddress, ushort numBits, byte[] values, ref byte[] result) { byte numBytes = Convert.ToByte(values.Length); byte[] data; data = CreateWriteHeader(id, startAddress, numBits, (byte)(numBytes + 2), fctWriteMultipleCoils); Array.Copy(values, 0, data, 13, numBytes); result = WriteSyncData(data, id); } // ------------------------------------------------------------------------ /// <summary>Write single register in slave asynchronous. The result is given in the response function.</summary> /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param> /// <param name="startAddress">Address to where the data is written.</param> /// <param name="values">Contains the register information.</param> public void WriteSingleRegister(ushort id, ushort startAddress, byte[] values) { byte[] data; data = CreateWriteHeader(id, startAddress, 1, 1, fctWriteSingleRegister); data[10] = values[0]; data[11] = values[1]; WriteAsyncData(data, id); } // ------------------------------------------------------------------------ /// <summary>Write single register in slave synchronous.</summary> /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param> /// <param name="startAddress">Address to where the data is written.</param> /// <param name="values">Contains the register information.</param> /// <param name="result">Contains the result of the synchronous write.</param> public void WriteSingleRegister(ushort id, ushort startAddress, byte[] values, ref byte[] result) { byte[] data; data = CreateWriteHeader(id, startAddress, 1, 1, fctWriteSingleRegister); data[10] = values[0]; data[11] = values[1]; result = WriteSyncData(data, id); } // ------------------------------------------------------------------------ /// <summary>Write multiple registers in slave asynchronous. The result is given in the response function.</summary> /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param> /// <param name="startAddress">Address to where the data is written.</param> /// <param name="values">Contains the register information.</param> public void WriteMultipleRegister(ushort id, ushort startAddress, byte[] values) { ushort numBytes = Convert.ToUInt16(values.Length); if (numBytes % 2 > 0) numBytes++; byte[] data; data = CreateWriteHeader(id, startAddress, Convert.ToUInt16(numBytes / 2), Convert.ToUInt16(numBytes + 2), fctWriteMultipleRegister); Array.Copy(values, 0, data, 13, values.Length); WriteAsyncData(data, id); } // ------------------------------------------------------------------------ /// <summary>Write multiple registers in slave synchronous.</summary> /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param> /// <param name="startAddress">Address to where the data is written.</param> /// <param name="values">Contains the register information.</param> /// <param name="result">Contains the result of the synchronous write.</param> public void WriteMultipleRegister(ushort id, ushort startAddress, byte[] values, ref byte[] result) { ushort numBytes = Convert.ToUInt16(values.Length); if (numBytes % 2 > 0) numBytes++; byte[] data; data = CreateWriteHeader(id, startAddress, Convert.ToUInt16(numBytes / 2), Convert.ToUInt16(numBytes + 2), fctWriteMultipleRegister); Array.Copy(values, 0, data, 13, values.Length); result = WriteSyncData(data, id); } // ------------------------------------------------------------------------ /// <summary>Read/Write multiple registers in slave asynchronous. The result is given in the response function.</summary> /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param> /// <param name="startReadAddress">Address from where the data read begins.</param> /// <param name="numInputs">Length of data.</param> /// <param name="startWriteAddress">Address to where the data is written.</param> /// <param name="values">Contains the register information.</param> public void ReadWriteMultipleRegister(ushort id, ushort startReadAddress, ushort numInputs, ushort startWriteAddress, byte[] values) { ushort numBytes = Convert.ToUInt16(values.Length); if (numBytes % 2 > 0) numBytes++; byte[] data; data = CreateReadWriteHeader(id, startReadAddress, numInputs, startWriteAddress, Convert.ToUInt16(numBytes / 2)); Array.Copy(values, 0, data, 17, values.Length); WriteAsyncData(data, id); } // ------------------------------------------------------------------------ /// <summary>Read/Write multiple registers in slave synchronous. The result is given in the response function.</summary> /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param> /// <param name="startReadAddress">Address from where the data read begins.</param> /// <param name="numInputs">Length of data.</param> /// <param name="startWriteAddress">Address to where the data is written.</param> /// <param name="values">Contains the register information.</param> /// <param name="result">Contains the result of the synchronous command.</param> public void ReadWriteMultipleRegister(ushort id, ushort startReadAddress, ushort numInputs, ushort startWriteAddress, byte[] values, ref byte[] result) { ushort numBytes = Convert.ToUInt16(values.Length); if (numBytes % 2 > 0) numBytes++; byte[] data; data = CreateReadWriteHeader(id, startReadAddress, numInputs, startWriteAddress, Convert.ToUInt16(numBytes / 2)); Array.Copy(values, 0, data, 17, values.Length); result = WriteSyncData(data, id); } // ------------------------------------------------------------------------ // Create modbus header for read action private byte[] CreateReadHeader(ushort id, ushort startAddress, ushort length, byte function) { byte[] data = new byte[12]; byte[] _id = BitConverter.GetBytes((short)id); data[0] = _id[0]; // Slave id high byte data[1] = _id[1]; // Slave id low byte data[5] = 6; // Message size data[6] = 0; // Slave address data[7] = function; // Function code byte[] _adr = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)startAddress)); data[8] = _adr[0]; // Start address data[9] = _adr[1]; // Start address byte[] _length = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)length)); data[10] = _length[0]; // Number of data to read data[11] = _length[1]; // Number of data to read return data; } // ------------------------------------------------------------------------ // Create modbus header for write action private byte[] CreateWriteHeader(ushort id, ushort startAddress, ushort numData, ushort numBytes, byte function) { byte[] data = new byte[numBytes + 11]; byte[] _id = BitConverter.GetBytes((short)id); data[0] = _id[0]; // Slave id high byte data[1] = _id[1]; // Slave id low byte+ byte[] _size = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)(5 + numBytes))); data[4] = _size[0]; // Complete message size in bytes data[5] = _size[1]; // Complete message size in bytes data[6] = 0; // Slave address data[7] = function; // Function code byte[] _adr = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)startAddress)); data[8] = _adr[0]; // Start address data[9] = _adr[1]; // Start address if (function >= fctWriteMultipleCoils) { byte[] _cnt = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)numData)); data[10] = _cnt[0]; // Number of bytes data[11] = _cnt[1]; // Number of bytes data[12] = (byte)(numBytes - 2); } return data; } // ------------------------------------------------------------------------ // Create modbus header for write action private byte[] CreateReadWriteHeader(ushort id, ushort startReadAddress, ushort numRead, ushort startWriteAddress, ushort numWrite) { byte[] data = new byte[numWrite * 2 + 17]; byte[] _id = BitConverter.GetBytes((short)id); data[0] = _id[0]; // Slave id high byte data[1] = _id[1]; // Slave id low byte+ byte[] _size = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)(11 + numWrite * 2))); data[4] = _size[0]; // Complete message size in bytes data[5] = _size[1]; // Complete message size in bytes data[6] = 0; // Slave address data[7] = fctReadWriteMultipleRegister; // Function code byte[] _adr_read = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)startReadAddress)); data[8] = _adr_read[0]; // Start read address data[9] = _adr_read[1]; // Start read address byte[] _cnt_read = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)numRead)); data[10] = _cnt_read[0]; // Number of bytes to read data[11] = _cnt_read[1]; // Number of bytes to read byte[] _adr_write = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)startWriteAddress)); data[12] = _adr_write[0]; // Start write address data[13] = _adr_write[1]; // Start write address byte[] _cnt_write = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)numWrite)); data[14] = _cnt_write[0]; // Number of bytes to write data[15] = _cnt_write[1]; // Number of bytes to write data[16] = (byte)(numWrite * 2); return data; } // ------------------------------------------------------------------------ // Write asynchronous data private void WriteAsyncData(byte[] write_data, ushort id) { if ((tcpAsyCl != null) && (tcpAsyCl.Connected)) { try { tcpAsyCl.BeginSend(write_data, 0, write_data.Length, SocketFlags.None, new AsyncCallback(OnSend), null); tcpAsyCl.BeginReceive(tcpAsyClBuffer, 0, tcpAsyClBuffer.Length, SocketFlags.None, new AsyncCallback(OnReceive), tcpAsyCl); } catch (SystemException) { CallException(id, write_data[7], excExceptionConnectionLost); } } else CallException(id, write_data[7], excExceptionConnectionLost); } // ------------------------------------------------------------------------ // Write asynchronous data acknowledge private void OnSend(System.IAsyncResult result) { if (result.IsCompleted == false) CallException(0xFFFF, 0xFF, excSendFailt); } // ------------------------------------------------------------------------ // Write asynchronous data response private void OnReceive(System.IAsyncResult result) { if (result.IsCompleted == false) CallException(0xFF, 0xFF, excExceptionConnectionLost); ushort id = BitConverter.ToUInt16(tcpAsyClBuffer, 0); ; byte function = tcpAsyClBuffer[7]; byte[] data; // ------------------------------------------------------------ // Write response data if ((function >= fctWriteSingleCoil) && (function != fctReadWriteMultipleRegister)) { data = new byte[2]; Array.Copy(tcpAsyClBuffer, 10, data, 0, 2); } // ------------------------------------------------------------ // Read response data else { data = new byte[tcpAsyClBuffer[8]]; Array.Copy(tcpAsyClBuffer, 9, data, 0, tcpAsyClBuffer[8]); } // ------------------------------------------------------------ // Response data is slave exception if (function > excExceptionOffset) { function -= excExceptionOffset; CallException(id, function, tcpAsyClBuffer[8]); } // ------------------------------------------------------------ // Response data is regular data else if (OnResponseData != null) OnResponseData(id, function, data); } // ------------------------------------------------------------------------ // Write data and and wait for response private byte[] WriteSyncData(byte[] write_data, ushort id) { if (tcpSynCl.Connected) { try { tcpSynCl.Send(write_data, 0, write_data.Length, SocketFlags.None); int result = tcpSynCl.Receive(tcpSynClBuffer, 0, tcpSynClBuffer.Length, SocketFlags.None); byte function = tcpSynClBuffer[7]; byte[] data; if (result == 0) CallException(id, write_data[7], excExceptionConnectionLost); // ------------------------------------------------------------ // Response data is slave exception if (function > excExceptionOffset) { function -= excExceptionOffset; CallException(id, function, tcpSynClBuffer[8]); return null; } // ------------------------------------------------------------ // Write response data else if ((function >= fctWriteSingleCoil) && (function != fctReadWriteMultipleRegister)) { data = new byte[2]; Array.Copy(tcpSynClBuffer, 10, data, 0, 2); } // ------------------------------------------------------------ // Read response data else { data = new byte[tcpSynClBuffer[8]]; Array.Copy(tcpSynClBuffer, 9, data, 0, tcpSynClBuffer[8]); } return data; } catch (SystemException) { CallException(id, write_data[7], excExceptionConnectionLost); } } else CallException(id, write_data[7], excExceptionConnectionLost); return null; } } }
Damit hast du erst mal die reine Kommunikation zu vielen SPS und auch Buskopplern.
Letztendlich kommt es aber auch immer auf die Anwendung an. Ich versuche mittlerweile sämtliches I/O Handling in einer SPS zu lassen, die kann das viel besser. Vor kurzem habe ich auch mal eine Soft-SPS eingesetzt, auch nicht schlecht.
Vielleicht erzählst du uns was über deine Anwendung...
Gruß
- Bearbeitet Stefan Krömer Montag, 11. Januar 2016 18:39 Da war noch was...
-
Damit hast du erst mal die reine Kommunikation zu vielen SPS und auch Buskopplern.
Letztendlich kommt es aber auch immer auf die Anwendung an. Ich versuche mittlerweile sämtliches I/O Handling in einer SPS zu lassen, die kann das viel besser. Vor kurzem habe ich auch mal eine Soft-SPS eingesetzt, auch nicht schlecht.
Vielleicht erzählst du uns was über deine Anwendung...
Hallo Stefan,ich verwende ein Achssystem x und y Richtung.
Die Achsen fahren mit einem positiven Ausgang, Flanke.
Ist die Position erreicht kommt ein Eingang, sprich positive Flanke, Zustandswechsel.
Dabei sind noch diverse Sensorik drin, die den Zustandsautomaten abbildet.Kurzum, ich muss die Eingänge abpollen und dann einen Event, ähnlich einem Mausklick auslösen.Wenn Du hier eine elegante Lösung weißt.
Nur Abfragen, setzen reicht nicht aus.Hinzu kommt die NotAus Geschichte.Viele Grüße Ulrich -
Hallo Ulrich,
also wenn du die Eingänge pollst, hast du ja eine Art von Programmzyklus (ähnlich SPS). Das läuft in einem eigene Thread/Prozess/Backgroundworker. Und dort kannst du dir die entsprechenden Flanken erzeugen, welche dann ein Event erzeugen:
Private bPosEdge2 As Boolean = True Private bHvPosEdge2 As Boolean Private bNegEdge2 As Boolean = False Private bHvNegEdge2 As Boolean Private bDorOpend2 As Boolean Private Sub ReadyToQuitt2() Try bPosEdge2 = DigInputsA(AdrIn.DI_03_TUERKREIS) And Not bHvPosEdge2 ' Positive Flanke bei Signalwechsel erzeugen bHvPosEdge2 = DigInputsA(AdrIn.DI_03_TUERKREIS) bNegEdge2 = Not DigInputsA(AdrIn.DI_03_TUERKREIS) And Not bHvNegEdge2 ' Negative Flanke bei Signalwechsel erzeugen bHvNegEdge2 = Not DigInputsA(AdrIn.DI_03_TUERKREIS) if bPosEdge2 then {DEIN EVENT/SUB/FUNCTION} end if Catch ex As Exception ... End Try End Sub
Wenn du allerdings die Achssteuerung im PC machen möchtest und deine Anwendung zeitkritisch ist, wäre eventuell ein OPC-Server das richtige. Bei ModbusTCP ist es eh schwer die 10 ms zu erreichen. Das mit der NotAus Geschichte habe ich jetzt nicht ganz verstanden. Das kann im PC doch nur eine Melde-Aufgabe haben und maximal die Sicherheitsschaltgeräte zurücksetzen.
-
Hallo Ulrich,
also wenn du die Eingänge pollst, hast du ja eine Art von Programmzyklus (ähnlich SPS). Das läuft in einem eigene Thread/Prozess/Backgroundworker. Und dort kannst du dir die entsprechenden Flanken erzeugen, welche dann ein Event erzeugen:
Private bPosEdge2 As Boolean = True Private bHvPosEdge2 As Boolean Private bNegEdge2 As Boolean = False Private bHvNegEdge2 As Boolean Private bDorOpend2 As Boolean Private Sub ReadyToQuitt2() Try bPosEdge2 = DigInputsA(AdrIn.DI_03_TUERKREIS) And Not bHvPosEdge2 ' Positive Flanke bei Signalwechsel erzeugen bHvPosEdge2 = DigInputsA(AdrIn.DI_03_TUERKREIS) bNegEdge2 = Not DigInputsA(AdrIn.DI_03_TUERKREIS) And Not bHvNegEdge2 ' Negative Flanke bei Signalwechsel erzeugen bHvNegEdge2 = Not DigInputsA(AdrIn.DI_03_TUERKREIS) if bPosEdge2 then {DEIN EVENT/SUB/FUNCTION} end if Catch ex As Exception ... End Try End Sub
Wenn du allerdings die Achssteuerung im PC machen möchtest und deine Anwendung zeitkritisch ist, wäre eventuell ein OPC-Server das richtige. Bei ModbusTCP ist es eh schwer die 10 ms zu erreichen. Das mit der NotAus Geschichte habe ich jetzt nicht ganz verstanden. Das kann im PC doch nur eine Melde-Aufgabe haben und maximal die Sicherheitsschaltgeräte zurücksetzen.
-
Hi,
bei Betätigung des Not-Aus werden sämtliche Antriebe entweder Spannungsfrei geschaltet, die Bremsen hauen rein oder die Antriebe gehen in einen sicheren Halt über (wenn sie über eine entsprechende Sicherheitstechnik verfügen), das ist erstmal Voraussetzung. Das Stillsetzen darf eine normale SPS nicht übernehmen und somit auch kein PC. Das sichere Not-Aus-Schaltgerät meldet den Zustand an die SPS oder PC. Diese/r reagiert mit dem Rücksetzen der Fahrbefehle usw.
Sämtliche Threads/Tasks... laufen weiter, fragen weiter die Eingänge ab...
Das Problem ist immer wieder der Wiederanlauf nach Not-Aus. Die aktuelle Situation erfassen, die Antriebe in eine sichere Position fahren usw.
Und dann geht's weiter.
Ich weiß allerdings nicht wie deine Anlage/Aufgabe aussieht, deshalb ist mir auch nicht ganz klar, wo ich ansetzen soll. Ich komme ursprünglich aus dem Sondermaschinenbau, ich habe in der Elektrokonstruktion Schaltpläne "gemalt", SPS und Roboter programmiert.
Gruß
- Bearbeitet Stefan Krömer Donnerstag, 14. Januar 2016 23:12
-
Beschäftige mich gerade mit was ähnlichem und bin echt froh dass ich auf den Beitrag gestossen bin!
Möchte Modus/TCP / UDP mit Win10IOT machen und stell mir nun die Frage ob das mit C# überhaupt schlau ist oder ob ich doch C++ nehmen muss, würd mir gerne meine eigene "Soft SPS programmieren" aber hab noch nicht genau den richtigen Lösungsansatz!
Wichtig für mich ist natürlich das "Echtzeit Verhalten"
Bin mir nicht so ganz sicher ob ich jetzt nun einen Treiber Programmieren muss oder einen Hintergrund Anwendung,
kann mir jemand von euch helfen?
-
kann mir jemand von euch helfen?
Hallo,
ich weniger. Fange ja auch gerade damit an.
Evtl. eine plattformunabhängige C# Klasse, Assembly.
Also, wo alle Anbieter gehen, Wago, Beckhoff, Bachmann etc.
Oder eben Lieferantenbezogen, evtl. bieten die Hersteller was an. Vielleicht gibt es auch so eine kleine Beispiel App, die jemand kennt.
Grüße Ulrich
-
Bin mir leider nicht ganz sicher wie das mit dem garbage collector unter windows 10 iot auschaut aber ich denke das wenn der Startet das echtzeitverhalten futsch ist, aber weis nicht ganz wie das mit dem win 10 iot ausschaut.
Ich denke dass ich einen AWL interpreter irgenwie hinbekommen werde :-) und dann kann ich ja auf dem rest aufbauen, ModbusTCP und der ganze andre kram ist glaub auch noch mal ganz schön aufwändig,...
Aber als übungsprojekt wärs glaub ich Perfekt :-)
-
Hallo Peter,
eine Soft-SPS mit Hilfe einer .NET-Anwendung ist nicht wirklich praktikabel. Wie du schon geschrieben hast, durch den garbage collector ist Echtzeit nicht wirklich drin. Weiter denke ich, das Modbus nicht der richtige Weg ist, die Peripherie anzusprechen. Meiner Meinung nach muss man da mindestens einen anständigen Treiber programmieren, und die Hardware direkt anzusprechen. Letztendlich ist dafür auch nicht jede Netzwerkkarte geeignet. Um verschiedene Hersteller ansprechen zu können, verwenden die kommerziellen Hersteller entsprechende Konfigurations-Dateien (GSD/GSE).
Ist das ein privates (Hobby) Projekt? Die Aufgabe ist riesig!
Gruß
Stefan -
Die Frage ist letztendlich, wie schnell du die Informationen benötigst! Ich polle alle 25ms durch alle Eingänge durch und verarbeite diese Informationen entsprechend. Bisher noch nie Probleme mit gehabt. Zu einer richtigen Echtzeitanwendung gehört neben der Software auch die entsprechende Hardware. Wenn also dein Netzwerk dafür nicht ausgelegt ist, wirst du auch da an Herausforderungen stoßen.
-
Wenn man ein Steuerungssystem baut, muss man richtig überlegen, welche Echtzeitanforderungen (harte, weiche, feste) gibt es für das System(Reaktionzeit, Datenübertragung, usw.). Für die harten Echtzeitsysteme nimmt man schon eine spezielle Hardware mit einem Echtzeitbetriebssystem oder Firmware.
Grüße
-
Für die Erfassung arbeite ich mit mit mehreren Threads und reagiere auf entsprechende
Signaländerungen. Wichtig ist hierbei zu beachten, dass bei einer so "kleinen" Reaktionszeit,
manchmal Events mehrfach ausgelöst werden ohne dass man dies möchte. Ich habe dann immer
eine Wartezeit pro Eingang hinterlegt......
Die Frage ist letztendlich, wie schnell du die Informationen benötigst! Ich polle alle 25ms durch alle Eingänge durch und verarbeite diese Informationen entsprechend. Bisher noch nie Probleme mit gehabt. Zu einer richtigen Echtzeitanwendung gehört neben der Software auch die entsprechende Hardware. Wenn also dein Netzwerk dafür nicht ausgelegt ist, wirst du auch da an Herausforderungen stoßen.
Die Zeiten würden mir locker reichen.
Die Frage, wie ich schon Stefan stellte, wie bekommst Du die Änderungen mit.
Positive, negative Flanke, evtl. prellen.
Wie hast Du das umgesetzt? Könntest Du das kurz aufzeigen, um die Möglichkeiten zu sehen?
Viele Grüße Ulrich -
Hi,
wie schon erwähnt, als erstes musst du die Eingänge einlesen. Dafür gibt es einige Möglichkeiten, welche aber doch stark hardwareabhängig sind. Ich hatte mich für meine Projekte für ModbusTCP entschieden. Bei mir kam es allerdings nicht so sehr auf kurze Reaktionszeit und nicht auf Echtzeitverhalten an.
Hier ein Link zu Modbus-TCP-class, der mir geholfen hat.Letztendlich kann ein digitaler Eingang 4 relevante Zustände haben. Dazu hier ein kleines Beispiel:
bool bit0; //Taster int state; //Zustand des Tasters int InputState() { if (state == 0 && !bit0) //Taster wird gedrueckt (steigende Flanke) { state = 1; //Zeit für steigende Flanke speichern... } else if (state == 1 && !bit0) //Taster wird gehalten { state = 2; //Vergleich der gespeicherten Zeit der steigenden Flanke... //...mit der Entprellzeit } else if (state == 2 && bit0) //Taster wird losgelassen (fallende Flanke) { state = 3; //Zeit für fallende Flanke speichern... } else if (state == 3 && bit0) //Taster losgelassen { state = 0; //Vergleich der gespeicherten Zeit der fallenden Flanke... //...mit der Entprellzeit } return state; }
Ich habe in meinen Projekten lediglich die Flanken benötigt. Im folgenden Beispielcode ist noch die hier gefragte Entprellzeit angedeutet enthalten. Eines muss ich dazu sagen; Der Programmcode stammt zum Teil aus meinen Projekten, zum anderen habe ich diesen lediglich kopiert und zur Anschauung angepasst. Also bitte nicht einfach kopieren, in der Form ist das nicht unbedingt praktikabel:
/// <summary> /// Die folgende Klasse ist ledeiglich zur Veranschaulichung und somit nicht vollständig. /// /// </summary> public class TestInputs { /// <summary> /// Status sämtlicher Diditaler Signale. /// Die Eingänge werden per ModbusTCP o.ä. gelesen /// und un ein Bit-Array gespeichert. /// </summary> public bool[] state; /// <summary>Klasse Digitale Eingänge im Array zusammengefasst.</summary> ClassDigitalInput[] IN; public TestInputs() { // Anzahl Eingänge initialisieren IN = new ClassDigitalInput[7]; for (int i = 0; i < IN.Length; i++) { IN[i] = new ClassDigitalInput(); } } /// <summary> /// Der Folgende Programmteil sollte "zyklisch" durchlaufen werden /// und gehört somit in einen eigenen Thread. /// </summary> void CheckInputs() { // Flanken aller konfigurierten Eingänge erzeugen for (int i = 0; i < state.Length; i++) { IN[i].makeEdges(state[i]); // Flanken und Zeitspempel der Signalwechsel erzeugen } //hier können nun die Enprellzeiten der Eingänge abgefragt werden. //Die Klasse "ClassDigitalInput" speichert die Zeiten der jeweiligen Flanken. if (state[0] && DateTime.Now > IN[0].LastPosEdge.AddSeconds(2)) { //Eingang 0 ist für min. 2 Sekunden EIN } if (!state[4] && DateTime.Now > IN[4].LastPosEdge.AddSeconds(1)) { //Eingang 4 ist für min. 1 Sekunden AUS } } } public class ClassDigitalInput { public DateTime LastPosEdge; public DateTime LastNegEdge; bool bPosEdge = true; bool bHvPosEdge; bool bNegEdge = false; bool bHvNegEdge; // ------------------------------------------------------------------------ /// <summary>Create ClassMain instance without parameters.</summary> public ClassDigitalInput() { LastPosEdge = DateTime.Now; LastNegEdge = DateTime.Now; } public void makeEdges(bool state) { // Flanken erzeugen // Positive Flanke bei Signalwechsel erzeugen - Signal 1 bPosEdge = (state & !bHvPosEdge); bHvPosEdge = state; if (bPosEdge == true) { LastPosEdge = DateTime.Now; } // Negative Flanke bei Signalwechsel erzeugen - Signal 1 bNegEdge = (!state & !bHvNegEdge); bHvNegEdge = !state; if (bNegEdge == true) { LastNegEdge = DateTime.Now; } } }
Um eine "richtige" Soft-SPS zu implementieren habe ich hier noch zwei interessante Links:
Ich denke, wenn du uns konkreter an deine Aufgabe teilhaben lässt, bekommst du auch passendere Antworten.
Gruß
Stefan -
Hallo Ulrich,
ich habe eine Klasse Eingang definiert:
public enum Schaltpunkt { Low, High } public class Eingang : ModelBase.ModelBase { public event EventHandler EingangEvent; #region Fields private bool gesetzt;
#endregion #region Properties public int Nummer { get; private set; } public int Eingangswert { get; private set; } public string Bezeichnung { get; private set; } public Schaltpunkt Schaltreaktion { get; private set; } public DateTime Zeitstempel { get; set; } public int Wartezeit { get; set; } public bool Gesetzt { get { return gesetzt; } set { gesetzt = value; // Prüfen ob Wartezeit abgelaufen if (Zeitstempel.AddMilliseconds(Wartezeit) < DateTime.Now) { if (value) { Schaltzyklen++; Zeitstempel = DateTime.Now; EingangFired(); OnChanged("Median"); } } OnChanged(string.Empty); } } #endregion public Eingang() { } public Eingang(int Nummer, string Bezeichnung, int StationID, int AnlageID, Schaltpunkt Schaltreaktion = Schaltpunkt.High, int MinimalGesetztZeit = 50, int Wartezeit = 5000, bool Endpunkt = false) { this.Nummer = Nummer; this.StationID = StationID; this.AnlageID = AnlageID; this.Eingangswert = (int)Math.Pow(2, Nummer); this.Bezeichnung = Bezeichnung; this.Schaltreaktion = Schaltreaktion; this.MinimalGesetztZeit = MinimalGesetztZeit; this.Wartezeit = Wartezeit; this.Endpunkt = Endpunkt; } void TaktZeitstempel_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset) { OnChanged(); } } public void EingangFired() { if (EingangEvent != null) { EingangEvent(this, new EventArgs()); } } }
Und eine weitere Klasse für die EingangsCollection:
public class EingangsCollection : ModelBase.ModelBase { public enum QuancomCards { USBOPTOREL8 = (int)qlib.qlib32.USBOPTOREL8, USBOPTOREL16 = (int)qlib.qlib32.USBOPTOREL16, USBOPTOREL32 = (int)qlib.qlib32.USBOPTOREL32 } public ObservableCollection<Model.Eingang> Eingänge { get; set; } public Timer AktualisierungsTimer { get; private set; } private bool verbindung; public bool Verbindung { get { return verbindung; } set { verbindung = value; OnChanged(); } } public EingangsCollection() { Eingänge = new ObservableCollection<Model.Eingang>(); AktualisierungsTimer = new Timer(25); AktualisierungsTimer.Elapsed += AktualisierungsTimer_Elapsed; AktualisierungsTimer.Start(); } async void AktualisierungsTimer_Elapsed(object sender, ElapsedEventArgs e) { await System.Threading.Tasks.Task.Factory.StartNew(() => { var read = ReadInputs(); SetInputs(read); }); } private void SetInputs(uint QuancomValue) { foreach (var eingang in Eingänge) { eingang.Gesetzt = IsBitSet(QuancomValue, eingang.Nummer); } } private bool IsBitSet(uint n, int position) { return (n & (1 << position)) != 0; } public uint ReadInputs() { uint result = 0; try { uint handler = qlib.qlib32.QAPIExtOpenCard((uint)QuancomCards.USBOPTOREL8, (uint)0); Verbindung = (handler != 0); if (handler != 0) { qlib.CARDDATAS lpcd = new qlib.CARDDATAS(); result = qlib.qlib32.QAPIExtReadDI32(handler, 0, 0); qlib.qlib32.QAPIExtCloseCard(handler); } } catch (Exception) { } return result; } }
In der CollectionKlasse wird alle 25ms ein Wert der Quancom Box abgerufen und anschließend mittels Bit-Shifting geprüft welcher Eingang geschaltet ist. Die Box liefert den Wert 1, wenn Eingang 1 geschaltet ist, 2 wenn Eingang 2 geschaltet und 3 wenn Eingang 1 und 2 geschaltet ist usw.. Daher muss man anhand des Werts prüfen welche Eingänge nun geschaltet sind und welche wiederum nicht.
- Als Antwort markiert Ulrich Stippe Mittwoch, 27. Januar 2016 17:10
-
Hallo David,
ist es nicht einfacher den Port ein Mal zu öffnen und im Thread nur die Werte auszulesen?
Wahrscheinlich würde ich auch den Aufruf von ReadInputs und SetInputs mit einem Lock absichern um die Ausführung der Methoden nur in einem Thread zu gewährleisten.
Grüße
-
Moin,
der Port wird doch in einem separaten Thread geöffnet, gelesen und geschlossen. Die Auswertung des Wertes erfolgt im selben Thread. Ein Lock könnte ich da sicherlich drauf legen, aber da ja nicht mehrere Threads parallel auf die Daten zugreifen lasse, sondern alle 25ms immer nur ein Thread, habe ich die Notwendigkeit nicht gesehen.
-
Hallo David,
das war nur ein Verbesserungsvorschlag.
ich kann mir folgendes Szenario vorstellen: es wird einen Thread zum Datenlesen gestartet. Dann schaltet sich GC ein oder man braucht mehr als 25 ms für die Lesefunktion und die Events, die in dieser Funktion ausgelöst werden.
Nach der 25 ms wird es noch einen Thread vom Timer gestartet, obwohl der letzte Thread noch nicht beendet wurde. In diesem Fall greifen zwei Threads auf die Daten zu.
Viele Grüße
- Bearbeitet Iso7 Donnerstag, 28. Januar 2016 14:47
-
Hab das auch nicht als Kritik verstanden :-)
Ich mache mir da mal meine Gedanken zu!
Hallo David,
ok, einfach dann veröffentlichen, wenn Du oder Iso7 was gutes hast. Grundlegend mal beantwortet. Ich markiere es somit als beantwortet.
Anregungen habe ich bekommen. Danke.
Viele Grüße Ulrich