Forum: Mikrocontroller und Digitale Elektronik Arduino Bibliothek um Steuerungsaufgaben und das Handling von Ein- und Ausgabe zu vereinfachen


von Thomas K. (thomaskaulen)


Angehängte Dateien:

Lesenswert?

Ich entwickele zurzeit eine Arduino Bibliothek um Steuerungsaufgaben und 
das Handling von Ein- und Ausgabe zu vereinfachen. Vielleicht habt Ihr 
ein paar Anregungen bevor ich das Teil bei github veröffentlichen werde.

Dadurch das die Programmlogik von den Ein- und Ausgabefunktionen durch 
die IO Controller strikt getrennt  sind, läuft das untere Codebeispiel 
ohne Änderungen sowohl auf einem Arduino als auch auf dem PC 
(Visualstudio c++).

Aus diesem Grund habe ich z.B.  Serial durch den Namen printStream 
ersetzt. Auf dem Arduino wird dann die UART Ein-/Ausgabe genutzt und auf 
dem Windows PC die Console. Die Bibliothek bildet auf dem Windows PC 
einige Arduino Funktionen(Streams,Print,String e.t.c.) nach, falls diese 
auf dem PC nicht verfügbar sind.

----------------------

Das folgende Beispiel benutzt die Bibliothek:
1
#include "PlcIncludeAll.h"
2
#include "PlcSample.h"
3
4
PlcStdIO* io;
5
PlcMachine machine;
6
7
8
class Example : public PlcProcess
9
{
10
public:
11
  enum commands {  run , stop  , list,last};
12
  int btn1;
13
  int stopwatchTempValue;
14
  int measuredTime;
15
  int clockRun;
16
  int clockTick;
17
  int clockResult;
18
  void onRegisterIO(PlcIO* io)
19
  {  
20
    io->addCommands(command, last,  "run", "stop","list");  
21
    io->add(btn1, "btn1");
22
    io->add(measuredTime, "measured time");
23
    io->add(clockTick, "clock tick");
24
  }
25
  Example()
26
  {  
27
    clockTick = 0; clockRun = 0; measuredTime = 0;
28
  }
29
  void onMessage(PlcIO* plcIO, PlcAdapter* adapter, int index)
30
  {
31
    if (adapter->isCommand() && adapter->origin == true)
32
    {
33
      switch (adapter->getIntValue())
34
      {
35
      case run:
36
        clockRun = true;
37
        this->println("clock start");
38
        break;
39
      case stop:
40
        clockRun = false;
41
        this->println("clock stop");
42
        break;
43
      case list:
44
        syncInit();
45
        break;
46
      }
47
    }
48
    int u = 0;
49
  }
50
  void onTask()
51
  {
52
    /*PLC Node Calls e.g.clock, ... must not be used within if statemnts or loops.
53
    Only linear sequences are permitted !!!!
54
    forbidden example:
55
     if (condition)
56
     {
57
        a = clock(1000);
58
        b = clock(2000);
59
     } 
60
    */
61
    stopwatchTempValue = stopwatch(btn1);
62
    if (fall(btn1))
63
    {
64
      measuredTime = stopwatchTempValue;
65
      clockRun = true;
66
    }
67
    clockResult=  clock(measuredTime);
68
    clockTick = clockRun & clockResult;
69
  }
70
} example;
71
void setup() {
72
  printStream.begin(9600);
73
  io = new PlcStdIO(&printStream);
74
  example.registerIO(io);
75
  machine.registerProcess(&example);
76
  PlcStream *st;
77
  PlcBugStream* m;
78
}
79
void loop() {
80
  machine.tick();
81
}

Die Klasse Example macht folgendes:

Wenn ein Button gedrückt und losgelassen wird, wird die Zeit zuwichen 
den beiden Tastenzuständen gemessen und angezeigt (measured time=). Im 
Anschluss gibt ein periodischer Timer das gemessene Zeitinterval als 
High und Low Flanken aus (clock tick=1, clock tick=0 ..... siehe 
Screenshot).

Um die Zeitmessung zu starten wird auf dem Windows PC in der Konsole 
btn1=1 und btn1=0 hintereinander eingegeben, um den IO Port des Arduinos 
zu simulieren.

Zusätzlich kann mit den Kommandos run und stop die Ausgabe gestartet und 
beendet werden.

Wenn das Programm auf einem Arduino laufen soll, wird zusätzlich ein 
HardwareIO Conroler registriert, welcher das Prozessabild der ein- und 
Ausgänge auf die IO Pins abbildet. Das eigentliche Programm hat keine 
Kenntnis darüber  welche IO Hardware existieren.

-----------------------------------

Die virtuelle Funktion onRegister bindet Variablen an beliebige IO 
Controller. Zudem wird gleichzeitig ein Name zugewiesen wie die Aus- und 
Eingabe erfolgen soll. Propertys wie z.B. ReadOnly sind auch möglich. 
Außerdem könne hier auch Kommandos registriert werden.

Zurzeit existieren IO Controller für die Kommandozeilenein-/ausgabe und 
die Hardware IO Pin Ein/Ausgabe. Es folgen noch weitere für ModBus, I2C 
und mqtt.

Die virtuelle Funktion onMessage reagiert auf Änderungen von gebunden 
Variablen und Kommandos.

Die virtuelle Funktion onTask bildet SPS/PLC Funktionalität ab. Es wird 
im Hintergrund das Prozessabbild der Ein/-Ausgänge ausgewertet und die 
daraus erfolgten Variablenänderungen an die registrierten IOController 
weitergeleitet.

Es sind folgende SPS/Plc Standardfunktionen implementiert.
1
bool rs(bool set, bool reset);
2
  Int32 ctu(bool trigger, bool reset, Int32 pv, bool& q);
3
  bool sr(bool set, bool reset);
4
  // Ausschaltverzögerung
5
  bool tof(bool trigger, Int32 duration);
6
  // Einschaltverzögerung
7
  bool ton(bool trigger, Int32 duration);
8
  /// Impulsbildung
9
  bool tp(bool trigger, Int32 duration);
10
  bool clock(Int32 interval);
11
  bool gateBool(bool open, bool value);
12
  Int32 gate(bool open, Int32 value);
13
  Int32 stopwatch(bool run);

Bei jedem Funktionsaufruf werden falls notwendig, im Hintergrund 
automatisch  Merker zugewiesen und dessen Zustände beim nächsten 
Funktionsaufruf abgerufen. Es können z.B. beliebig viele clock Aufrufe 
mit unterschiedlichen Zeiten verwendet werden. Alles läuft asynchron und 
das Hauptprogramm wird nicht blockiert wie es z.B. bei delay Aufrufen 
der Fall wäre


-------------------
die setup Funktion:

- serial wird durch printStream ersetzt

- der PLCStdIO Controller ist ein IOController der ein Ausgaben für die 
Kommandozeile bereitstellt. Jede gebundene Variable kann mit 
Variablenname=Wert zugewiesen werden und jede Werteänderung wird als 
Variablennam=Wert ausgegeben. Der PlcStdIO Controller kann beliebige 
Arduino Streams wie z.B. die Serials oder bei einem ESP32 über Wifi 
einen Telnet Dienst bedienen.

- die Maschine kann beliebig viele Prozesse mit unterschiedlichen 
Prioritäten registrieren.

die loop Funktion:
zyklicher Aufruf der Bibliothek



viele Grüße

: Bearbeitet durch User
von Alexander (alecxs)


Lesenswert?

stell es online und gut

von Motopick (motopick)


Lesenswert?

> Vielleicht habt Ihr ein paar Anregungen

Ja, man benutzt einen Controller der bereits auf
unterster Ebene dafuer passende Befehle bereitstellt.
Dann reichen dafuer Assemblermakros.
Der Restaufwand ist gerade noch ein Scheduler, der sich um
Tasks, Queues und Timerevents kuemmert.

> beliebig viele clock Aufrufe

Glaube ich nicht.

> wird nicht blockiert wie es z.B. bei delay Aufrufen
> der Fall wäre

Das ist woanders der Normalfall.

> auf einem Arduino als auch auf dem PC

Wozu soll das denn gut sein?

> kann beliebige
> Arduino Streams wie z.B. die Serials oder bei einem ESP32 über Wifi
> einen Telnet Dienst bedienen

Die je nach Medium aber einige 100 bis einige 10000 ms fuer die
Ausfuehrung brauchen. :)


> stell es online und gut

Ja, da stoert es keinen.

von Thomas K. (thomaskaulen)


Lesenswert?

Motopick schrieb:
> Glaube ich nicht.

Da gebe ich Dir recht. Ich korrigiere: Beliebig viele so lange es die 
Ressourcen der Hardware zulassen.

Natürlich wird für jeden Aufruf Speicherplatz auf dem Stack reserviert. 
Klar wenn der Speicher voll ist können auch keine Merker im Hintergrund 
reserviert werden.

Motopick schrieb:
> Wozu soll das denn gut sein?
Debuggen auf einen PC in Visualstudio ist um einiges effektiver als auf 
dem yC.

Motopick schrieb:
> Die je nach Medium aber einige 100 bis einige 10000 ms fuer die
> Ausfuehrung brauchen. :)
Lass es mal bei einem Stream für die Serial Ausgabe 10-50 ms sein . 
100-10000 ms ist schon sehr übertrieben.
Für Hardware IO's werden in den IO Controllern natürlich KEINE Streams 
benutzt. Da liegen die Latenzzeiten unter 1 ms.

von Peter D. (peda)


Lesenswert?

Also ich gucke da rein, wie ein Schwein ins Uhrwerk. Da ist wohl der 
Großteil der Funktionalität in den nicht gezeigten h-Files versteckt.
Ich mag es bei Steueraufgaben, wenn alle Abläufe einfach zu verfolgen 
sind. Also wann wird was und in welcher Reihenfolge ausgeführt. Und 
nicht über 1000 Instanzen hinweg von hinten durch die Brust ins Auge.
Z.B. wodurch die Anzeige von measuredTime getriggert wird, ist mir 
vollkommen unklar.

von Thomas K. (thomaskaulen)


Lesenswert?

Peter D. schrieb:
> Also ich gucke da rein, wie ein Schwein ins Uhrwerk.
> Da ist wohl der
> Großteil der Funktionalität in den nicht gezeigten h-Files versteckt.

Bei einem solchem Framework ist es normal das Funktionalität abstrahiert 
und hinter einer Fassade versteckt wird. Genau das ist Sinn und Zweck 
der Sache. Letztendlich dient es dazu Dinge schneller und fehlerfreier 
zu entwickeln.

Die ganze Bibliothek hat ca. 85 KB Quellcode. g

Peter D. schrieb:
> Z.B. wodurch die Anzeige von measuredTime getriggert wird, ist mir
> vollkommen unklar.

Ein Zeiger auf die Variable measuredTime wird mit der Funktion add(int& 
ref, const char* name) dem IO Controller gemeldet. Beim Aufruf der add 
Funktion wird im Controller zusätzlich eine Kopie der Speicherzelle 
erzeugt. Bei jedem Zyklus wird die referenzierte Variable mit der Kopie 
verglichen. Wenn die beiden Werte unterschiedlich sind, wird eine 
Triggerfunktion ausgelöst und die Kopie aktualisiert.

: Bearbeitet durch User
von Vax W. (Gast)


Lesenswert?

Wir koennen ja nicht kommentieren, weil wir nicht die Quellen und 
Examples gesehen haben.

- Vergleich Performance/Umfang z.B. zu FreeRTOS oder Azure? IO 
(Disk/Netzwerk)

- Unterstuetzte Prozessoren ("Arduino" ist ein Framework, keine 
Hardware)

- Ist das ein Ersatz fuer ein RTOS?

Du solltest versuchen, Deine SW in einem anderen Forum vorzustellen 
(hier wird es zerredet) oder ein Abo fuer "Asbestos Underwear" haben.

von Stefan F. (Gast)


Lesenswert?

Thomas K. schrieb:
> Die ganze Bibliothek hat ca. 85 KB Quellcode.

Falls du denkst, das sei viel: Ist es nicht.

von Peter D. (peda)


Lesenswert?

Thomas K. schrieb:
> Bei jedem Zyklus wird die referenzierte Variable mit der Kopie
> verglichen. Wenn die beiden Werte unterschiedlich sind, wird eine
> Triggerfunktion ausgelöst und die Kopie aktualisiert.

Das wäre mir zu aufwendig, haufenweise Schattenspeicher einzurichten und 
zu vergleichen.
Ich habe auch die Erfahrung gemacht, daß es sicherer ist, alle Ausgänge 
zyklisch neu zu setzen, egal, ob sie sich geändert haben. Es können ja 
Störungen auf SPI, I2C usw. einstrahlen, ein DAC kippt um, eine Anzeige 
zeigt Müll an usw. Oder es passiert ein kurzer Spannungseinbruch, den 
die CPU gar nicht mitkriegt.

Thomas K. schrieb:
> clockTick = clockRun & clockResult;

Ich würde diese Variablen aber als bool definieren und mit && 
verknüpfen.
Oder ist das & überladen und was bewirkt es hier?

von Thomas K. (thomaskaulen)


Lesenswert?

Peter D. schrieb:
> Ich habe auch die Erfahrung gemacht, daß es sicherer ist, alle Ausgänge
> zyklisch neu zu setzen, egal, ob sie sich geändert haben.

Bei direkten IO's ist das sicherlich die Standardvorgehensweise, die 
Ports immer wieder zyklisch neu zu setzen. Aber nehmen wir einmal an, Du 
hast einen Zyklus der 10 ms dauert und Du möchtest keinen direkten IO 
setzen, sondern irgendetwas das sich außerhalb des Controllers befindet. 
Zum Beispiel eine SCADA Anzeige über Modbus RS485. Dann würden in dieser 
Situation (alle Ausgänge zyklisch neu setzen)alle 10 ms der gesamte 
Status der Ausgänge über den Bus verschickt. Wenn 32 solcher Ausgänge 
übermittelt werden sollen und jeder genau 1 byte an Informationen hat, 
dann wären das 256 Bit's netto + Overhead. Mit diesen 256 Bit's alleine 
wären bei einer Zyklusrate von 10 ms schon 25.600 Baud komplett 
ausgelastet. Den Overhead noch gar nicht mit einberechnet.
Da erstens das menschliche Auge eine solche Anzeigerate auf der SCADA 
GUI gar nicht benötigt und zweitens es auch durchaus Maximalbegrenzung 
einer Bandbreite bei einem Datenbus gibt, finde ich das neu senden aller 
Daten bei jedem Zyklus für keine so gute Lösung.  Und wenn Du dann noch 
Pesch hast, läuft Dir der Sendebuffer über oder der Prozess blockiert, 
weil alles verstopft. Ja kann man so machen, natürlich. Hier finde ich 
es besser nach jedem Zyklus die Änderungen zu erfassen und nur die 
Änderungen zu übertragen. ggf. alle 100 Zyklen zusätzlich einmal eine 
Komplettsynchronisation. Das ganze zusammen mit einem 
Bandbreitenmanagement das im Hintergrund läuft und notfalls den 
Datenstrom abbremst.

Peter D. schrieb:
> Ich würde diese Variablen aber als bool definieren und mit &&
> verknüpfen.
> Oder ist das & überladen und was bewirkt es hier?

Fehler von mir, müßte eine boolsche Verknüpfung sein und keine 
mathematische AND Operation auf den Integerwert. Obwohl, solange der 
Integer nur 1 und 0 als Wert annehmen kann müsste sich das Programm 
gleich verhalten.

Vax W. schrieb:
> - Ist das ein Ersatz fuer ein RTOS?
Wenn ich die Frage an dieser Stelle mit ja beantworten würde, wäre das 
formell falsch, weil es kein Multithreading gibt. Aber es gibt auf der 
Anwendungsebene eine Art "gefakte Paralellität", weil vieles in möglich 
kleine Slices aufgeteilt wird. Der Parser z.B. der die String Eingaben 
parst, liest pro Zyklus eine gewisse feste Anzahl an Bytes aus dem 
Stream und speichert den Status zwischen, bis der Ausdruck vollständig 
gelesen und interpretiert ist. Das Gegenteil wäre z.B. alles zeilenweise 
zu lesen und die Zeile dann komplett auf einmal auszuwerten. Aus meiner 
Erfahrung führt das dann manchmal dazu, das der Zyklus zu lange 
unterbrochen wird und über die Schwelle der maximal gewünschten 
Zyklusdauer geht.

Vax W. schrieb:
> - Unterstuetzte Prozessoren ("Arduino" ist ein Framework, keine
> Hardware)

Das stimmt. Aber die Adrdunio Plattform hat klar definierte 
Schnittstellen für die Bibliotheken und einen HAL Layer für die 
darunterliegende Hardware. Solange es eine Arduino kompatible HAL 
Implementation und einen C++ Compiler für den Prozessor gibt, sollte es 
darauf laufen. Ich habe bis jetzt noch nicht alle getestet, aber Arduino 
MEGA 2560, Arduino DUE, Arduino Zero, ESP32, ESP8266 laufen damit, weil 
ich die selber zu Hause habe

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Thomas K. schrieb:
> ...
Da du klug bist, ist dir auch klar, dass ich das nicht testen/beurteilen 
kann.
Und da du schlau bist, sorgst du absichtlich dafür.
(warum auch immer)

Als alter PLC/SPS Liebhaber/Nutzer verstehe ich dein Vorhaben, habe 
allerdings gänzlich andere Wege genutzt um das für meine Belange zu 
realisieren. Z.B. mich Für die Datenfluss Variante entschieden, wie sie 
z.B. von den Koppelplänen bekannt ist.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.