# Gemeinsamkeiten C <->Step7



## Markus (27 Januar 2006)

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.


----------



## Barnee (27 Januar 2006)

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

```
X := SHL (IN := Y, N := 2);
```
in C sieht das so aus:

```
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:

```
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:

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


----------



## Barnee (4 Februar 2006)

*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:

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

```
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:

```
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:

```
void eine_Funktion (void);
```
und mit einem definierten Rückgabewert (hier z.b. _long_):

```
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:

```
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:

```
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.

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


```
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:

```
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:

```
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:

```
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 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:

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


----------



## Barnee (5 Februar 2006)

*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:

```
Boolean flag;
long    eineZahl;

(long)flag = (eineZahl >> 11) & 0x0001;
```
alternativ hätte man dazu auch schreiben können:

```
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:

```
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:

```
(byte)flag03 = (eineZahl >> 11) & 0x0001;
```
wäre somit in S7-C nicht zulässig, die einzig zulässige Konstruktion wäre ausschließlich durch:

```
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:

```
(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


----------



## Barnee (9 Februar 2006)

*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_:

```
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;

```
L   temp
T   S7-C-Variable
```

-- Bei _VAR_INOUT_ und einer S7-C-Variable vom Typ _SInt32_:

```
L   S7-C-Variable
T   temp
```
_temp_ wird dann als Aktualoperand verwendet.

```
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:

```
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:

```
SInt16     einInteger, b;
S7BCD16    eineBCDZahl;

eineBCDZahl = (S7BCD16)(einInteger * b);
```
Der S7-C-Compiler macht daraus folgendes:

```
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:

```
SInt16     einShort, b;
SInt32     einLong;

einLong = einShort * b;
```
dann sollte der S7-C-Compiler folgendes daraus machen:

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


----------

