diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ed3ca2..a12483c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,15 +6,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] -## 0.5.0-beta.3 - 2018-03-08 +## [0.5.0] - 2018-03-29 ### Added - CHANGELOG.md added to the project folder - Library Reference Manual (RefManual.md) added to the project folder - DTE Interface description (DTE-Interface.md) added to the project folder -- LogoClient::GetPlcDateTime and WritePlcDateTime -- LogoClient::ReadByte, WriteByte and CpuError -- Example ReadClockDemo.ino added to the project folder -- Example WriteClockDemo.ino added to the project folder +- Date/Time functions: LogoClient::GetPlcDateTime and WritePlcDateTime +- System info function: LogoClient::GetOrderCode +- Private functions: LogoClient::GetOrderCode, WriteByte and CpuError +- Security function: LogoClient::GetProtection +- Example ReadClockDemo.ino and WriteClockDemo.ino added to the project folder +- Example PlcInfoDemo.ino added to the project folder ### Changed - Customization of CPU Exception Codes for LogoClient::CpuError @@ -118,7 +120,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - LogoPG.cpp added to the project folder - keywords.txt added to the project folder -## 0.1.0 - 2018-02-07 +## 0.1.0-pre-alpha - 2018-02-07 ### Added - Initial version created - LICENCE.md added to the project diff --git a/README.md b/README.md index 952f4c6..6b85c32 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Information about the API is described in the document [LOGO! PG Library Referen All information about the protocol is described in a separate document: [LOGO! PG Protocol Reference Guide](/extras/docs/PG-Protocol.md) ## Releases -The current library version is [0.4.3](https://github.com/brickpool/logo/releases). This version is not the final version, it is a release candidate, with patchlevel 3. +The current library version is [0.5.0](https://github.com/brickpool/logo/releases). This version is not the final version, it is a release candidate, with patchlevel 0. ## Examples This directory contains the library and some examples that illustrate how the library can be used. The examples were tested with an Arduino UNO. Other hardware has not been tried. @@ -18,6 +18,7 @@ This directory contains the library and some examples that illustrate how the li - [CyclicReading.ino](/examples/CyclicReading/CyclicReading.ino) Cyclic reading of inputs, outputs and flags. The example uses the same function as the LOGO!Soft Routine _Online Test_. - [ReadClockDemo.ino](/examples/ReadClockDemo/ReadClockDemo.ino) The example reads the date and time from the _LOGO!_ controller. - [WriteClockDemo.ino](/examples/WriteClockDemo/WriteClockDemo.ino) The example writes a date and time to the _LOGO!_ controller. +- [PlcInfoDemo.ino](/examples/PlcInfoDemo/PlcInfoDemo.ino) The example reads the Ident Number, Firmware version and Protection level from the PLC. ## Dependencies - _LOGO!_ controller version __0BA4__, __0BA5__ or __0BA6__, e.g. part number `6ED1052-1MD00-0BA6` diff --git a/examples/PlcInfoDemo/PlcInfoDemo.ino b/examples/PlcInfoDemo/PlcInfoDemo.ino new file mode 100644 index 0000000..8452049 --- /dev/null +++ b/examples/PlcInfoDemo/PlcInfoDemo.ino @@ -0,0 +1,123 @@ +#include +#include "LogoPG.h" + +const byte rxPin = 2; +const byte txPin = 3; + +// set up the SoftwareSerial object +CustomSoftwareSerial LogoSerial(rxPin, txPin); +// set up the LogoClient object +LogoClient LOGO(&LogoSerial); + +void setup() { + // initialize digital pin LED_BUILTIN as an output and turn the LED off + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, LOW); + + // Init Monitor interface + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + + // Start the SoftwareSerial Library + LogoSerial.begin(9600, CSERIAL_8E1); + // Setup Time, 1s. + delay(1000); + Serial.println(""); + Serial.println("Cable connected"); + if (LogoSerial.isListening()) + Serial.println("Softserial is listening !"); +} + +void loop() +{ + int Result; + + // Connection + while (!LOGO.Connected) + { + if (!Connect()) + delay(2000); + } + + // System info functions + TProtection Info; + Result = LOGO.GetProtection(&Info); + if (Result == 0) + { + if (Info.sch_rel > 0) + { + Serial.print("CPU protection level:"); + Serial.print(" sch_schal="); + Serial.print(Info.sch_schal); + Serial.print(", sch_par="); + Serial.print(Info.sch_par); + Serial.print(", sch_rel="); + Serial.print(Info.sch_rel); + Serial.print(", bart_sch="); + Serial.println(Info.sch_rel); + // 'anl_sch' is always 0 + } + } + else + CheckError(Result); + + // Security functions + TOrderCode OrderCode; + Result = LOGO.GetOrderCode(&OrderCode); + if (Result == 0) + { + Serial.print("Order code: "); + Serial.println(OrderCode.Code); + // print firmware version, if the values are valid + if (OrderCode.V1 > 0 && OrderCode.V2 > 0 && OrderCode.V3 > 0) + { + char Version[] = "V0.00.00"; + sprintf(Version, "V%d.%02d.%02d", + OrderCode.V1, + OrderCode.V2, + OrderCode.V3 + ); + Serial.print("Firmware version: "); + Serial.println(Version); + } + } + else + CheckError(Result); + + delay(10000); +} + +bool Connect() +{ + int Result = LOGO.Connect(); + Serial.println("Try to connect with LOGO"); + if (Result == 0) + { + // turn the built-in LED on + digitalWrite(LED_BUILTIN, HIGH); + Serial.println("Connected!"); + Serial.print("PDU Length = "); + Serial.println(LOGO.GetPDULength()); + } + else + { + Serial.println("Connection error!"); + } + return Result == 0; +} + +void CheckError(int ErrNo) +{ + Serial.print("Error No. 0x"); + Serial.println(ErrNo, HEX); + + // Checks if it's a LOGO Error => we need to disconnect + if (ErrNo & 0x00FF) + { + // turn the built-in LED off + digitalWrite(LED_BUILTIN, LOW); + Serial.println("LOGO ERROR, disconnecting."); + LOGO.Disconnect(); + } +} + diff --git a/examples/WriteClockDemo/WriteClockDemo.ino b/examples/WriteClockDemo/WriteClockDemo.ino index f207ec3..4cd615b 100644 --- a/examples/WriteClockDemo/WriteClockDemo.ino +++ b/examples/WriteClockDemo/WriteClockDemo.ino @@ -5,7 +5,7 @@ const byte rxPin = 2; const byte txPin = 3; -const unsigned long START_TIME = 1357041600; // Jan 1 2013 +const unsigned long MY_TIME = 1522238400; // Mar 28 2018 // set up the SoftwareSerial object CustomSoftwareSerial LogoSerial(rxPin, txPin); @@ -29,6 +29,10 @@ void setup() { Serial.println("Cable connected"); if (LogoSerial.isListening()) Serial.println("Softserial is listening !"); + + // set the system time to the give time MY_TIME + if (timeStatus() != timeSet) + setTime(MY_TIME); } void loop() @@ -50,15 +54,14 @@ void loop() { Serial.println("STOPPING THE PROG"); LOGO.PlcStop(); - Serial.print("DEFAULT: "); - Result = LOGO.SetPlcDateTime(START_TIME); - breakTime(START_TIME, DateTime); - } - else - { - Serial.print("LOGO Clock: "); - Result = LOGO.GetPlcDateTime(&DateTime); + if (LogoClockStatus() != timeSet) + { + Serial.print("Set Time: "); + Result = LOGO.SetPlcDateTime(now()); + } } + Serial.print("LOGO Clock: "); + Result = LOGO.GetPlcDateTime(&DateTime); if (Result == 0) { DisplayClock(DateTime); @@ -129,3 +132,19 @@ void CheckError(int ErrNo) LOGO.Disconnect(); } } + +timeStatus_t LogoClockStatus() +{ + // Su 00:00 01-01-2003 + const unsigned long START_TIME = 1041379200; + const uint8_t START_WDAY = 1; + const int op_mode; + + TimeElements tm; + if (LOGO.GetPlcDateTime(&tm) == 0) { + if (!(makeTime(tm) == START_TIME && tm.Wday == START_WDAY)) + return timeSet; + } + return timeNotSet; +} + diff --git a/extras/de-DE/Dekodierung/0BA5-Dekodierung.md b/extras/de-DE/Dekodierung/0BA5-Dekodierung.md index fddc52a..37ace04 100644 --- a/extras/de-DE/Dekodierung/0BA5-Dekodierung.md +++ b/extras/de-DE/Dekodierung/0BA5-Dekodierung.md @@ -1,7 +1,7 @@ # Undocumented LOGO! __0BA5__ ## Aufbau, Funktionsweise und Programmierung -Ausgabe Am +Ausgabe An März 2018 @@ -18,6 +18,7 @@ _Siemens_ und _LOGO!_ sind eingetragene Marken der Siemens AG. Wertvolle Informationen finden Sie in den folgenden Veröffentlichungen: * [LOGO! Handbuch Ausgabe 05/2006](http://www.google.de/search?q=A5E00380834-02), Bestellnummer 6ED1050-1AA00-0AE6 * [SPS Grundlagen](http://www.sps-lehrgang.de/ "SPS Lehrgang") + * [Translation Method of Ladder Diagram on PLC with Application to an Manufacturing Process](http://www.google.de/search?q=A+Translation+Method+of+Ladder+Diagram+on+PLC+with+Application+to+an+Manufacturing+Process) von Hyung Seok Kim und weiteren Autoren Einzelheiten zum _LOGO!_-Adresslayout finden Sie in den folgenden Veröffentlichungen: * neiseng @ [https://www.amobbs.com](https://www.amobbs.com/thread-3705429-1-1.html "Siemens LOGO! Pictures") dekodierte den Datenadressraum einer __0BA5__ (@post 42) @@ -77,61 +78,98 @@ Das _LOGO!_ Basismodul ist das Herzstück. In dem Basismodul wird das Schaltprog Innerhalb des Basismoduls sind verschiedene Systembereiche untergebracht, damit die verschieden Vorgänge ablaufen können. Im folgenden Bild ist der Aufbau schematisch dargestellt. ``` -.------------------------------------. -| PE O O O O O O O O | -+------------------------------------+ -| PG-Schnittstelle ### -| .------------.----------.--------. | -| | Steuereinh.| Speicher | Zähler | | -| '-----o------'-----o----'----o---' | -| =====|=====Busverbindung====|===### -| .-----o------.-----o----.----o---. | -| | Rechenwerk | Merker | Zeiten | | -| '------------'----------'--------' | -+------------------------------------+ -| PA O O O O O O O O | -'------------------------------------' + .------------------------. + | Eingänge | + '------------------------' + ..........|..|..|..|..|..|..|..|.......... + v v v v v v v v +.---+------------------------------------. +| | PE O O O O O O O O | : +| +------------------------------------+ : +| | .-------> PG-Schnittstelle ### <--:--> Programmiergerät +| | v | : +| | .------------.----------.--------. | : +| | | Steuereinh.| Speicher | Zähler | | : +| P | '-----o------'-----o----'----o---' | : +| S | =====|=====Busverbindung====|===### <--:--> Erweiterungsmodule +| U | .-----o------.-----o----.----o---. | : +| | | Rechenwerk | Merker | Zeiten | | : +| | '------------'----------'--------' | +| | | +| +------------------------------------+ +| | PA O O O O O O O O | +'---+------------------------------------' + ..........|..|..|..|..|..|..|..|.......... + v v v v v v v v + .------------------------. + | Ausgänge | + '------------------------' + ``` >Abbild: Schema Systembereiche Zu den Systembereichen eines Basismoduls gehören: -- Steuereinheit -- Rechenwerk -- Speicher -- Prozessabbild der Eingänge (_PE_) -- Prozessabbild der Ausgänge (_PA_) -- Zeiten -- Zähler -- Merker +- Steuereinheit +- Rechenwerk +- Speicher +- Prozessabbild der Eingänge (_PE_) +- Prozessabbild der Ausgänge (_PA_) +- Zeiten +- Zähler +- Merker + +Zur Peripherie gehören +- Stromversorgung (_PSU_) +- PG-Schnittstelle und Progammiergerät (PC mit LOGO!Soft Comfort) +- Eingänge (Eingangsklemmen) +- Ausgänge (Relais: LOGO! R/RC/RCo, Transitoren:_LOGO!_ 24/24o) +- Erweiterungsmodule (DM8, DM16, AM2, CM) + ### Steuereinheit Die Steuereinheit wird verwendet, um den gesamten Ablauf innerhalb des Basismoduls zu koordinieren. Dazu gehören u.a. die Koordination der internen Vorgänge und der Datentransport. -Die Befehle vom Schaltprogramm werden aus dem Speicher ausgelesen und Schrittweise ausgeführt. Die Verarbeitung erfolgt in mehreren Stufen. Zuerst erfolgt eine Initialisierung des Programmzyklus. Als nächstes werden die Zustände der Eingänge ausgelesen und in das Prozessabbild der Eingänge _PE_ geschrieben, um anschließend die Anweisungsbefehle für die Funktionen auszuführen, wobei weitere Werte wie Zeiten, Zählern und Merker berücksichtigt werden. Das Ergebnis der Programmbearbeitung wird schließlich in das Prozessabbild der Ausgänge _PA_ geschrieben, um die Ausgänge neu zu setzen. Als letzter Schritt wird der interne Status aktualisiert und der Zyklus beginnt erneut. - -``` -.----->-----. -| V -| .--------o------------------. -| | Initialisierung | -| o---------------------------o -| | Eingänge auf PE abbilden | -| o---------------------------o -| | Programmausführung unter | -^ | Nutzung PE, T, Cnt, Flags | -| | etc. und schreiben in PA | -| o---------------------------o -| | PA auf Ausgänge abbilden | -| o---------------------------o -| | Status aktualisieren | -| '--------o------------------' -| V -'-----<-----' -``` ->Abb: Schematische Darstellung eines Programmzyklus +Die Übersetzung des Schaltprogramms in ausführbare Anweisungen erfolgt sobald _LOGO!_ in den Betriebszustand _RUN_ wechselt. Die Verknüpfungen vom Schaltprogramm werden aus dem Ladespeicher gelesen, in einem Zwischencode (Steuerungsprogramm) übersetzt und in den Arbeitsspeicher gehalten (1). + +Kennzeichen einer jeden SPS ist die über die Firmware gesteuerte zyklische Programmabarbeitung, so auch bei der LOGO! Kleinsteuerung. Im Betriebszustand _RUN_ führt die LOGO! die Anweisungem vom Schaltprogramm zyklisch aus. Die Steuereinheit ruft bei jedem Zyklus das Steuerungsprogramm auf führt die Anweisungsbefehle Schrittweise aus. Typische Zykluszeiten liegen im Bereich von etwa 100 ms (Zykluszeit je Funktion < 0,1ms). + +Ein Programzyklus wird in drei Schritten ausgeführt: +1. Zuerst erfolgt eine Initialisierung des Programmzyklus. Hierbei werden Zeiten gestartet, die Ausgänge anhand des Prozessabbildes der Ausgänge _PA_ gesetzt, sowie die Zustände der Eingänge ausgelesen und in das Prozessabbild der Eingänge _PE_ übernommen. +2. Anschließend werden die Anweisungsbefehle vom Steuerungsprogramm sequentiell abgearbeitet, wobei weitere Werte wie Zeiten, Zählern und Merker berücksichtigt werden. +3. Als letzter Schritt wird die PG-Schnittstelle für Senden/Empfangen von Daten bedient, der interne Status aktualisiert und der Zyklus beginnt erneut. + +``` +.------->--------. +| V +| .-------------o-------------. ... +| | Zeiten starten | : +| |...........................| : +| | PA auf Ausgänge abbilden | Initialisierung +| |...........................| : +| | Eingänge auf PE abbilden | : +| o---------------------------o ... +| | Programmausführung unter | : +| | von Nutzung PE, PA sowie | : +| | Zeiten, Zähler und Merker | : +^ | 1. Anweisung | Ausführung, +| | 2. Anweisung | Steuerungsprogramm +| | ... | : +| | n. Anweisung | : +| o---------------------------o ... +| | Senden/Empfangen von | : +| | Daten der | : +| | PG-Schnittstelle | Daten austauschen, +| |...........................| Status aktualisieren +| | Status aktualisieren | : +| '-------------o-------------' ... +| V +'-------<--------' +``` +>Abb: Schematische Darstellung Prozessabbild Die Steuereinheit ist über die Busverbindung mit den anderen Systembereichen wie Rechenwerk, Zeiten, Zähler usw. verbunden. Unmittelbar nach Anlegen der Netzspannung werden die remanenten Zähler, Zeiten und Merker sowie die Prozessabbilder der Eingänge und Ausgänge gesetzt. + ### Rechenwerk Der Begriff wird häufig synonym mit Arithmetisch-Logischen Einheit (_ALU_) gebraucht, genau genommen stellt eine _ALU_ jedoch lediglich die zentrale Komponente eines Rechenwerks dar, das zusätzlich aus einer Reihe von Hilfs- und Statusregistern besteht. Die _ALU_ selbst enthält hingegen keine Registerzellen und stellt somit ein reines Schaltnetz dar. @@ -144,7 +182,7 @@ Beim Ausführen von Operationen verknüpft die _ALU_ zwei Binärwerte (Akku1 und ^ | | | v v -|--- Akku1 16bit ---| |--- Akku1 16bit ---| +|--- Akku1 16bit ---| |--- Akku2 16bit ---| |++++*++++|++++*++++|-. .-|++++*++++|++++*++++| ^ | | | v v .-------------. @@ -188,7 +226,7 @@ Die Zustände der Eingänge und Ausgänge werden in den Speicherbereichen _PE_ u ### Programmierschnittstelle (PG) Die _LOGO!_ Kleinsteuerung besitzt eine PG-Schnittstelle, um die Verbindung zum Programmiergerät/PC herzustellen. Im Betriebszustand _STOP_ kann über die Schnittstelle das Schaltprogramm zwischen PC und Basismodul (und umgekehrt) übertragen werden, sowie Parameter (Echtzeituhr, Displayanzeige, Passwort, Programmname) gesetzt werden. Im Betriebszustand _RUN_ können mittels der _Online-Test_-Funktion aktuelle Werte (Eingänge, Ausgänge, Zustände, Zeiten, Zähler etc.) ausgelesen werden. Die Schnittstelle dient leider nicht zum Anschluss von Erweiterungsbaugruppen oder Bedien- und Beobachtungsgeräten wie bei einer Standard SPS. -_N_ nicht unterstützt, _R_ lesen unterstützt, _W_ scheiben unterstützt +_N_ nicht unterstützt, _R_ lesen unterstützt, _W_ schreiben unterstützt Funktion, Datenelement| _RUN_ | _STOP_ ----------------------|-------|------- @@ -223,9 +261,7 @@ Das Bussystem kann unterteilt werden in ein Rückwandbus und Peripheriebus. Der Hinsichtlich der Spannungsklassen gelten Regeln, die beim Verbinden mit dem _P-Bus_ beachtet werden müssen! Detaillierte Informationen zur Integration der Erweiterungs- bzw. Kommunikationsmodule (Einbaureihenfolge etc.) finden sich im _LOGO!_ Handbuch. ## Speicherarchitektur -Der Speicherbereich innerhalb eines Basismoduls ist in mehrere Bereiche aufgeteilt, wobei jeder Speicherbereich eigene Aufgaben erfüllt. Vereinfacht gesagt besteht der Speicher im Basismodul aus dem Systemspeicher, dem Ladespeicher und dem Arbeitsspeicher. - -Alle Konfigurationsdaten sind im Basismodul gespeichert. Unter anderem das Anwenderprogramm, sowie die Symboltabelle und evtl. noch Meldetexte und Blocknamen. Das Anwenderprogramm besteht aus Steuerungsbefehlen und Steuerungsdaten, die in Programmbausteinen und Datenbausteinen untergebracht sind. +Der Speicherbereich innerhalb eines Basismoduls ist in mehrere Bereiche aufgeteilt, wobei jeder Speicherbereich eigene Aufgaben erfüllt. Vereinfacht gesagt besteht der Speicher im Basismodul aus dem Ladespeicher, dem Arbeitsspeicher und dem Systemspeicher. Zusätzlich ist die Firmware mit untergebracht. Die Firmware sorgt dafür, dass das Programm in der korrekten Reihenfolge abgearbeitet wird. Für die Firmware ist ein eigener Festwertspeicher im Basismodul vorhanden. @@ -235,17 +271,39 @@ Folgend die Auflistung der verwendeten Speichertypen: - [Arbeitsspeicher](#arbeitsspeicher) - [Systemspeicher](#systemspeicher) +Die Programmbearbeitung erfolgt ausschließlich im Bereich von Arbeitsspeicher und Systemspeicher. Die folgende Abbildung veranschaulicht das Zusammenspiel: +``` + Ladespeicher Arbeitsspeicher Systemspeicher +.-----------------. .---------------. .-------------------. +| Klemmenspeicher |--1->| Interpreter |--. | Prozessabb. Eing. | +| Programmzeilen- | |...............| | |...................| +| speicher | .->| Funktionen | | | Prozessabb. Ausg. | +|.................| | |...............| 2 |...................| +| Passwort | 5 | Programmdaten | | | Zeiten (T) | +| Programmname | | | Anweisungs- | | | Zähler (C) | +| ... | '--| befehle (4) |<-' | Merker (M) | +| | | ... | |...................| +| | | | | Stack | +| | | |<-3->|...................| +| | | | | Statusdaten | +| | | | | ... | +'-----------------' '---------------' '-------------------' +``` +> Abb. Speicher Blockdiagramm + ### Festwertspeicher Der Festwertspeicher ist ein Nur-Lese-Speicher (engl. Read Only Memory, kurz _ROM_). Hierauf befindet sich die _LOGO!_ Firmware, welche vom Hersteller Siemens stammt. _Read Only_ deshalb, weil der Speicher nicht gelöscht oder geändert werden kann und zudem nicht flüchtig ist. Bei Spannungsausfall bleiben die Daten erhalten. ### Ladespeicher -Über die Schnittstelle zum Programmiergerät werden die gesamten Schaltdaten inkl. der Konfiguration in den Ladespeicher geladen. Der Ladespeicher kann zusätzlich auf sogenannte Memory Cards gesichert werden. Die Daten bleiben auch bei Spannungsausfall erhalten. +Alle Konfigurationsdaten sind im Ladespeicher vom Basismodul gespeichert. Unter anderem das Anwenderprogramm, sowie die Funktionsparameter und evtl. noch Passwort, Programmname, Meldetexte und Blocknamen. Der Ladespeicher ist ein digitaler Flash-EEPROM Speicherbaustein. Die Daten bleiben auch bei Spannungsausfall erhalten. Über die Schnittstelle zum Programmiergerät werden die Schaltdaten inkl. der Konfiguration (kurz Schaltprogram) in den Ladespeicher geladen. + +### Arbeitsspeicher +Beim Arbeitsspeicher (engl. Random Access Memory, kurz _RAM_) handelt es sich um einen flüchtiger Speicher, d.h. der Inhalt geht nach einem Spannungsausfall verloren. Er ist als ein Schreib-/Lesespeicher konzipiert. -### Arbeitsspeicher -Beim Arbeitsspeicher (engl. Random Access Memory, kurz _RAM_) handelt es sich um einen flüchtiger Speicher, d.h. der Inhalt geht nach einem Spannungsausfall verloren. Er ist als ein Schreib-/Lesespeicher konzipiert. Vom Ladespeicher werden die ablaufrelevanten Teile des Programms in den Arbeitsspeicher geladen. Dazu zählt insbesondere der Klemmenspeicher und Programmzeilenspeicher (vergl. _S7_ Programmbausteine) sowie die Speicherbereiche für Passwort, Programmname, Meldetexte und Blocknamen (vergl. _S7_ Datenbausteine). +Im Arbeitsspeicher werden die ablaufrelevanten Programm- und Datenbausteine sowie Konfigurationsdaten abgelegt (4). Der Arbeitsspeicher dient zur Abarbeitung, sowie zur Bearbeitung der Daten des Steuerungsprogramms (5). Dazu werden die vom Ladespeicher ablaufrelevanten Teile des Schaltrogramms beim Wechsel vom Betriebszustand _STOP_ nach _RUN_ geladen (1) und von einem Interpreter übersetzt und das Ergebnis als Steuerungsprogramm im Arbeitsspeicher gespeichert (2). -### Systemspeicher -Im Systemspeicher werden die Zustände der Eingänge und Ausgänge über das Prozessabbild (_PE_ und _PA_), die Zeiten, Zähler, Merker und der Daten-Stack gespeichert. +### Systemspeicher +Der Systemspeicher ist ein flüchtiger Speicher (_RAM_) und in Operandenbereiche aufgeteilt. Im Systemspeicher werden die Zustände der Eingänge und Ausgänge über das Prozessabbild (_PE_ und _PA_), die Zeiten (_T_), Zähler (_C_), Merker (_M_) und der Stack und Statusdaten gespeichert. Während eine Programzyklus werden auf die Datem im Systemspeicher zugegriffen und Programmzustände aktualisiert (3). ### Adressierung _LOGO!_ __0BA5__ nutzt eine 16-Bit-Adressierung. Vereinfacht dargestellt bedeutet dies, dass die Speicherarchitektur so ausgelegt ist, dass jede Speicherstelle durch einen 16Bit-Zeiger (also 2 Byte) direkt adressiert werden kann. Die _LOGO!_ Kleinsteuerung __0BA5__ nutzt teilweise eine Segmentierung, sodass auch 8Bit-Zeiger (Verweise) zu Anwendung kommen, um eine Speicherstelle zu adressieren. @@ -282,6 +340,7 @@ Beispiel: >Abb: Byte-Reihenfolge im Speicher bei Little-Endian ## Adressübersicht +Das folgende Adresslayout ist ein Abbild des dekodierten Ladespeichers einer _LOGO!_ __0BA5__. Auf den Lagespeicher kann (bis auf wenige Ausnahmen) nur im Betriebszustand _STOP_ zugegriffen werden. ### Parameter | Beispiel | Adresse | Länge | | | @@ -332,8 +391,8 @@ Beispiel: | Beispiel | Adresse | Länge | | | |-------------|-------------|-------|---|--------------------------------------------------------| |           | 4100       | 1     | W |                                                        | -| 01 43 00 00 | [4300](#4400)       | 1     | W | Werte speichern: RTC=00, S/W=01         | -| 01 44 00 00 | [4400](#4400)       | 1     | W | Werte laden: RTC=00, S/W=01             | +| 01 43 00 00 | [4300](#FB00)       | 1     | W | Werte speichern: RTC=00, S/W=01         | +| 01 44 00 00 | [4400](#FB00)       | 1     | W | Werte laden: RTC=00, S/W=01             | ### Systemfunktion Passwort | Beispiel | Adresse | Länge | | | @@ -344,7 +403,7 @@ Beispiel: ### Parameter Echtzeituhr | Beispiel | Adresse | Länge | | | |-------------|-------------|-------|---|--------------------------------------------------------| -| 02 FB 00   | [FB00](#4400) - FB05 | 6     |   | Echtzeituhr, Sommer-/Winterzeitumstellung              | +| 02 FB 00   | [FB00](#FB00) - FB05 | 6     |   | Echtzeituhr, Sommer-/Winterzeitumstellung              | __Hinweis:__ @@ -363,7 +422,7 @@ Speicherbereich: 0552, 1 Byte Zugriffsmethode: Lesen und Schreiben -### Darstellungsformat im Speicher +### Darstellungsformat im Ladespeicher ``` send> 02 05 52 recv< 06 @@ -397,7 +456,7 @@ recv< 06 Wertebereich: 0.00 - 9.99 -### Darstellungsformat im Speicher +### Darstellungsformat im Ladespeicher ``` 00 [C4 03] [D5 01] Val [AQ1 ] [AQ2 ] @@ -512,7 +571,7 @@ FF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 // (40x) ... ``` -### Darstellungsformat im Speicher +### Darstellungsformat im Ladespeicher ``` 03 04 20 20 20 20 00 00 00 00 00 00 00 00 00 00 02 04 0B 2B 00 00 4C 65 6E 3A 20 20 00 00 00 00 @@ -653,7 +712,7 @@ Read Byte | Wert | Beschreibung ### Passwort R/W Ein Schaltprogramm mit Passwort sollte geschützt bleiben. Das Knacken eines Passwortes ist nicht Zielsetzung dieser Beschreibung, daher sind die Systemfunktionen und Befehlsfolgen für das Auslesen und Setzen des Passwortes hier nicht beschrieben. -## Echtzeituhr, S/W +## Echtzeituhr, S/W Alle Datums- und Zeitwerte sind Bytewerte. Die Echtzeituhr (RTC) startet nach längerem Stromausfall oder nach Speicherverlust mit folgendem Datum und folgender Zeit: - Datum: `01-Jan-2003` - Zeit: `00:00:00` @@ -1521,7 +1580,7 @@ Die Flags für Remananz und Parameterschutz sowie die Beschaltung von Eingang _T RAM/Rem/Online: 8/3/4 -### Darstellungsformat im Speicher +### Darstellungsformat im Ladespeicher ``` 21 40 01 00 7A 80 00 00 FF FF FF FF 21 40 [01 00] [7A 80] 00 00 FF FF FF FF @@ -1667,7 +1726,7 @@ Die Flags für Remanenz und Parameterschutz sowie die Beschaltung von Eingang _T RAM/Rem/Online: 12/3/? -### Darstellungsformat im Speicher +### Darstellungsformat im Ladespeicher ``` 22 80 01 00 02 00 1F CD 00 00 00 00 22 80 [01 00] [02 00] [1F CD] 00 00 00 00 @@ -1703,7 +1762,7 @@ Die Flags für Remanenz und Parameterschutz sowie die Beschaltung von Eingang _T RAM/Rem/Online: 12/3/6 -### Darstellungsformat im Speicher +### Darstellungsformat im Ladespeicher ``` 2F 40 00 00 03 80 E9 43 00 00 FF FF @@ -1796,7 +1855,7 @@ Die Flags für Remanenz und Parameterschutz sowie die Beschaltung von Eingang _S RAM/Rem/Online: 12/1/? -### Darstellungsformat im Speicher +### Darstellungsformat im Ladespeicher ``` 23 00 02 00 FF FF FF FF 00 00 00 00 23 00 [02 00] [FF FF] [FF FF] 00 00 00 00 @@ -1827,7 +1886,7 @@ __Hinweis:__ Da die _LOGO!_ Kleinsteuerung Typ 24/24o keine Uhr besitzt, ist die RAM/Rem/Online: 20/-/5 -### Darstellungsformat im Speicher +### Darstellungsformat im Ladespeicher ``` 24 40 F2 00 A0 00 FF FF FF FF FF FF FF FF 2A 00 00 00 00 00 24 40 [F2 00] [A0 00] [FF FF] [FF FF] [FF FF] [FF FF] 2A 00 00 00 00 00 @@ -1931,7 +1990,7 @@ Mit der Funktion _Selbsthalterelais_ wird der Ausgang _Q_ abhängig vom Signalzu RAM/Rem/Online: 8/1/? -### Darstellungsformat im Speicher +### Darstellungsformat im Ladespeicher ``` 25 00 0A 80 FF FF 00 00 25 00 [0A 80] [FF FF] 00 00 @@ -1955,7 +2014,7 @@ R: Eingang R (Co oder GF/SF) RAM/Rem/Online: 12/3/? -### Darstellungsformat im Speicher +### Darstellungsformat im Ladespeicher ``` 27 80 02 00 FF FF 36 81 00 00 00 00 27 80 [02 00] [FF FF] [36 81] 00 00 00 00 @@ -1991,7 +2050,7 @@ Die Flags für Remanenz und Parameterschutz sowie die Beschaltung der Eingänge RAM/Rem/Online: 24/5/10 -### Darstellungsformat im Speicher +### Darstellungsformat im Ladespeicher ``` send> 05 0F 00 00 18 recv< 06 @@ -2103,7 +2162,7 @@ Die aktuelle Zeit _Ta_ benennt die Zeitdauer des letzten Flankenwechsels (Wechse RAM/Rem/Online: 12/3/? -### Darstellungsformat im Speicher +### Darstellungsformat im Ladespeicher ``` 2D 40 02 00 FF FF 02 80 02 80 00 00 2D 40 [02 00] [FF FF] [02 80] [02 80] 00 00 @@ -2131,7 +2190,7 @@ Par: RAM/Rem/Online: 20/- -### Darstellungsformat im Speicher +### Darstellungsformat im Ladespeicher ``` 39 40 FD 00 92 00 C8 00 66 00 00 00 00 00 00 00 00 00 00 00 39 40 [FD 00] [92 00] [C8 00 66 00 00 00 00 00 00 00 00 00 00 00 @@ -2364,9 +2423,9 @@ Pure | _LOGO!_ 230 RCo | 6ED1052-2FB00-0BA5 - __OR__: Oder-Verknüpfung - __OT__: Aufgelaufene Gesamtbetriebszeit - __p__ (_position_): Anzahl der Nachkommastellen -- __PA__: Prozessabbild der Ausgänge +- __PA__: Prozessabbild der Ausgänge - __Par__: Parameter -- __PE__: Prozessabbild der Eingänge +- __PE__: Prozessabbild der Eingänge - __PV__: Regelgröße - __Q__: Digitaler Ausgang - __R__ (_Reset_): Rücksetzeingang diff --git a/extras/docs/RefManual.md b/extras/docs/RefManual.md index 66db193..3bba762 100644 --- a/extras/docs/RefManual.md +++ b/extras/docs/RefManual.md @@ -1,6 +1,6 @@ # LOGO! PG Library Reference Manual -Rev. Af +Rev. Ag March 2018 @@ -189,14 +189,15 @@ Gets CPU order code and version info. The `Info` argument is an _C_ structure defined in the library: ``` typedef struct { - char Code[18]; // Order Code + char Code[19]; // Order Code byte V1; // Version V1.V2.V3 byte V2; byte V3; } TOrderCode; ``` -The Order code is a _C_ string such as `6ED1052-xxx00-0BA6`. +The Order code is a null terminated _C_ string such as `6ED1052-xxx00-0BA6`. +Please note, for the _LOGO!_ __0BA4__ device and in Operation mode _STOP_ , the firmware can not be read out (V1 to V3 have a value of `0`). Returns a `0` on success or an `Error` code (see Errors Code List [below](#error-codes)). @@ -273,8 +274,8 @@ Field Values: Protection | Values | Description --- | --- | --- -`sch_schal` | 1,2,3 | Protection level set with the mode selector (1:STOP, 2:RUN,no password, 3:RUN,password set) -`sch_par` | 0,1 | Password level (0: no password) +`sch_schal` | 1,2,3 | Protection level set with the mode selector (1:STOP, 2:RUN, 3:RUN and password set) +`sch_par` | 0,1 | Password level (0: no password or cannot be determined) `sch_rel` | 0,1,2,3 | Valid protection level of the CPU `bart_sch` | 1,3 | Mode selector setting (1:RUN, 3:STOP, 0:undefined or cannot be determined) `anl_sch` | 0 | Startup switch setting (0:undefined, does not exist or cannot be determined) diff --git a/keywords.txt b/keywords.txt index 7f8a7db..3446b13 100644 --- a/keywords.txt +++ b/keywords.txt @@ -29,6 +29,7 @@ PlcStop KEYWORD2 GetPlcStatus KEYWORD2 GetPlcDateTime KEYWORD2 SetPlcDateTime KEYWORD2 +GetOrderCode KEYWORD2 ####################################### # Constants (LITERAL1) diff --git a/src/LogoPG.cpp b/src/LogoPG.cpp index 7a12afd..df3555a 100644 --- a/src/LogoPG.cpp +++ b/src/LogoPG.cpp @@ -1,38 +1,38 @@ /* - * LogoPG library, Version 0.5.0-beta.3 - * - * Portion copyright (c) 2018 by Jan Schneider - * - * LogoPG provided a library under ARDUINO to read data from a - * Siemens(tm) LOGO! PLC via the serial programing interface. - * Only LOGO 0BA4 to 0BA6 are supported. - * + LogoPG library, Version 0.5.0-rc1 + + Portion copyright (c) 2018 by Jan Schneider + + LogoPG provided a library under ARDUINO to read data from a + Siemens(tm) LOGO! PLC via the serial programing interface. + Only LOGO 0BA4 to 0BA6 are supported. + *********************************************************************** - * - * This file is based on the implementation of SETTIMINO Version 1.1.0 - * The project SETTIMINO is an ARDUINO Ethernet communication library - * for S7 Siemens(tm) PLC - * - * Copyright (c) of SETTIMINO 2013 by Davide Nardella - * - * SETTIMINO is free software: we can redistribute it and/or modify - * it under the terms of the Lesser GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License. - * - * Please visit http://settimino.sourceforge.net/ for more information - * of SETTIMINO - * + + This file is based on the implementation of SETTIMINO Version 1.1.0 + The project SETTIMINO is an ARDUINO Ethernet communication library + for S7 Siemens(tm) PLC + + Copyright (c) of SETTIMINO 2013 by Davide Nardella + + SETTIMINO is free software: we can redistribute it and/or modify + it under the terms of the Lesser GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License. + + Please visit http://settimino.sourceforge.net/ for more information + of SETTIMINO + *********************************************************************** - * - * Changelog: https://github.com/brickpool/logo/blob/master/CHANGELOG.md - * + + Changelog: https://github.com/brickpool/logo/blob/master/CHANGELOG.md + */ #include "Arduino.h" -#ifdef _EXTENDED +#ifdef _EXTENDED #include "TimeLib.h" -#endif +#endif #include "LogoPG.h" #include @@ -45,7 +45,7 @@ */ -// LOGO 0BA4 Connection Request (Read Revision Byte) +// LOGO 0BA4 Connection Request (Read Ident Number) const byte LOGO4_CR[] = { 0x02, 0x1F, 0x02 }; @@ -82,14 +82,24 @@ const byte LOGO_MODE[] = { 0x55, 0x17, 0x17, 0xAA }; -#define ADDR_CLK_W_INIT 0x00FF4400UL +#define ADDR_CLK_W_GET 0x00FF4400UL #define ADDR_CLK_RW_DAY 0x00FFFB00UL #define ADDR_CLK_RW_MONTH 0x00FFFB01UL #define ADDR_CLK_RW_YEAR 0x00FFFB02UL #define ADDR_CLK_RW_MINUTE 0x00FFFB03UL #define ADDR_CLK_RW_HOUR 0x00FFFB04UL #define ADDR_CLK_RW_DOW 0x00FFFB05UL -#define ADDR_CLK_W_COMPL 0x00FF4300UL +#define ADDR_CLK_W_SET 0x00FF4300UL + +const PROGMEM char ORDER_CODE[Size_OC] = "6ED1052-xxx00-0BAx"; +#define ADDR_OC_R_ASC_V 0x00FF1F03UL // ASCII = V +#define ADDR_OC_R_MAJOR 0x00FF1F04UL // ASCII = X.__.__ +#define ADDR_OC_R_MINOR1 0x00FF1F05UL // ASCII = _.X_.__ +#define ADDR_OC_R_MINOR2 0x00FF1F06UL // ASCII = _._X.__ +#define ADDR_OC_R_PATCH1 0x00FF1F07UL // ASCII = _.__.X_ +#define ADDR_OC_R_PATCH2 0x00FF1F08UL // ASCII = _.__._X + +#define ADDR_PW_R_SET 0x00FF48FFUL // password exist (Y = 0x40, N = 0x00) #endif // _EXTENDED @@ -120,9 +130,9 @@ const byte LOGO_MODE[] = { // Store mapping in flash (program) memory instead of SRAM /* -// VM Mapping byte 851 to 922 -const PROGMEM byte VM_MAP_851_922[] = -{ + // VM Mapping byte 851 to 922 + const PROGMEM byte VM_MAP_851_922[] = + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 851-855 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 856-863 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 872-863 @@ -133,7 +143,7 @@ const PROGMEM byte VM_MAP_851_922[] = 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 904-911 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 912-919 0xFF, 0xFF, 0xFF, // 920-922 -} + } */ // VM 0BA7 Mapping for 0BA4 and 0BA5 @@ -150,7 +160,7 @@ const PROGMEM byte VM_MAP_923_983_0BA4[] = 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 968-975 : analoge flag 9-12 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 976-983 : analoge flag 13-16 }; - + // VM 0BA7 Mapping for 0BA6 const PROGMEM byte VM_MAP_923_983_0BA6[] = { @@ -166,25 +176,25 @@ const PROGMEM byte VM_MAP_923_983_0BA6[] = 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 976-983 : analoge flag 13-16 }; -/* -#ifdef _EXTENDED -// VM Mapping for diagnostic, date and time -const PROGMEM byte VM_MAP_984_1002[] = -{ +/* + #ifdef _EXTENDED + // VM Mapping for diagnostic, date and time + const PROGMEM byte VM_MAP_984_1002[] = + { 0xFF, // 984 : Diagnostic bit array 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 985-990 : RTC (YY-MM-DD-hh:mm:ss) 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 991-998 : S7 date time (YYYY-MM-DD-hh:mm:ss) 0xFF, 0xFF, 0xFF, 0xFF, // 999-1002 : S7 date time (nanoseconds) -} + } -// VM Mapping byte 1003 to 1023 -const PROGMEM byte VM_MAP_1003_1023[] = -{ + // VM Mapping byte 1003 to 1023 + const PROGMEM byte VM_MAP_1003_1023[] = + { 0xFF, 0xFF, 0xFF, 0xFF, // 1003-1007 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 1008-1015 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 1016-1023 -} -#endif // _EXTENDED + } + #endif // _EXTENDED */ TPDU PDU; @@ -199,7 +209,7 @@ bool LogoHelper::BitAt(void *Buffer, int ByteIndex, byte BitIndex) { byte mask[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}; pbyte Pointer = pbyte(Buffer) + ByteIndex; - + if (BitIndex > 7) return false; else @@ -208,13 +218,13 @@ bool LogoHelper::BitAt(void *Buffer, int ByteIndex, byte BitIndex) bool LogoHelper::BitAt(int ByteIndex, int BitIndex) { - if (ByteIndex < VM_0BA7_AREA || ByteIndex > VM_DDT_AREA-1) + if (ByteIndex < VM_0BA7_AREA || ByteIndex > VM_DDT_AREA - 1) return false; // https://www.arduino.cc/reference/en/language/variables/utilities/progmem/ - ByteIndex = pgm_read_byte_near(LogoClient::Mapping + ByteIndex- VM_0BA7_AREA); + ByteIndex = pgm_read_byte_near(LogoClient::Mapping + ByteIndex - VM_0BA7_AREA); - if (ByteIndex > MaxPduSize-Size_WR-1) + if (ByteIndex > MaxPduSize - Size_RD - 1) return false; else return BitAt(PDU.DATA, ByteIndex, BitIndex); @@ -228,12 +238,12 @@ byte LogoHelper::ByteAt(void *Buffer, int index) byte LogoHelper::ByteAt(int index) { - if (index < VM_0BA7_AREA || index > VM_DDT_AREA-1) + if (index < VM_0BA7_AREA || index > VM_DDT_AREA - 1) return 0; - + index = pgm_read_byte_near(LogoClient::Mapping + index - VM_0BA7_AREA); - - if (index > MaxPduSize-Size_WR-1) + + if (index > MaxPduSize - Size_RD - 1) return 0; else return ByteAt(PDU.DATA, index); @@ -242,18 +252,18 @@ byte LogoHelper::ByteAt(int index) word LogoHelper::WordAt(void *Buffer, int index) { word hi = (*(pbyte(Buffer) + index)) << 8; - return hi+*(pbyte(Buffer) + index+1); + return hi + *(pbyte(Buffer) + index + 1); } word LogoHelper::WordAt(int index) { - if (index < VM_0BA7_AREA || index > VM_DDT_AREA-2) + if (index < VM_0BA7_AREA || index > VM_DDT_AREA - 2) return 0; - + byte hi = pgm_read_byte_near(LogoClient::Mapping + index - VM_0BA7_AREA); - byte lo = pgm_read_byte_near(LogoClient::Mapping + index+1 - VM_0BA7_AREA); - - if (hi > MaxPduSize-Size_WR-1) + byte lo = pgm_read_byte_near(LogoClient::Mapping + index + 1 - VM_0BA7_AREA); + + if (hi > MaxPduSize - Size_RD - 1) return 0; else return (PDU.DATA[hi] << 8) + PDU.DATA[lo]; @@ -267,13 +277,13 @@ int LogoHelper::IntegerAt(void *Buffer, int index) int LogoHelper::IntegerAt(int index) { - if (index < VM_0BA7_AREA || index > VM_DDT_AREA-2) + if (index < VM_0BA7_AREA || index > VM_DDT_AREA - 2) return 0; - + byte hi = pgm_read_byte_near(LogoClient::Mapping + index - VM_0BA7_AREA); - byte lo = pgm_read_byte_near(LogoClient::Mapping + index+1 - VM_0BA7_AREA); - - if (hi > MaxPduSize-Size_WR-1) + byte lo = pgm_read_byte_near(LogoClient::Mapping + index + 1 - VM_0BA7_AREA); + + if (hi > MaxPduSize - Size_RD - 1) return 0; else return (PDU.DATA[hi] << 8) + PDU.DATA[lo]; @@ -352,7 +362,7 @@ int LogoClient::Connect() { LastError = NegotiatePduLength(); // Third stage : PDU negotiation } - } + } } Connected = LastError == 0; return LastError; @@ -365,7 +375,7 @@ void LogoClient::Disconnect() Connected = false; PDULength = 0; LastError = 0; - } + } } int LogoClient::ReadArea(int Area, word DBNumber, word Start, word Amount, void *ptrData) @@ -381,7 +391,7 @@ int LogoClient::ReadArea(int Area, word DBNumber, word Start, word Amount, void size_t Offset = 0; const int WordSize = 1; // DB1 element has a size of one byte static unsigned long LastCycle; // variable for storing the time from the last call - // declared statically, so that it can used locally + // declared statically, so that it can used locally // For LOGO 0BA4 to 0BA6 the only compatible information that we can read are inputs, outputs, flags and rtc. // We will represented this as V memory, that is seen by all HMI as DB 1. @@ -398,23 +408,23 @@ int LogoClient::ReadArea(int Area, word DBNumber, word Start, word Amount, void // fetch data or cyclic data read? // The next line will also notice a rollover after about 50 days. // https://playground.arduino.cc/Code/TimingRollover - if (millis()-LastCycle > CYCLETIME) + if (millis() - LastCycle > CYCLETIME) { // fetch data or start a continuously polling! #if defined _EXTENDED int Status; if (GetPlcStatus(&Status) != 0) return LastError; - + // operation mode must be RUN if we use LOGO_FETCH_DATA // so exit with an Error if the LOGO is not in operation mode RUN - if (Status != LogoCpuStatusRun) + if (Status != LogoCpuStatusRun) return SetLastError(errCliFunction); #endif // _EXTENDED - + if (StreamClient->write(LOGO_FETCH_DATA, sizeof(LOGO_FETCH_DATA)) != sizeof(LOGO_FETCH_DATA)) return SetLastError(errStreamDataSend); - + RecvControlResponse(&Length); if (LastError) return LastError; @@ -423,15 +433,15 @@ int LogoClient::ReadArea(int Area, word DBNumber, word Start, word Amount, void { // cyclic reading of data! // If code 06 is sent to the LOGO device within 600 ms, - // the LOGO device sends updated data (cyclic data read). + // the LOGO device sends updated data (cyclic data read). if (StreamClient->write(LOGO_ACK, sizeof(LOGO_ACK)) != sizeof(LOGO_ACK)) return SetLastError(errStreamDataSend); - + PDU.H[0] = 0; - RecvPacket(&PDU.H[1], GetPDULength()-1); + RecvPacket(&PDU.H[1], GetPDULength() - 1); if (LastError) return LastError; - Length = GetPDULength()-1; + Length = GetPDULength() - 1; // Get End Delimiter (1 byte) if (RecvPacket(PDU.T, 1) != 0) @@ -450,7 +460,7 @@ int LogoClient::ReadArea(int Area, word DBNumber, word Start, word Amount, void if (Length != GetPDULength()) return SetLastError(errCliInvalidPDU); - + // To recognize a cycle time we save the number of milliseconds // since the last successful call of the current function LastCycle = millis(); @@ -463,7 +473,7 @@ int LogoClient::ReadArea(int Area, word DBNumber, word Start, word Amount, void // Set buffer to 0 SizeRequested = TotElements * WordSize; - memset(Target+Offset, 0, SizeRequested); + memset(Target + Offset, 0, SizeRequested); // Skip if Start points to user defined VM memory if (Start >= VM_USER_AREA && Start < VM_NDEF_AREA1) @@ -490,7 +500,7 @@ int LogoClient::ReadArea(int Area, word DBNumber, word Start, word Amount, void Address = Start - VM_NDEF_AREA1; SizeRequested = NumElements * WordSize; // tbd, no copy yet - + TotElements -= NumElements; Start += NumElements; } @@ -543,13 +553,13 @@ int LogoClient::ReadArea(int Area, word DBNumber, word Start, word Amount, void // tbd, no copy yet /* last one, we don't need it - TotElements -= NumElements; - Start += NumElements; + TotElements -= NumElements; + Start += NumElements; - Offset += SizeRequested; + Offset += SizeRequested; */ } - + return SetLastError(0); } @@ -570,7 +580,7 @@ int LogoClient::PlcStart() { if (StreamClient->write(LOGO_START, sizeof(LOGO_START)) != sizeof(LOGO_START)) return SetLastError(errStreamDataSend); - + // Setup the telegram memset(&PDU, 0, sizeof(PDU)); @@ -578,10 +588,10 @@ int LogoClient::PlcStart() RecvControlResponse(&Length); // Get PDU response if (LastError) return LastError; - + if (LastPDUType != ACK) // Check Confirmation return SetLastError(errCliFunction); - + if (Length != 1) // 1 is the expected value return SetLastError(errCliInvalidPDU); } @@ -603,7 +613,7 @@ int LogoClient::PlcStop() { if (StreamClient->write(LOGO_STOP, sizeof(LOGO_STOP)) != sizeof(LOGO_STOP)) return SetLastError(errStreamDataSend); - + // Setup the telegram memset(&PDU, 0, sizeof(PDU)); @@ -611,10 +621,10 @@ int LogoClient::PlcStop() RecvControlResponse(&Length); // Get PDU response if (LastError) return LastError; - + if (LastPDUType != ACK) // Check Confirmation return SetLastError(errCliFunction); - + if (Length != 1) // 1 is the expected value return SetLastError(errCliInvalidPDU); } @@ -628,12 +638,12 @@ int LogoClient::GetPlcStatus(int *Status) return SetLastError(errPGConnect); *Status = LogoCpuStatusUnknown; - + if (StreamClient->write(LOGO_MODE, sizeof(LOGO_MODE)) != sizeof(LOGO_MODE)) return SetLastError(errStreamDataSend); memset(&PDU, 0, sizeof(PDU)); // Setup the telegram - + size_t Length; RecvControlResponse(&Length); // Get PDU response if (LastError) @@ -642,7 +652,7 @@ int LogoClient::GetPlcStatus(int *Status) if (LastPDUType != ACK) // Check Confirmation return SetLastError(errCliFunction); - if (Length != sizeof(PDU.H)+1) // Size of Header + 1 Data Byte is the expected value + if (Length != sizeof(PDU.H) + 1) // Size of Header + 1 Data Byte is the expected value return SetLastError(errCliInvalidPDU); switch (PDU.DATA[0]) { @@ -655,14 +665,14 @@ int LogoClient::GetPlcStatus(int *Status) default: *Status = LogoCpuStatusUnknown; } - + return SetLastError(0); } int LogoClient::GetPlcDateTime(TimeElements *DateTime) { int Status; // Operation mode - + if (DateTime == NULL) // Exit with Error if DateTime is undefined return SetLastError(errPGInvalidPDU); @@ -676,7 +686,7 @@ int LogoClient::GetPlcDateTime(TimeElements *DateTime) return SetLastError(errCliFunction); // clock reading initialized - if (WriteByte(ADDR_CLK_W_INIT, 0x00) != 0) + if (WriteByte(ADDR_CLK_W_GET, 0x00) != 0) return LastError; // clock reading day @@ -686,12 +696,12 @@ int LogoClient::GetPlcDateTime(TimeElements *DateTime) // clock reading month if (ReadByte(ADDR_CLK_RW_MONTH, &DateTime->Month) != 0) return LastError; - + // clock reading year if (ReadByte(ADDR_CLK_RW_YEAR, &DateTime->Year) != 0) return LastError; DateTime->Year = y2kYearToTm(DateTime->Year); - + // clock reading hour if (ReadByte(ADDR_CLK_RW_HOUR, &DateTime->Hour) != 0) return LastError; @@ -725,7 +735,7 @@ int LogoClient::GetPlcDateTime(time_t *DateTime) int LogoClient::SetPlcDateTime(TimeElements DateTime) { int Status; // Operation mode - + if (!Connected) // Exit with Error if not connected return SetLastError(errPGConnect); @@ -756,11 +766,11 @@ int LogoClient::SetPlcDateTime(TimeElements DateTime) return LastError; // clock writing day-of-week (sunday is day 0) - if (WriteByte(ADDR_CLK_RW_DOW, (DateTime.Wday-1) % 7) != 0) + if (WriteByte(ADDR_CLK_RW_DOW, (DateTime.Wday - 1) % 7) != 0) return LastError; // clock writing completed - if (WriteByte(ADDR_CLK_W_COMPL, 0x00) != 0) + if (WriteByte(ADDR_CLK_W_SET, 0x00) != 0) return LastError; memset(&PDU, 0, PDURequested); // Clear the telegram @@ -771,7 +781,7 @@ int LogoClient::SetPlcDateTime(TimeElements DateTime) int LogoClient::SetPlcDateTime(time_t DateTime) { TimeElements tm; - + breakTime(DateTime, tm); // break time_t into elements if (SetPlcDateTime(tm) != 0) @@ -780,28 +790,138 @@ int LogoClient::SetPlcDateTime(time_t DateTime) return SetLastError(0); } -int LogoClient::SetPlcSystemDateTime() -{ - if (timeStatus() == timeNotSet) - // the time has never been set, the clock started on Jan 1, 1970 - return SetLastError(errCliFunction); - - if (SetPlcDateTime(now()) != 0) - return LastError; - - return SetLastError(0); -} - -int LogoClient::GetOrderCode(TOrderCode *Info) -{ - return SetLastError(errCliFunction); -} - -void LogoClient::ErrorText(int Error, char *Text, int TextLen) -{ - -} - +int LogoClient::SetPlcSystemDateTime() +{ + if (timeStatus() == timeNotSet) + // the time has never been set, the clock started on Jan 1, 1970 + return SetLastError(errCliFunction); + + if (SetPlcDateTime(now()) != 0) + return LastError; + + return SetLastError(0); +} + +int LogoClient::GetOrderCode(TOrderCode *Info) +{ + int Status; // Operation mode + + if (Info == NULL) // Exit with Error if Info is undefined + return SetLastError(errPGInvalidPDU); + + if (!Connected) // Exit with Error if not connected + return SetLastError(errPGConnect); + + // Set Info to predefined values + strcpy_P(Info->Code, ORDER_CODE); + Info->V1 = 0; + Info->V2 = 0; + Info->V3 = 0; + + switch (IdentNo) { + case 0x40: + // 0BA4 + Info->Code[Size_OC - 2] = '4'; + break; + case 0x42: + // 0BA5 + Info->Code[Size_OC - 2] = '5'; + break; + case 0x43: + // 0BA6 + case 0x44: + // 0BA6.ES3 + Info->Code[Size_OC - 2] = '6'; + break; + default: + // 0BAx + return SetLastError(errCliFunction); + } + + // Check operation mode + if (GetPlcStatus(&Status) != 0) + return LastError; + if (Status != LogoCpuStatusStop) + return SetLastError(0); + + char ch; + // reading ASCII = V + if (ReadByte(ADDR_OC_R_ASC_V, &ch) != 0) + return LastError; + if (ch != 'V') return SetLastError(0); + + // reading ASCII = X.__.__ + if (ReadByte(ADDR_OC_R_MAJOR, &ch) != 0) + return LastError; + if (ch > '0') Info->V1 = ch - '0'; + + // reading ASCII = _.X_.__ + if (ReadByte(ADDR_OC_R_MINOR1, &ch) != 0) + return LastError; + if (ch > '0') Info->V2 = (ch - '0') * 10; + + // reading ASCII = _._X.__ + if (ReadByte(ADDR_OC_R_MINOR2, &ch) != 0) + return LastError; + if (ch > '0') Info->V2 += ch - '0'; + + // reading ASCII = _.__.X_ + if (ReadByte(ADDR_OC_R_PATCH1, &ch) != 0) + return LastError; + if (ch > '0') Info->V3 = (ch - '0') * 10; + + // reading ASCII = _.__._X + if (ReadByte(ADDR_OC_R_PATCH2, &ch) != 0) + return LastError; + if (ch > '0') Info->V3 += ch - '0'; + + return SetLastError(0); +} + +int LogoClient::GetProtection(TProtection *Protection) +{ + int Status; // Operation mode + + if (Protection == NULL) // Exit with Error if Protection is undefined + return SetLastError(errPGInvalidPDU); + + if (!Connected) // Exit with Error if not connected + return SetLastError(errPGConnect); + + // Set Protection to predefined values + memset(Protection, 0, sizeof(TProtection)); + + // Check operation mode + if (GetPlcStatus(&Status) != 0) + return LastError; + + if (Status == LogoCpuStatusStop) + { + Protection->sch_schal = 1; // Protection level set with the mode selector = STOP + byte pw; + if (ReadByte(ADDR_PW_R_SET, &pw) == 0 && pw == 0x40) + Protection->sch_par = 1; // Password level + Protection->sch_rel = 3; // Set valid protection level + Protection->bart_sch = 3; // Mode selector setting = STOP + // anl_sch = 0; + } + else + { + Protection->sch_schal = 2; // Protection level set with the mode selector = RUN + // sch_par = 0; + Protection->sch_rel = 2; // Set valid protection level + Protection->bart_sch = 1; // Mode selector setting = STOP + // anl_sch = 0; + } + + return SetLastError(0); +} + +void LogoClient::ErrorText(int Error, char *Text, int TextLen) +{ + +} + #endif // _EXTENDED // -- Private functions ------------------------------------------------- @@ -811,7 +931,7 @@ int LogoClient::RecvControlResponse(size_t *Size) // Setup the telegram memset(&PDU, 0, sizeof(PDU)); - + // Get first byte if (RecvPacket(PDU.H, 1) != 0) return LastError; @@ -833,7 +953,7 @@ int LogoClient::RecvControlResponse(size_t *Size) if (PDU.H[1] != 0x55) // Control Command Code { // OK, the response has no Control Code 0x55, for example Control Commands 0x17 - *Size = Size_WR+1; // Update size = 7 + *Size = Size_RD + 1; // Update size = 7 // We need to align with PDU.DATA PDU.DATA[0] = PDU.H[1]; PDU.H[1] = 0; @@ -848,7 +968,7 @@ int LogoClient::RecvControlResponse(size_t *Size) if (PDU.H[2] != 0x11 || PDU.H[3] != 0x11) // Error, the response does not have the expected Function Code 0x11 return SetLastError(errCliDataRead); - + // Get Number of Bytes and the Padding Byte (2 bytes) if (RecvPacket(&PDU.H[4], 2) != 0) return LastError; @@ -857,16 +977,16 @@ int LogoClient::RecvControlResponse(size_t *Size) // Store Number of Bytes size_t ByteCount = PDU.H[4]; - if (*Size+ByteCount < MinPduSize) + if (*Size + ByteCount < MinPduSize) return SetLastError(errCliInvalidPDU); - else if (*Size+ByteCount > MaxPduSize) + else if (*Size + ByteCount > MaxPduSize) return SetLastError(errCliBufferTooSmall); // Get the Data Block (n bytes) if (RecvPacket(PDU.DATA, ByteCount) != 0) return LastError; *Size += ByteCount; // Update Size = 6+ByteCount - + // Get End Delimiter (1 byte) if (RecvPacket(PDU.T, 1) != 0) return LastError; @@ -895,11 +1015,11 @@ int LogoClient::RecvPacket(byte buf[], size_t Size) { size_t Length = 0; -/* - // If you doesn't need to transfer more than 80 byte (the length of a PDU) you can uncomment the next two lines - if (Size > MaxPduSize) - return SetLastError(errCliBufferTooSmall); -*/ + /* + // If you doesn't need to transfer more than 80 byte (the length of a PDU) you can uncomment the next two lines + if (Size > MaxPduSize) + return SetLastError(errCliBufferTooSmall); + */ // To recognize a timeout we save the number of milliseconds (since the Arduino began running the current program) unsigned long Elapsed = millis(); @@ -911,10 +1031,10 @@ int LogoClient::RecvPacket(byte buf[], size_t Size) buf[Length++] = StreamClient->read(); else delayMicroseconds(500); - + // The next line will also notice a rollover after about 50 days. // https://playground.arduino.cc/Code/TimingRollover - if (millis()-Elapsed > RecvTimeout) + if (millis() - Elapsed > RecvTimeout) break; // Timeout } @@ -926,7 +1046,7 @@ int LogoClient::RecvPacket(byte buf[], size_t Size) while (StreamClient->available() > 0) StreamClient->read(); - if (Length > 0 && buf[Length-1] == AA) + if (Length > 0 && buf[Length - 1] == AA) // Timeout, but we have an End delimiter return SetLastError(errStreamConnectionReset); @@ -954,7 +1074,7 @@ int LogoClient::LogoConnect() // Setup the telegram memset(&PDU, 0, sizeof(PDU)); - + // Get 4 bytes RecvPacket(PDU.H, 4); if (LastError & errStreamDataRecvTout) // 0BA4 and 0BA5 doesn't support a connection request @@ -987,7 +1107,7 @@ int LogoClient::NegotiatePduLength() // Setup the telegram memset(&PDU, 0, sizeof(PDU)); PDURequested = 4; - + // Get 4 bytes RecvPacket(PDU.H, PDURequested); if (LastError & errStreamDataRecvTout) // 0BA4 and 0BA5 doesn't support a connection request @@ -1007,10 +1127,10 @@ int LogoClient::NegotiatePduLength() if (LastPDUType != ACK) // Check Confirmation return SetLastError(errCliNegotiatingPDU); - byte Revision = PDU.H[PDURequested-1]; - switch (Revision) { + IdentNo = PDU.H[PDURequested - 1]; + switch (IdentNo) { case 0x40: - // 0BA4 + // 0BA4 case 0x42: // 0BA5 PDULength = PduSize0BA4; @@ -1018,7 +1138,7 @@ int LogoClient::NegotiatePduLength() Mapping = VM_MAP_923_983_0BA4; break; case 0x43: - // 0BA6 + // 0BA6 case 0x44: // 0BA6.ES3 PDULength = PduSize0BA6; @@ -1049,7 +1169,7 @@ int LogoClient::ReadByte(dword Addr, byte *Data) // Setup the telegram memset(&PDU, 0, sizeof(PDU)); - + PDU.H[Length++] = 0x02; // Read Byte Command Code if (AddrLength == 4) { @@ -1074,9 +1194,9 @@ int LogoClient::ReadByte(dword Addr, byte *Data) if (LastPDUType == ACK) // Connection confirmed { // Get next bytes - if (RecvPacket(&PDU.H[1], PDURequested-1) != 0) + if (RecvPacket(&PDU.H[1], PDURequested - 1) != 0) return LastError; - + if (PDU.H[1] != 0x03) // Error, the response does not have the expected Code 0x03 return SetLastError(errCliDataRead); @@ -1084,9 +1204,9 @@ int LogoClient::ReadByte(dword Addr, byte *Data) // We should align PDU if (PDURequested < sizeof(PDU.H)) { - PDU.DATA[0] = PDU.H[PDURequested-1]; - PDU.H[PDURequested-1] = 0; - PDURequested = Size_WR+1; + PDU.DATA[0] = PDU.H[PDURequested - 1]; + PDU.H[PDURequested - 1] = 0; + PDURequested = Size_RD + 1; } } else if (LastPDUType == NOK) // Request not confirmed @@ -1111,7 +1231,7 @@ int LogoClient::WriteByte(dword Addr, byte Data) // Setup the telegram memset(&PDU, 0, sizeof(PDU)); - + PDU.H[Length++] = 0x01; // Write Byte Command Code if (AddrLength == 4) { @@ -1151,7 +1271,7 @@ int LogoClient::WriteByte(dword Addr, byte Data) int LogoClient::CpuError(int Error) { - switch(Error) + switch (Error) { case 0: return 0; @@ -1179,4 +1299,5 @@ int LogoClient::CpuError(int Error) default: return errCliInvalidPDU; }; -}; +}; + diff --git a/src/LogoPG.h b/src/LogoPG.h index 0d7ae01..825e9db 100644 --- a/src/LogoPG.h +++ b/src/LogoPG.h @@ -1,5 +1,5 @@ /* - * LogoPG library, Version 0.5.0-beta.3 + * LogoPG library, Version 0.5.0-rc1 * * Portion copyright (c) 2018 by Jan Schneider * @@ -46,9 +46,9 @@ #endif #include "Arduino.h" -#ifdef _EXTENDED +#ifdef _EXTENDED #include "TimeLib.h" -#endif +#endif // Error Codes // from 0x0001 up to 0x00FF are severe errors, the Client should be disconnected @@ -92,9 +92,9 @@ const byte LogoCpuStatusUnknown = 0x00; const byte LogoCpuStatusRun = 0x08; const byte LogoCpuStatusStop = 0x04; - -#define Size_OC 18 -#define Size_WR 6 + +#define Size_OC 19 // Order Code +#define Size_RD 6 typedef unsigned long dword; // 32 bit unsigned integer @@ -102,27 +102,29 @@ typedef byte *pbyte; typedef int *pint; typedef struct { - byte H[Size_WR]; // PDU Header - byte DATA[MaxPduSize-Size_WR]; // PDU Data + byte H[Size_RD]; // PDU Header + byte DATA[MaxPduSize-Size_RD]; // PDU Data byte T[1]; // PDU Trailer } TPDU; extern TPDU PDU; -#ifdef _EXTENDED -typedef struct { - char Code[Size_OC]; // Order Code - byte V1; // Version V1.V2.V3 - byte V2; - byte V3; -} TOrderCode; - -typedef struct { - byte sch_par; - byte bart_sch; - byte anl_sch; -} TProtection; -#endif - +#ifdef _EXTENDED +typedef struct { + char Code[Size_OC]; // Order Code + byte V1; // Version V1.V2.V3 + byte V2; + byte V3; +} TOrderCode; + +typedef struct { + byte sch_schal; + byte sch_par; + byte sch_rel; + byte bart_sch; + byte anl_sch; +} TProtection; +#endif + #ifdef _LOGOHELPER class LogoHelper @@ -171,11 +173,11 @@ class LogoClient int GetPDULength() { return PDULength; } // Extended functions -#ifdef _EXTENDED -/* - int GetDBSize(word DBNumber, size_t *Size); - int DBGet(word DBNumber, void *ptrData, size_t *Size); -*/ +#ifdef _EXTENDED +/* + int GetDBSize(word DBNumber, size_t *Size); + int DBGet(word DBNumber, void *ptrData, size_t *Size); +*/ // Control functions int PlcStart(); // start PLC int PlcStop(); // stop PLC @@ -185,20 +187,20 @@ class LogoClient int GetPlcDateTime(time_t *DateTime); int SetPlcDateTime(TimeElements DateTime); int SetPlcDateTime(time_t DateTime); - int SetPlcSystemDateTime(); - // System info functions - int GetOrderCode(TOrderCode *Info); - // Security functions -/* - SetSessionPassword - ClearSessionPassword -*/ - int GetProtection(TProtection *Protection); - // Miscellaneous functions -/* - GetExecTime -*/ - void ErrorText(int Error, char *Text, int TextLen); + int SetPlcSystemDateTime(); + // System info functions + int GetOrderCode(TOrderCode *Info); + // Security functions +/* + int SetSessionPassword(char *password); + int ClearSessionPassword(); +*/ + int GetProtection(TProtection *Protection); + // Miscellaneous functions +/* + GetExecTime +*/ + void ErrorText(int Error, char *Text, int TextLen); #endif private: @@ -211,6 +213,7 @@ class LogoClient // so LogoClient can use HardwareSerial or CustomSoftwareSerial Stream *StreamClient; + byte IdentNo; // Ident Number int PDULength; // PDU length negotiated (0 if not negotiated) int PDURequested; // PDU length requested by the client int AddrLength; // Address length negotiated (in bytes for function sizeof) @@ -235,6 +238,10 @@ class LogoClient * position description * 0BA4 0BA5 0BA6 *********************************************************************** + 6 6 6 block 1-128 + 22 22 block 129-130 + 22 block 129-200 + 23 23 31 input 1-8 24 24 32 input 9-16 25 25 33 input 17-24 @@ -244,10 +251,10 @@ class LogoClient 26 26 35 output 1-8 27 27 36 output 9-16 - 28 28 37 flag 1-8 - 29 29 38 flag 9-16 - 30 30 39 flag 17-24 - 40 flag 25-27 + 28 28 37 merker 1-8 + 29 29 38 merker 9-16 + 30 30 39 merker 17-24 + 40 merker 25-27 31 31 41 shift register 1-8 @@ -275,22 +282,23 @@ class LogoClient 51 51 61 analog output 2 low 52 52 62 analog output 2 high - 53 53 63 analog flag 1 low - 54 54 64 analog flag 1 high - 55 55 65 analog flag 2 low - 56 56 66 analog flag 2 high - 57 57 67 analog flag 3 low - 58 58 68 analog flag 3 high - 59 59 69 analog flag 4 low - 60 60 70 analog flag 4 high - 61 61 71 analog flag 5 low - 62 62 72 analog flag 5 high - 63 63 73 analog flag 6 low - 64 64 74 analog flag 6 high + 53 53 63 analog merker 1 low + 54 54 64 analog merker 1 high + 55 55 65 analog merker 2 low + 56 56 66 analog merker 2 high + 57 57 67 analog merker 3 low + 58 58 68 analog merker 3 high + 59 59 69 analog merker 4 low + 60 60 70 analog merker 4 high + 61 61 71 analog merker 5 low + 62 62 72 analog merker 5 high + 63 63 73 analog merker 6 low + 64 64 74 analog merker 6 high *********************************************************************** */ // LOGO 0BA4 and 0BA5 index in PDU.DATA +#define B_0BA4 0x06 #define I_0BA4 0x16 #define Q_0BA4 0x19 #define M_0BA4 0x1B @@ -301,6 +309,7 @@ class LogoClient #define AM_0BA4 0x34 // LOGO 0BA6 index in PDU.DATA +#define B_0BA6 0x06 #define I_0BA6 0x1E #define F_0BA6 0x21 #define Q_0BA6 0x22 @@ -313,7 +322,7 @@ class LogoClient /* * The LOGO > 0BA6 has reserved fixed memory addresses for inputs, - * outputs, flags in the Variable Memory above the 850th byte. + * outputs, merkers in the Variable Memory above the 850th byte. * These depending on the model (0BA7 or 0BA8). Our library should * represent the Variable Memory as 0BA7 address scheme. * @@ -372,48 +381,48 @@ class LogoClient 1086 analog output 8 high 1087 analog output 8 low - 948 1104 flag 1-8 - 949 1105 flag 9-16 - 950 1106 flag 17-24 - 951 flag 25-27 - 1107 flag 25-32 - 1108 flag 33-40 - 1109 flag 41-48 - 1110 flag 49-56 - 1111 flag 47-64 + 948 1104 merker 1-8 + 949 1105 merker 9-16 + 950 1106 merker 17-24 + 951 merker 25-27 + 1107 merker 25-32 + 1108 merker 33-40 + 1109 merker 41-48 + 1110 merker 49-56 + 1111 merker 47-64 - 952 1118 analog flag 1 high - 953 1119 analog flag 1 low - 954 1120 analog flag 2 high - 955 1121 analog flag 2 low - 956 1122 analog flag 3 high - 957 1123 analog flag 3 low - 958 1124 analog flag 4 high - 959 1125 analog flag 4 low - 960 1126 analog flag 5 high - 961 1127 analog flag 5 low - 962 1128 analog flag 6 high - 963 1129 analog flag 6 low - 964 1130 analog flag 7 high - 965 1131 analog flag 7 low - 966 1132 analog flag 8 high - 967 1133 analog flag 8 low - 968 1134 analog flag 9 high - 969 1135 analog flag 9 low - 970 1136 analog flag 10 high - 971 1137 analog flag 10 low - 972 1138 analog flag 11 high - 973 1139 analog flag 11 low - 974 1140 analog flag 12 high - 975 1141 analog flag 12 low - 976 1142 analog flag 13 high - 977 1143 analog flag 13 low - 978 1144 analog flag 14 high - 979 1145 analog flag 14 low - 980 1146 analog flag 15 high - 981 1147 analog flag 15 low - 982 1148 analog flag 16 high - 983 1149 analog flag 16 low + 952 1118 analog merker 1 high + 953 1119 analog merker 1 low + 954 1120 analog merker 2 high + 955 1121 analog merker 2 low + 956 1122 analog merker 3 high + 957 1123 analog merker 3 low + 958 1124 analog merker 4 high + 959 1125 analog merker 4 low + 960 1126 analog merker 5 high + 961 1127 analog merker 5 low + 962 1128 analog merker 6 high + 963 1129 analog merker 6 low + 964 1130 analog merker 7 high + 965 1131 analog merker 7 low + 966 1132 analog merker 8 high + 967 1133 analog merker 8 low + 968 1134 analog merker 9 high + 969 1135 analog merker 9 low + 970 1136 analog merker 10 high + 971 1137 analog merker 10 low + 972 1138 analog merker 11 high + 973 1139 analog merker 11 low + 974 1140 analog merker 12 high + 975 1141 analog merker 12 low + 976 1142 analog merker 13 high + 977 1143 analog merker 13 low + 978 1144 analog merker 14 high + 979 1145 analog merker 14 low + 980 1146 analog merker 15 high + 981 1147 analog merker 15 low + 982 1148 analog merker 16 high + 983 1149 analog merker 16 low *********************************************************************** */