Gemeinsamkeiten C <->Step7

Markus

Administrator
Teammitglied
Beiträge
6.324
Reaktionspunkte
2.341
Zuviel Werbung?
-> Hier kostenlos registrieren
ich habe von c/c++ nicht allzuviel anhnung, aber programmieren tue ich ausschlieslich in AWL...

vieleicht kann ich von der seite etwas untersüzung bringen.


"case" sollte man direkt in "SPL" übersetzen können.

Schleifen sollten mit "loop" identisch sein.

ich poste nacher noch eine übersicht der awl befehle.
 
Hallo Markus

Markus schrieb:
ich habe von c/c++ nicht allzuviel anhnung, aber programmieren tue ich ausschlieslich in AWL...

vieleicht kann ich von der seite etwas untersüzung bringen.

Man muss zweierlei unterscheiden C und C++.

C++ als Programmiersprache zur Entwicklung von SPS-Anwendungen halte ich nicht für sinnvol, da man die Objekt-Orientierung der Sprache sicher nicht ausnützen kann. Vielleicht gibt es da andere Meinungen, ich glaub aber eher nicht.

C als Programmiersprache zur Entwicklung von SPS-Anwendungen halte ich für sehr geeignet, parallel dazu gibt es ja das SCL mit der etwas schwachen Anlehnung an PASCAL.

In SCL erfolgt der Funktionsaufruf mit etwas eigenwilligen Methoden, was die Sache nicht besser macht. Hier mal eine Gegenüberstellung eines kleinen Beispiels für das Bitschieben nach links um 2 Stellen::
in SCL schreibt man
Code:
   X := SHL (IN := Y, N := 2);
in C sieht das so aus:
Code:
   X = Y << 2;
was in AWL da stehen würde, dürfte klar sein, d.h. wir sind mit C näher an AWL dran als es mit SCL jemals möglich sein wird. Das v.g. ist natürlich nicht ganz korrekt, was in SCL wie eine Funktion ausschaut, ist in C bereits schon keine Funktion mehr, was auch gleichermaßen für AWL gilt:
Code:
   L     Y
   SLW  2
   T      X
In AWL muß ich dem Typ von X (!!!!) entsprechend einen Schiebebefehl auswählen, SLW, SLD. In C erfolgt dies implizit, der Schiebeoperator << wird dabei in der Schreibweise nicht unterschieden.

Für unser Team, in welcher Programmiersprache wir das Projekt entwickeln sollten, dürfte halbwegs schon entschieden sein, d.h. sicher werden wir große Teile in C++ entwickeln.

Markus schrieb:
"case" sollte man direkt in "SPL" übersetzen können.
Zur Anwendung als SPS-Programmierumgebung, ein kleines Beispiel in C:
Code:
switch (XZY) {
    case Fall_1:
         a = b + c;
         z = x - y;
   break;
    case Fall_2:
         a = 2 * b + c;
         z = x - 2 *y;
   break;
   default:
         a = 1;
         z = 0;
   break;
}

SCL ist da an dieser Stelle sehr beschränkt und nutzt letzten Endes die Möglichkeiten von Step7 nicht aus, C ist da viel flexibler und eine C-konforme Umsetzung nach Step7 sollte hier eigentlich keine Probleme bereiten.

Markus schrieb:
Schleifen sollten mit "loop" identisch sein.

Für Schleifen gibt es in C eine große Anzahl von Möglichkeiten, mehr als das rigide SCL bieten und was auch von Step7 unterstützt werden kann. Ich bin mir z.Zt. aber nicht sicher, wie die Implementierung der Schleifenfunktionen in "AWL" aussehen wird. Sicher wird auch der LOOP-Befhl zur Anwendung kommen.

Gruß Barnee
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Grundsätzlices zum Aufbau von FC's und FB's

Hallo @All
Und der Weg zur Hölle ist mit guten Vorsätzen gepflastert...

Das ist mir so in den Sinn gekommen, als ich über den Stand des Projektes nachgedacht habe. Ich hab diesen Satz dann auch gleich für meine Signatur übernommen und wir werden sehen, ob uns der Teufel überhaupt haben will.

"Gemeinsamkeiten C <-> Step7" ist vielleicht nicht unbedingt die passende Überschrift für diesen Thread aber ich belasse es dabei.

Mein Antrieb zu diesem Projekt war u.a., das ich mich über SCL geärgert habe, dass ja von Siemens so vollmundig an PASCAL angelehnt wurde aber nicht einmal das mindeste einhält, was man hätte erwarten können. Es gibt z.B. in AWL die Möglichkeit einen Wert vom Typ TIME in den AKKU zu laden und diesen dann ganz ungeniert als ein DINT zu betrachten, das ist auch zulässig, da TIME in Wahrheit tatsächlich vom Typ DINT ist. In SCL also einer Variablen vom Typ DINT den Wert einer Variablen vom Typ TIME zuweisen zu können, gelingt in SCL nicht mit den in PASCAL jedoch vorgesehen Möglichkeiten, als da wäre:
Code:
WERT1 : DINT;
WERT2 : DINT;
WERT1 := (DINT)WERT2;
Hier meldet der Übersetzer einen Fehler, weil die Anlehnung an PASCAL soweit nun doch nicht vorhanden ist. Jetzt wollte ich aber nicht ein besseres SCL mit strikterer Implementierung von Eigenschaften aus PASCAL entwickeln, da AWL von PASCAL noch weiter entfernt ist, dagegen aber ein Teil der AWL-Sprachmittel aus C entlehnt worden sind. So war es für mich nahe liegend, einen S7-C-Compiler entwickeln zu wollen.

Nun gibt es aber doch gewaltige Unterschiede zwischen einer SPS-Anwendung und einer Anwendung für z.B. einen PC.

Stackzugriffe - bei einer SPS nicht anwendbar
Wenn eine in C geschriebene Anwendung z.B. auf dem PC ausgeführt wird, dann wird Maschinencode auf dem uP des PC's abgearbeitet, Maschinencode, der z.B. vom C-Compiler errichtet wurde. Ein realer uP hat viele Register, die teilweise für bestimmte Aufgaben hergenommen werden können und arbeitet bei der Ausführung von Funktionen mit einem Stack. Wer schon einmal in Assembler programmiert hat, weiss, was ich damit meine, da bei der Programmierung in Assembler die Stackversorgung bzw. Stackentsorgung zu Fuß erstellt werden muss. Bei der Programmierung in C ist das nicht erforderlich, da dies der Compiler automatisch in den Maschinencode einfügt.

Bei einer SPS-Anwendung gibt es keinen Zugriff auf den Stack, der sich aus der Anwendung direkt ergibt, obwohl der physische uP einer SPS sicher mit einem Stack hantiert. Eine Funktion, die in C z.B. wie folgt aufgerufen würde
Code:
a = meine_funktion (b,c,d);
hat in Step7 mit einem FC - hier durch meine_funktion symbolisch adressiert - Ähnlichkeiten, ich vermeide damit bewusst den Ausdruck der Vergleichbarkeit. In C würden die Parameter b,c,d des o.g. Beispiels vor der Ausführung der Funktion auf den Stack geladen, erst danach wird in die Funktion gesprungen, als vorletzte MC-Anweisung bei der Ausführung der Funktion (also noch innerhalb der Funktion selbst) wird das Funktionsergebnis auf den Stack geladen und dann erfolgt die letzte MC-Anweisung return, was den Rücksprung auf die der Absprungstelle folgenden Befehlsdresse bewirkt, die dort vorhanden MC-Anweisung holt sich das Funktionsergebnis vom Stack, danach wird der Stackrahmen korrigiert, das er den Bedingungen vor dem Aufruf der Funktion entspricht. Diese bei einem "gewöhnlichen" uP geübte Handhabung ist bei einem S7-C-Compiler nicht anwendbar, da AWL (MC7-Code) dafür nicht vorgesehen ist.

Die Ähnlichkeit eines C-Funktionsaufrufes gemäß dem o.g. Beispiel zu einem Step7-FC-Konstrukt ist zwar gegeben aber ANSI-C lässt eine Veränderung der Parameter, die einer Funktion bei deren Aufruf übergeben werden nicht zu. In den o.g. Beispiel würden die Parameter b,c,d bei der Ausführung als Step7-FC in der Schnittstelle als VAR_INPUT definiert. Würde der Step7-FC zusätzlich noch in der Schnittstellenbeschreibung die Definitionen für VAR_OUTPUT oder gar noch VAR_IN_OUT aufweisen, wird das Dilemma gegenüber ANSI-C sofort sichtbar. (Anm. In PASCAL wäre eine Ähnlichkeit zu VAR_IN_OUT gegeben, wenn die Parameter mit dem Zusatz var übergeben würden.)

Es muss daher eine Lösung gefunden werden, wie die Schnittstellenbeschreibung in C gecodet werden könnte. Nach meiner Meinung müssen wir "Strict-ANSI-C" eben an solchen Punkten verlassen. Folgend werde ich auf die Gemeinsamkeiten von Step7-FC und Step7-FB eingehen, die speziellen Betrachtungen zu einem Step7-FB-Konstrukt lasse ich hier vorerst einmal beiseite.

Somit dürfte klar sein, das wir in Step7 nicht ein Stackmodell aus ANSI-C übernehmen können, egal was die S7-CPU im tiefsten Inneren tatsächlich ausführt, da der von unserem S7-C-Compiler übersetzte Code nur den MC7-Code repräsentiert, der von Natur aus ein gemäß ANSI-C verwendbares Stackmodell ausschließt!

Wie wird die Parameterübergabe konform zu Step7 gestaltet?
In C gibt es generell nur Funktionen. Eine C-Funktion hat immer einen Rückgabewert. Für C-Funktionen, die keinen anwendbaren Rückgabewert liefern, wird in C der Prototyp wie folgt definiert:
Code:
void eine_Funktion (void);
Bezogen auf C handelt es sich hier um eine Funktion, die ausschließlich mit globalen Variablen hantiert, wenn diese Funktion einen Sinn machen soll. Übertragen auf Step7 geraten wir nun hier schon in einen Konflikt! Bei der Siemens-Terminologie wird unterschieden zwischen Step7-FC bzw. Step7-FB! (Anm. ich lasse die Siemens-spezifischen Unterschiede zwischen Step7-FC bzw. Step7-FB erst einmal außer Betracht.) Mein Vorschlag wäre, den S7-C-Prototypen für einen Step7-FC so zu definieren:
Code:
void eine_Funktion (void);
und mit einem definierten Rückgabewert (hier z.b. long):
Code:
long eine_Funktion (void);
wobei long für DINT bezogen auf AWL steht. (Anm. mit typedef oder mit #define kann sich ja jeder, der es möchte, DINT als Substitut für long anlegen usw.usf.)

Gemäß diesem Modell wäre die in C übliche in Klammern eingeschlossene Parameterübergabe hinter der Nennung des Bezeichners (hier in dem Beispiel eine_Funktion) ohne zusätzliche Informationen nicht mehr anwendbar! Dieses wäre auch mit keinen Nachteilen verbunden, da wir uns ohnehin eine andere Art der Parameterübergabe, welche die Anforderung von Step7 erfüllt, überlegen müssen.

Es fehlt noch die Definition des Prototypen, der später in einen Step7-FB übersetzt werden soll:
Code:
void ein_Funktionsbaustein (void);
Formal sind nun die Prototypen für einen Step7-FC ohne Rückgabewert und für einen Step7-FB gleich! Das stellt aber kein Problem dar. Woher nimmt nun der S7-C-Compiler die Information, ob es sich um eine Step7-FC oder um einen Step7-FB her, den es zu übersetzen gilt?

Im einem S7-Projekt kann in der Symboltabelle für jeden Baustein ein Symbol angelegt werden, so würde z.B. entweder die Assoziation eine_Funktion = FC 102 oder ein_Funktionsbaustein = FB 306 bestehen. Die Anforderung an unseren S7-C-Compiler besteht also darin, sich die Informationen aus dieser S7-Symboltabelle zu holen! ANSI-C schreibt vor, dass Symbole keine Zeichen enthalten dürfen, die für C-Operatoren verwendet werden. Dummerweise lässt der Symboleditor in Step7 z.B. die Eingabe des "-"Operators zu! Wenn wir mit einem gängigen PP arbeiten wollen, würde sich diese schlechte Angewohnheit verbieten! Mein Vorschlag wäre, sich bei der Erstellung der Symboltabellen im S7-Projekt auf die Zeichen zu beschränken, die auch üblicherweise zur Symboldefinition unter ANSI-C zugelassen sind.

Wenn wir an diesem Punkt angekommen sind, dann können wir die in C übliche Parameterübergabe nach ANSI-C nicht mehr konform anwenden! Doch wie könnte sich eine Parameterübergabe gestalten, welche die Anforderungen an Step7 erfüllt? Übersetzten wir einen AWL-Baustein als AWL-Quelle, so findet sich die Lösung. Anders als im AWL-Editor (den ich besonders liebe, da Siemens seit Jahren dessen Macken nicht beheben will!) sehen wir in einer AWL-Quelle die Schnittstellenbeschreibung. Mein Vorschlag wäre, die VAR_INPUT, VAR_OUTPUT und VAR_IN_OUT als Schlüsselwörter in unseren S7-C-Compiler einzuführen. (Anm. in Step7 gibt es noch weitere Teile der Schnittstellenbeschreibung, auf die ich hier an dieser Stelle vorerst noch nicht eingehen will.) So könnte z.B. der Prototyp für einen Step7-FC aussehen, der von unserem S7-C-Compiler erwartet würde:
Code:
long eine_Funktion (VAR_INPUT Boolean flag1, Boolean flag2, long wert1,
                    VAR_OUTPUT Boolean flag3, Boolean flag4);
Damit hätte diese Schreibweise noch eine Nähe zu ANSI-C, würde aber unseren S7-C-Compiler mit den notwendigen Informationen versorgen. Die in der AWL-Quelle übliche Abgrenzung mit VAR_END wäre somit völlig überflüssig, da der Geltungsbereich von z.B. VAR_INPUT sich über alle nachfolgenden Parameter erstreckt, bis er durch ein anderes Schlüsselwort wie VAR_OUTPUT oder VAR_IN_OUT aufgehoben wird. Der Geltungsbereich endet grundsätzlich, wenn die Parameterliste mit einer runden Klammer abgeschlossen wird. Ob wir nun diese Schlüsselwörter mit den v.g. Begriffen bezeichnen, was zumindest eine Nähe zu einer AWL-Quelle herstellt oder ob andere kürzere Begriffe verwendet werden, ist schlussendlich eine Frage des Geschmacks. Ich persönlich würde vin, vout und vio bevorzugen, weil das etwas lästige Tipparbeit einsparen würde, diese Schlüsselwörter sollten lediglich nicht mit ANSI-C kollidieren.

Welche Besonderheiten sind bei Schnittstellen-Parametern noch zu beachten?
Haben wir es bisher mit Formalien zu tun gehabt, die auf dem Verständnis des direkten Vergleichs von Gemeinsamkeiten zwischen AWL und C-Prototypen abgestellt waren, so gelangen wir nun zu einem Kapitel über Informationen, welche auch nicht im weitesten Sinne etwas mit der Logik eines Prozesses zu tun haben, der mit einer SPS-Anwendung kontrolliert werden soll.

Nachstehend zeige ich zwei Ausschnitte aus einer Schnittstellenbeschreibung, der erste Ausschnitt stammt aus einer SCL-Quelle und der zweite Ausschnitt aus der AWL-Quelle des gleichen Bausteins.
Code:
VAR_INPUT
  SEL01   {S7_visible:= 'true'} : BOOL := 0; // to select output 1
  SEL02   {S7_visible:= 'true'} : BOOL := 0; // to select output 2
  SEL03   {S7_visible:= 'true'} : BOOL := 0; // to select output 3
  SEL04   {S7_visible:= 'true'} : BOOL := 0; // to select output 4
  SEL05   {S7_visible:= 'false'}: BOOL := 0; // to select output 5
  SEL06   {S7_visible:= 'false'}: BOOL := 0; // to select output 6
END_VAR

Code:
VAR_INPUT
  SEL01 { S7_visible := 'true' }: BOOL ;	//to select output 1
  SEL02 { S7_visible := 'true' }: BOOL ;	//to select output 2
  SEL03 { S7_visible := 'true' }: BOOL ;	//to select output 3
  SEL04 { S7_visible := 'true' }: BOOL ;	//to select output 4
  SEL05 { S7_visible := 'false' }: BOOL ;	//to select output 5
  SEL06 { S7_visible := 'false' }: BOOL ;	//to select output 6
END_VAR
Auf den ersten Blick erscheint der Unterschied kaum merkbar, mich hat das auch völlig überrascht, als ich die AWL-Quelle des Bausteins erzeugt hatte. Aber das ist es nicht, worauf ich hinaus will. Wir sehen in geschweiften Klammern z.B. {S7_visible:= 'true'}. Hierbei handelt es sich um eine Objekteigenschaft eines Schnittstellenparameters, der z.B. in CFC benötigt wird. {S7_visible:= 'true'} entspricht ohnehin der Default-Einstellung, die ggf. mit {S7_visible:= 'false'} überschrieben werden kann. In CFC wird ein Baustein grafisch präsentiert und ein Schnittstellenparameter, dessen S7_visible-Attribut auf false eingestellt ist, wird in CFC nicht dargestellt.

Wo jetzt die Objekteigenschaften eines Schnittstellenparameters gespeichert werden, entzieht sich im Augenblick meiner Erkenntnis. (Das wäre mal eine Frage an Rainer, da ja wohl in seiner Umgebung schon weitere Erkenntnisse vorliegen könnten!) Im AWL-Editor sind diese Objekteigenschaften wie folgt erreichbar:
- einen Schnittstellenparameter anwählen und rechte Maustaste drücken
- es erscheint ein Dialog mit der Überschrift Eigenschaften - Variable
- den Reiter Attribute auswählen.

Für unseren S7-C-Compiler sind die Attribute wohl nicht relevant, für die Einbindung in ein bestehendes S7-Projekt dürften jedoch die Beschreibungen dieser Attribute nicht unter den Tisch fallen gelassen werden. Wie in dem vorherigen Abschnitt bereits dargestellt, sollte die C-spezifischen Parameterdefinitionen im Prototyp ausreichend befriedigt sein. Wie aber sollen die Beschreibungen der Attribute berücksichtigt werden? Sollte man sich an das Konzept anlehnen, was in AWL oder SCL praktiziert wird? Nach meiner Ansicht sollte man dies nicht tun, wir sollten das von mir bereits vorgestellte Prototyp-Konzept beibehalten, weil dies am ehesten noch eine Nähe zu ANSI-C darstellt. Die Lösung wäre ein weiteres Schlüsselwort pattr einzuführen. Die Anwendung würde dann aber nicht innerhalb der Definition des Prototyps erfolgen sondern innerhalb der Definition der Funktion im C-File. Dies könnte dann so aussehen:
Code:
void eine_Funktion (VAR_INPUT Boolean SEL01, Boolean SEL02, Boolean SEL03, 
                              Boolean SEL04, Boolean SEL05, Boolean SEL04,
                    VAR_OUTPUT Boolean flag3, Boolean flag4)
{
   pattr SEL01 (S7_visible true,  0, 'to select output 1'),
         SEL02 (S7_visible true,  0, 'to select output 2'),
         SEL03 (S7_visible true,  0, 'to select output 3'),
         SEL04 (S7_visible true,  0, 'to select output 4'),
         SEL05 (S7_visible false, 0, 'to select output 5'),
         SEL06 (S7_visible false, 0, 'to select output 6');

  if (SEL01 && SEL02) .....

}
Natürlich muss an diesem Konzept noch gefeilt werden, denn es ist durchaus möglich, das für einen Schnittstellenparameter mehr als nur ein Attribut zugeordnet werden kann. In dem v.g. Beispiel werden dem Parameter SEL01 das Attribut S7_visible, die Default-Einstellung 0 für die Parameterversorgung bei "unbeschaltetem" Zustand und der Kommentartext zugewiesen. Der Geltungsbereich der Attributdefinitionen beginnt mit dem Schlüsselwort pattr und endet mit einem Semikolon. Die Definitionen von Attributen für einen Schnittstellenparameter beginnen mit der Nennung des Bezeichners, gefolgt von einer (-Klammer und wird durch eine )-Klammer abgeschlossen, innerhalb des Klammerpaares werden die Attribute durch Kommata getrennt. Folgt der Attributdefinition eines Schnittstellenparameters eine Attributdefinition für einen weiteren Schnittstellenparameter, so wird diese durch ein Komma abgetrennt, sonst wird die gesamte Attributdefinition durch ein Semikolon abgeschlossen.

Wenn wir auf einen "fertigen" CPP (C-preprocessor) aufsetzten wollen, beginnen sicher hier die Schwierigkeiten. Wir müssen dann dort einen entsprechenden Anteil nachrüsten, der sich dieser zusätzlichen Definitionen annimmt, welche ja nicht unbedingt in einem normalen CPP von Hause aus anzutreffen sein werden.

Da zwangsläufig in AWL jeder Parameter versorgt werden muss, mag er in der AWL-Quelle ohne die Default-Einstellung für die Parameterversorgung daherkommen. Wenn ein solcher Baustein in einen CFC-Plan eingebaut wird, dann werden die "unverdrahteten" Eingänge mit der Defaultversorgung beschaltet, auch die "unsichtbaren" Parameter! Wer etwas tiefer in CFC eingestiegen ist, wird feststellen, dass diese Versorgung auf Datenstellen eines Instanzdatenbausteins erfolgen, was ich hier aber nicht weiter ausführen will, da dies im CFC-Konzept von Hause aus erledigt wird.

Unterschiede zwischen Step7-FC und Step7-FB
Bis hierhin wären rein oberflächlich betrachtet einige Gemeinsamkeiten bei Step7-FC und Step7-FB vorhanden. Dem ist aber nicht so! Die bisher behandelten Typen der Schnittstellenparameter weisen aus der Sicht der Syntax scheinbare Gemeinsamkeiten auf. Der wesentliche Unterschied ist aber vorhanden und dürfte damit sicher auch Auswirkungen auf den zu erzeugenden MC7-Code haben.

Ein Step7-FC kommt ohne Instanzdatenbaustein aus! Aber wo werden die Schnittstellenparameter für die Ausführung der Funktion hinterlegt? Laut Siemens werden diese auf einem Stack gespeichert! Ob dieser Stack jedoch mit einem Stack herkömmlicher Art vergleichbar wäre, entzieht sich derzeit meiner Erkenntnis. Die Anwendung dieses eigentümlichen Stacks ist auch die Erklärung dafür, dass ein Step7-FC keine statischen Variablen halten kann, was einen wesentlichen Unterschied zu einem Step7-FB ausmacht. Aus der Sicht der AWL-Codierung sind die Unterschiede bezüglich der Ein- und Ausgabeparameter im Hinblick auf einen Step7-FB nur in bestimmtem Umfang relevant, im Hinblick auf den zu erzeugenden MC7-Code wird es wahrscheinlich notwendig sein, hier tiefere Erkenntnisse zu gewinnen.

Ein weiterer wesentlicher Unterschied zwischen einem Step7-FC und einem Step7-FB besteht darin, dass nur der Step7-FC einen Rückgabewert liefern kann! Wenn also ein Step7-FC noch in etwa eine Konformität zu ANSI-C hat, dann ist dies bei einem Step7-FB schon nicht mehr gegeben! Ein Step7-FB darf konform zu Step7 keinen Rückgabewert liefern! Dies hat beachtliche Konsequenzen bei einer Programmierung in S7-C! Wir werden diesbezüglich das noch in einem anderen Rahmen diskutieren müssen.

Der gravierendste Unterschied zwischen einem Step7-FC und einem Step7-FB besteht aber darin, dass die Anwendung eines Step7-FB immer mit einem Instanzdatenbaustein einhergeht! Deshalb noch einmal: Aus der Sicht der AWL-Codierung sind die Unterschiede bezüglich der Ein- und Ausgabeparameter im Hinblick auf einen Step7-FC nur in bestimmtem Umfang relevant, im Hinblick auf den zu erzeugenden MC7-Code wird es sicher notwendig sein, hier tiefere Erkenntnisse zu gewinnen.

Aber anders als ein Step7-FC kann ein Step7-FB mit statischen Variablen ausgestattet werden. Der Vergleich zu ANSI-C hinkt aber etwas. Wird unter ANSI-C innerhalb einer Funktion eine statische Variable definiert, so verliert diese Variable nach Verlassen der Funktion nicht ihre Gültigkeit, kann aber von anderen Funktionen, die im gleichen C-File definiert werden, nicht gesehen werden, d.h. eine unter ANSI-C innerhalb einer Funktion definierte statische Variable hat nur innerhalb der Funktion, in der sie definiert worden ist, ihren Geltungsbereich! Eine in einem Step7-FB definierte statische Variable hat generell globalen Charakter, da diese Variable eine Datenstelle in einem Instanzdatenbaustein adressiert! Hier sollte nun überlegt werden, wie die Syntax zur Definition einer statischen Variable unter S7-C für einen Step7-FB formuliert werden sollte. Man könnte, wenn man nicht zu kleinlich ist, das von ANSI-C vorgehaltene Schlüsselwort static anwenden - ich hätte dagegen nichts einzuwenden. Gleichzeitig wird man aber dabei unterscheiden müssen, ob es auch statische Variablen geben soll, deren Geltungsbereich sich modulweit erstrecken soll und die ggf. mit einer extern-Referenz angesprochen werden könnte. Aber diesen Punkt möchte ich vorerst an dieser Stelle nicht weiter vertiefen.

Statische Variablen eines Step7-FB werden daher von dem S7-C-Compiler automatisch als Schnittstellenparameter zwischen den Step7-Schlüsselwörtern VAR und VAR_END platziert.

Lokale Variablen in Step7-FC und Step7-FB
Ich denke, dies ist eigentlich der simpelste Teil der Definitionen unter S7-C und könnte völlig konform zu ANSI-C erfolgen, d.h. ANSI-C-konforme lokale Variablen werden von dem S7-C-Compiler automatisch als Schnittstellenparameter zwischen den Step7-Schlüsselwörtern VAR_TEMP und VAR_END platziert.

Definitionen der Bausteinköpfe für Step7-FC und Step7-FB
Eine Definition eines Bausteinkopfes kann als SCL-Quelle wie folgt aussehen:
Code:
FUNCTION_BLOCK FB813
 NAME    : 'RADIOBTN'
 AUTHOR  : 'HL'
 FAMILY  : 'XYZ'
 VERSION : '1.0'
 {S7_tasklist := 'OB100'; S7_m_c := 'false'; S7_blockview := 'big'}
Als AWL-Quelle sieht der gleiche Bausteinkopf wie folgt aus:
Code:
FUNCTION_BLOCK FB 813
 TITLE =
 { S7_m_c := 'false'; S7_blockview := 'big'; S7_tasklist := 'OB100' }
 AUTHOR : HL
 FAMILY : XYZ
 NAME : RADIOBTN
 VERSION : 1.0
Wobei ich mir aber die Bemerkung nicht verkneifen kann, das Siemens hier schon für eine Inkonsistenz gesorgt hat. In SCL ist hinter dem Schlüsselwort HEADER der Titeltext einzutragen, in dem Beispiel wird HEADER nicht verwendet, deshalb wird im AWL-Quelltext hinter TITLE = kein Text angezeigt!

Um die Konformität zu Step7 zu erreichen, wäre es notwendig, wie schon bei den Attributen zu den Schnittstellenparametern, eine (oder mehrere) zusätzliche Methode(n) in S7-C einzuführen. Soweit die in AWL verwendeten Schlüsselwörter mit ANSI-C nicht kollidieren (ich hab das i.A. nicht geprüft) könnte das dann wie folgt aussehen:
Code:
void eine_Funktion (VAR_INPUT Boolean SEL01, Boolean SEL02, Boolean SEL03, 
                              Boolean SEL04, Boolean SEL05, Boolean SEL04,
                    VAR_OUTPUT Boolean flag3, Boolean flag4)
{
   title 'Auswertung von Radiobuttons';
   fattr (S7_m_c false, S7_blockview big, S7_tasklist OB100);
   author 'HL';
   family 'XYZ;
   name 'RADIOBTN';
   version '1.0';

   pattr SEL01 (S7_visible true,  0, 'to select output 1'),
         SEL02 (S7_visible true,  0, 'to select output 2'),
         SEL03 (S7_visible true,  0, 'to select output 3'),
         SEL04 (S7_visible true,  0, 'to select output 4'),
         SEL05 (S7_visible false, 0, 'to select output 5'),
         SEL06 (S7_visible false, 0, 'to select output 6');

  if (SEL01 && SEL02) .....

}

Ausblick
Damit möchte ich erst einmal für den heutigen Beitrag den Ausflug in die Betrachtungen, wie man in S7-C eine Konformität zu Step7 erreichen könnte, beenden. Sicher wird man an den einzelnen Abschnitten noch feilen müssen, denn mir ist jetzt schon bewusst, dass in meinen Ausführungen noch nicht alles angesprochen worden ist, d.h. meine Ausführungen sind sicher noch unvollständig. Ich verstehe meinen Beitrag als Grundlage zur weiteren Diskussion und als Anregung für alle, das ihr hier eure Ergänzungen aus eurer Sicht beitragen solltet.

In meinem nächsten Beitrag werde ich mich damit beschäftigen, welche Parametertypen bei den in S7-C programmierten Funktionen konform zu ANSI-C vs. Step7-FC bzw. Step7-FB überhaupt angewendet werden können. Dies wird einen erheblichen Einfluß auf die verwendbaren Typen bei den Schnittstellenparametern haben. Wenn dieses Thema geklärt ist, dann ergibt sich sicher eine neue Sichtweise, welche ANSI-C-konformen C-Konstruktionen auch in S7-C gecodet werden können.

Gruß Barnee
 
Das Boolean-Problem

Hallo @All

Ich starte mal meine nächste Betrachtung zur Konformität von ANSI-C vs S7-C (wie ich mal unser "C-Derivat" benennen möchte) mit der Step7-Besonderheit des BOOL-Types.

Im ANSI-C gäbe es als angebliches Äquivalent den Typ Boolean, der es aber in sich hat, weil die tatsächliche Darstellung einer Variablen vom Typ Boolean sehr von dem verwendeten Typ des uP abhängig ist. Auf modernen uPs wird oft eine Boolean-Variable mit der Breite von 2 Bytes aber seit längerem oft auch mit der Breite von 4 Bytes angewendet, obwohl dort nur das LSB eine relevante Bedeutung hat, d.h. 0x0001 wird als true und 0x0000 wird als false gewertet. Es gibt auch andere Assoziationen in anderen Sprachen wie z.B. in PASCAL, da war es üblich $00 als true und $FF als false zu interpretieren. Generell ist es in ANSI-C nicht unüblich, eine solche Konstruktion zu coden:
Code:
Boolean flag;
long    eineZahl;

(long)flag = (eineZahl >> 11) & 0x0001;
alternativ hätte man dazu auch schreiben können:
Code:
flag = ((eineZahl >> 11) & 0x0001) != 0;
Die erste Variante erzeugt kürzeren Maschinencode, da der Vergleich mit != 0 entfällt, jedoch beide Varianten erzeugen den gleichen Effekt!

Was hat das mit Step7 zu tun? Eine ganze Menge finde ich. Schauen wir uns doch die Repräsentanz einer Variablen vom Typ BOOL in Step7 an. Zur Veranschaulichung sei hier die folgende Variablendefinition im lokalen Datenbereich eines FCs gezeigt:
Code:
VAR_TEMP
  flag00 : BOOL;
  flag01 : BOOL;
  flag02 : BOOL;
  flag03 : BOOL;
  flag04 : BOOL;
  flag05 : BOOL;
  flag06 : BOOL;
  flag07 : BOOL;
  flag10 : BOOL;
END_VAR
Die Variablen flag00 bis flag07 belegen das Byte 0 (LB 0) im lokalen Bereich. Die Variable flag10 belegt das Bit 0 im LB 1, die übrigen Bits im LB 1 bleiben ungenutzt. Die unter ANSI-C zulässige Konstruktion:
Code:
(byte)flag03 = (eineZahl >> 11) & 0x0001;
wäre somit in S7-C nicht zulässig, die einzig zulässige Konstruktion wäre ausschließlich durch:
Code:
flag03 = ((eineZahl >> 11) & 0x0001) != 0;
gegeben. Daraus folgt, dass das in ANSI-C nicht verbotene type casting in S7-C zu einer nicht auflösbaren Konstruktion führen würde.

Es gibt in Kreisen von Puristen die Anwandlung, das type casting generell nicht anzuwenden, Bjarne Stroustrup als maßgeblicher Erfinder von C++ findet für type casting in seinen Büchern nur böse Worte. Deswegen aber type casting in S7-C generell ausschließen zu wollen, das wäre aber IMHO trotzdem nicht der richtige Weg.

Wir müssen uns daher deutlich machen, dass dem Step7-Typ BOOL im ANSI-C dann doch kein echtes Äquivalent gegenübersteht! Was folgt daraus? Etwa in S7-C den Typ Boolean zu verbieten? Das Dilemma liegt in der Assoziation zu einer Bool'schen Variablen, die durch die Ähnlichkeit der Typnamen gegeben ist. Betrachten wir also noch einmal diese C-Konstruktion:
Code:
(long)flag = (eineZahl >> 11) & 0x0001;
Wenn wir in S7-C eine solche Konstruktion zulassen würden, dann hätte das folgende Konsequenzen:
-- Der ANSI-C-Typ Boolean würde mit dem Step7-Typ DWORD übersetzt werden müssen.
-- Für den Step7-Typ BOOL müsste in S7-C ein Äquivalent geschaffen werden.

Man wird sehr schnell einsehen, dass dies zur Konfusion führen wird. Ich halte es daher für sinnvoll, in S7-C den ANSI-C-Typ Boolean nicht zu verwenden und selbst auch nicht den Namen dazu zu verwenden, um ein Äquivalent zu dem Step7-Typ BOOL zu erzeugen. Als Äquivalent zu dem Step7-Typ BOOL würde ich vorschlagen, den S7-C-Typ S7Bit einzuführen. Jeder, der es dann mag, kann sich ja per typedef oder #define den Namen BOOL darüber legen.

Als weitere Maßnahme sollte das type casting in Verbindung mit dem S7Bit-Typ ausgeschlossen sein, d.h. type casting in Verbindung mit dem S7Bit-Typ muss von dem PP abgefangen werden und eine entsprechende Fehlermeldung erzeugen.

Mir ist i.A. noch nicht ganz klar, welche Auswirkungen das auf Step7-UDT haben könnte, welche wohl im S7-C durch typedef struct gebildet werden würden.

Anm.: In der Urfassung von C hat es den Typ Boolean ohnehin nicht gegeben. Er wurde vielfach per typedef oder #define in globalen Headern vereinbart. In moderneren IDEs konnte man das sogar per Default einstellen, ohne dass man sich um eine Definition in einem Header bemühen musste.

Gruß Barnee
 
Die Geschichte von ganzen Zahlen und von solchen, die es...

...nur vorgeben welche zu sein.


Hallo @All

Historisch gesehen, gibt es in C eine Integer-Problematik, die sich zwangsläufig aus der HW-Entwicklung von uPs ergeben hat. Ich will hier nicht die ganze Geschichte aufrollen. Aber es hat im wesentlichen mit der Byte-Maschine begonnen. Die Zusammenfassung von 2 Bytes, um den Zahlenbereich eines Integers von -32768...0...+32767 darzustellen, führt aber je nach HW-Hersteller zu unterschiedlichen Handhabungen, doch dazu später mehr. Lange bevor es 4-Byte-Maschinen und FPUs gab, hat man sich damit befasst, auch größere Zahlenbereiche verwalten zu können. Ich kann mich noch sehr gut an Arithmetikmethoden erinnern, die solches bewältigen sollten (Arithmetik auf der Basis von Strings) oder aber auch an Methoden in Assembler, wo man z.B. auf einer 2-Byte-Maschine eine Multiplikation von zwei 2-Byte-Integer ausführte, wo das Ergebnis schlussendlich als 4-Byte-Integer gespeichert wurde. War man bei 2-Byte Maschinen mit dem Typ int meist völlig zufrieden, war die Verwirrung perfekt, als die uPs in die Lage versetzt wurden, auch 4-Byte-Integer verarbeiten zu können. Besonders in der Übergangsphase musste man aufpassen, ob der Typ int wirklich nur einen 2-Byte-Integer oder gar einen 4-Byte-Integer spezifizierte.

Heute wird in C ein 2-Byte-Integer häufig als short und ein 4-Byte-Integer als long spezifiziert. Schaut man sich die Repräsentanz von -32768 als Hex-Muster 0xFFFF an, so könnte dieses Muster auch den Wert 65535 darstellen. Das Schlüsselwort unsigned kann nun in Verbindung mit int dazu dienen, hier die Unterscheidung herbeizuführen. Wenn man also den Wert 65535 in einer 2-Byte-Variablen darstellen will, so wäre diese Variable mit dem Typ unsigned int zu definieren. Jetzt gibt es gottlob mit typedef oder #define die Möglichkeiten, die nach ANSI-C unhandlichen Typen etwas komfortabler zu definieren. In der C-IDE, in der ich in den letzten Jahren unterwegs war, gab es mit typedef vorgefertigte Typen:
-- SInt16 für 2-Byte-Variablen mit Vorzeichenbit von -32768...0...+32767
-- UInt16 für 2-Byte-Variablen ohne Vorzeichenbit von 0...65535
-- SInt32 für 4-Byte-Variablen mit Vorzeichenbit von -2147483648...0...+2147483647
-- UInt32 für 4-Byte-Variablen ohne Vorzeichenbit von 0...4294967295

Basierend auf diesen (S7)-C-Typ wären in Step7 folgende Äquivalente vorhanden:
-- SInt16 Step7: INT
-- UInt16 Step7: WORD
-- SInt32 Step7: DINT
-- UInt32 Step7: DWORD

Die Ableitung des Step7-Typs WORD von dem engl. word empfinde ich persönlich etwas unglücklich, weil das IMHO in C fast ganz ungebräuchlich ist, mit word assoziiere ich immer eine 2-Byte-Variable, ohne dass dabei eine Aussage über den dargestellten Wertebereich gemacht wird, d.h. es lässt IMHO nur die Aussage über den Speicherplatz zu - vielleicht bin ich da auch etwas zu puristisch :wink: :wink: :wink:

Jetzt hält Step7 bei den dort verwendeten S7-Typen und verwendeten Zahlendarstellungen noch einige Überraschungen bereit, die uns bei dem Plan, das S7-C zu entwickeln, doch arg verwirren könnten:
-- TIME, eine 4-Byte-Variablen mit Vorzeichenbit;
-- 3-stellige BCD-Zahlen, die im 2-Byte-Format und dem Wertebereich von -999...000...+999;
-- 7-stellige BCD-Zahlen, die im 4-Byte-Format und dem Wertebereich von -9999999...0000000...+9999999.

Eine sehr ärgerliche Implementierung von TIME wurde von Siemens (IEC 1131?!) in SCL vorgenommen. TIME ist genau genommen ein Typ, dem wir in ANSI-C völlig ungeniert den Typ SInt32 gegenüberstellen können. Eine Variable dieses Typs enthält Zeitangaben im Format von Millisekunden! Aber jeder Versuch in SCL, eine TIME-Variable in einen DINT-Wert umzuwandeln, endet erfolglos, es sein denn, man kreiert eine Funktion zur Typkonvertierung in AWL, welche dann in SCL angewendet werden kann! In AWL lädt man den Wert einer TIME-Variablen schlicht in den Akku und kann damit völlig ohne Einschränkungen arithmetische Operationen durchführen - nicht so in SCL. SCL mit der Anlehnung an PASCAL hält nicht einmal die in PASCAL möglich Typkonvertierung vor, hier muss man wie schon gesagt mit AWL-Hilfsmitteln eine Krücke bauen.

Zunächst müssen wir uns verdeutlichen, dass der Typ TIME in Step7 eine besondere Bedeutung hat, d.h. der Typ TIME, dem eine besondere Eigenschaft mitgegeben wurde, basiert auf dem elementaren Typ DINT. Diese besondere Eigenschaft hat aber nur in Verbindung mit Step7 eine Bedeutung, in S7-C verliert sich die Bedeutung, wenn man von einigen Sonderfällen absieht. Um diese Sonderfälle aber abzudecken, sollte in S7-C der Typ S7time eingeführt werden. Für die Handhabung müssen folgende Regeln aufgestellt werden:
1. Wird in S7-C eine Variable vom Typ S7time direkt gelesen oder direkt geschrieben, dann wird implizit die Variable so behandelt, als wäre sie vom Typ SInt32! Unter direkt gelesen oder direkt geschrieben verstehe ich das, wenn sich die Variable vom Typ S7time entweder links oder rechts von einem "="-Operator befindet.
2. Wird eine Variable vom Typ S7time als Aktualoperand angewendet, dann muss der S7-C-Compiler bei der AWL-Klartexterzeugung, eine Zwischenvariable einführen, da, welche Überraschung, auch der AWL-Editor im Step7-Manager über eine Typprüfung verfügt, die sofort zuschlägt, wenn man versucht, bei einem Formaloperanden, der den Typ TIME erwartet, eine Variable eines anderen Typs anzulegen. Unser Compiler müsste dann zusätzlichen Code einfügen, und das wie ist wiederum davon abhängig, um welchen Schnittstellentyp (VAR_INPUT, VAR_OUTPUT bzw. VAR_IN_OUT) es sich beim dem Formaloperanden handelt:
-- es muss eine temporäre Variable (z.B. temp) vom Typ TIME in der Schnittstellenbeschreibung angelegt werden.
-- Bei VAR_INPUT und einer S7-C-Variable vom Typ SInt32:
Code:
L   S7-C-Variable
T   temp
temp wird dann als Aktualoperand verwendet.


-- Bei VAR_OUTPUT und einer S7-C-Variable vom Typ SInt32:
temp wird dann als Aktualoperand verwendet;
Code:
L   temp
T   S7-C-Variable

-- Bei VAR_INOUT und einer S7-C-Variable vom Typ SInt32:
Code:
L   S7-C-Variable
T   temp
temp wird dann als Aktualoperand verwendet.
Code:
L   temp
T   S7-C-Variable

Wenn die S7-C-Variable vom Typ SInt16 wäre, kann gefahrlos die implizite Typänderung eigentlich nur bei VAR_INPUT angewendet werden:
Code:
L   S7-C-Variable
ITD
T   temp
temp wird dann als Aktualoperand verwendet.

Bei allen anderen Versuchen sollte der S7-C-compiler ggf. eine Fehlermeldung zumindest aber eine Warnung ausgeben.

Lustig wird das ganze erst noch, wenn wir in S7-C mit #define eine (Makro)-Konstante anlegen wollen, die dem TIME-Format gemäß IEC 1131 entsprechen soll. Wir erinnern uns, der PP substituiert Makros durch das textuelle Äquivalent! Schauen wir uns das jetzt mal in einem Beispiel an: T#2d_26h_4m_12s_123ms !!! Diese Konstante hat rein äußerlich gar nichts mit einer normalen Zahlendarstellung zu tun. Jedoch im Speicher einer SPS ist von dieser Darstellungsform nichts mehr aber auch rein gar nichts mehr zu bemerken. Das man bei TIME von einem elementaren Datentyp spricht, kann ich persönlich nur akzeptieren, als das sich dieser Datentyp eigentlich nur hinter einer Fassade versteckt. Diese Fassade ist aber nur etwas, was sich außerhalb des Rechenwerks einer SPS abspielt, weil dem Typ TIME eine Eigenschaft mitgegeben wurde, die eben auch nur rein äußerlich wirkt. Sollten wir jemals einen Debugger einbauen, dann wird uns der S7-C-Typ S7time den Wert einer solchen Variablen hoffentlich in dieser Form präsentieren. So stellt sich für den Augenblick die Frage, wie wir unserem PP beibringen, wie er mit (Makro)-Konstanten gemäß dieser Schreibweise umzugehen hat. Dies ist also noch ein Forschungsgebiet, um heraus zu finden, wie das in MC7-Code (nicht AWL - das wäre simpel) tatsächlich auszusehen hat.

Jetzt hat die S7 auch noch Relikte aus der S5-Zeit übernommen. Hierbei handelt es sich um die speziellen Darstellungen von Zeitparametern, die schon damals nicht mit "normalen" Zahlendarstellung unter einen Hut zu bringen waren. Da dies aber ein Geschäft ist, was auf einem absterbenden Ast beheimatet ist, will ich das hier vorerst einmal ausklammern.

Es bleiben noch die AWL-Befehle BTI, ITB, BTD und DTB, die in Verbindung mit BCD-Zahlen Anwendung finden. Jetzt kommen wir mit unserem S7-C etwas in Not! In Step7 existiert für BCD-Zahlen kein Datentyp, obwohl die Repräsentanz dieser BCD-Zahlen im Speicher einer SPS mit keinem der vorhandenen Datentypen beschrieben werden kann (zumindest ist mir da bisher nichts untergekommen, vielleicht hab ich da ja auch was verpasst...). In ANSI-C gibt es auch nichts Vergleichbares! Wenn wir aber in S7-C ein SPS-Programm schreiben wollen, das mit solchen Zahlen hantiert, dann macht es keinen Sinn, solche Methoden mit Bordmitteln aus ANSI-C beschreiben zu wollen, denn unser Compiler würde da aus dem Häuschen geraten, wenn wir ihm mit einem Pseudo-Geschubse von Byte-Nibblen mitteilen wollten, das er z.B. schlicht und ergreifend nur den AWL-Befehl ITB anwenden sollte!

Mir würden zwei Methoden gefallen:
-- Die Einführung der Datentypen S7BCD16 bzw. S7BCD32.
-- Die explizite Typwandlung unter Anwendung des type castings.

Das könnte dann wie folgt aussehen:
Code:
SInt16     einInteger, b;
S7BCD16    eineBCDZahl;

eineBCDZahl = (S7BCD16)(einInteger * b);
Der S7-C-Compiler macht daraus folgendes:
Code:
L     #einInteger
L     #b
*I
ITB
T     #eineBCDZahl
Was ich hier in diesem Beispiel noch nicht eingebaut habe, wäre die Überprüfung der ITB-Befehlsausführung durch Auswertung des BIE-Bits, das wäre auf jeden Fall durch den S7-C-Compiler implizit einzufügen.

Wenn die CPU einen AKKU-Ladefehl anwendet, dann wird implizit eine Typwandlung von INT nach DINT vorgenommen (voraus gesetzt der Wert der INT-Variablen ist positiv). Um es anders auszudrücken, wenn in den niederwertigen Anteil des CPU-AKKU ein Wert (mit dem AWL-Befehl L) geladen wird, dann wird der höherwertige Anteil des AKKUs auf 0 gesetzt. Da man aber nur in bestimmten Fällen sicher sein kann, nur mit positiven Zahlen zu hantieren, ist es sinnvoll, eine Vorzeichenerweiterung anzuwenden. Auf der AWL-Ebene erfolgt dies mit der Anwendung des ITD-Befehls. Haben wir z.B. in einem S7-C-Programm folgende Situation:
Code:
SInt16     einShort, b;
SInt32     einLong;

einLong = einShort * b;
dann sollte der S7-C-Compiler folgendes daraus machen:
Code:
L     #einShort
ITD
L     #b
ITD
*D
T     #einLong

So das wär's für heute einmal wieder. Mal sehen was euch an Kommentaren dazu einfällt...
Fortsetzung folgt!

Gruß Barnee
 
Zurück
Oben