PD4-E Modbus RTU Online-Handbuch

Programmierung mit NanoJ

NanoJ ist eine C- bzw. C++-nahe Programmiersprache. NanoJ ist in der Software Plug & Drive Studio integriert. Weiterführende Informationen finden Sie im Dokument Plug & Drive Studio: Quick Start Guide auf www.nanotec.de.

NanoJ-Programm

Ein NanoJ-Programm stellt eine geschützte Ausführungsumgebung innerhalb der Firmware zur Verfügung. In dieser kann der Anwender eigene Abläufe anlegen. Diese können dann Funktionen in der Steuerung auslösen, indem beispielsweise Einträge im Objektverzeichnis gelesen oder geschrieben werden.

Durch Verwendung von Schutzmechanismen wird verhindert, dass ein NanoJ-Programm die Firmware zum Absturz bringt. Im schlimmsten Fall wird die Ausführung mit einem im Objektverzeichnis hinterlegten Fehlercode abgebrochen.

Wenn das NanoJ-Programm auf die Steuerung geladen wurde, wird es nach dem Einschalten oder Neustarten der Steuerung automatisch ausgeführt, sofern Sie Bit 0 im Objekt 2300h nicht auf "0" setzen.

Verfügbare Rechenzeit

Ein NanoJ-Programm erhält zyklisch im 1 ms-Takt Rechenzeit (siehe folgende Abbildung). Da durch Interrupts und Systemfunktionen der Firmware Rechenzeit verloren geht, stehen dem Benutzerprogramm (abhängig von Betriebsart und Anwendungsfall) nur ca. 30% … 50% Rechenzeit zur Verfügung. In dieser Zeit muss das Benutzerprogramm den Zyklus durchlaufen und entweder beenden oder durch Aufruf der Funktion yield() die Rechenzeit abgeben. Bei Ersterem wird das Benutzerprogramm mit dem Beginn des nächsten 1 ms-Zyklus wieder neu gestartet, letzteres bewirkt eine Fortsetzung des Programms an dem der Funktion yield() nachfolgenden Befehl beim nächsten 1 ms-Zyklus.

Falls das NanoJ-Programm mehr als die ihm zugeteilte Zeit benötigt, wird es beendet und im Objektverzeichnis ein Fehlercode gesetzt.

Tipp: Bei der Entwicklung von Benutzerprogrammen ist speziell bei zeitintensiveren Aufgaben eine sorgfältige Überprüfung des Laufzeitverhaltens durchzuführen. So empfiehlt sich beispielsweise die Verwendung von Tabellen, anstatt einen Sinuswert über eine sin Funktion zu berechnen.
Anmerkung:

Sollte das NanoJ-Programm zu lange die Rechenzeit nicht abgeben, wird es vom Betriebssystem beendet. In diesem Fall wird in das Statusword bei Objekt 2301h die Ziffer 4 eingetragen, im Fehlerregister bei Objekt 2302h wird die Ziffer 5 (Timeout) notiert, siehe 2301h NanoJ Status und 2302h NanoJ Error Code.

Damit das NanoJ-Programm nicht angehalten wird, können Sie den AutoYield-Modus aktivieren, indem Sie den Wert "5" in 2300h schreiben. Im AutoYield-Modus ist aber das NanoJ-Programm nicht mehr echtzeitfähig und läuft nicht mehr im 1-Millisekunde-Takt.

Geschützte Ausführungsumgebung

Durch prozessorspezifische Eigenschaften wird eine sogenannte Geschützte Ausführungsumgebung generiert. Ein Benutzerprogramm in der geschützten Ausführungsumgebung hat nur die Möglichkeit, auf speziell zugewiesene Speicherbereiche und Systemressourcen zuzugreifen. Beispielsweise wird ein Versuch, auf ein Prozessor-IO-Register direkt zu schreiben, mit einem MPU Fault quittiert und das Benutzerprogramm wird mit dem entsprechenden Fehlercode im Objektverzeichnis abgebrochen.

NanoJ-Programm - Kommunikationsmöglichkeiten

Ein NanoJ-Programm hat mehrere Möglichkeiten, mit der Steuerung zu kommunizieren:

  • Lesen und Schreiben von OD-Werten per PDO-Mapping
  • direktes Lesen und Schreiben von OD-Werten über NanoJ-Funktionen
  • Aufruf sonstiger NanoJ-Funktionen (z. B. Debug-Ausgabe schreiben)

Über ein PDO-Mapping werden dem Benutzerprogramm OD-Werte in Form von Variablen zur Verfügung gestellt. Bevor ein Benutzerprogramm die 1 ms-Zeitscheibe erhält, werden dazu von der Firmware die Werte aus dem Objektverzeichnis in die Variablen des Benutzerprogramms übertragen. Sobald das Benutzerprogramm Rechenzeit erhält, kann es diese Variablen wie gewöhnliche C-Variablen manipulieren. Am Ende der Zeitscheibe werden letztendlich die neuen Werte von der Firmware wieder automatisch in die jeweiligen OD-Einträge kopiert.

Um die Performance zu optimieren, werden dabei drei Arten von Mappings definiert: Input, Output und Input/Output (In, Out, InOut).

  • Input Mappings lassen sich nur lesen und werden nicht zurück ins Objektverzeichnis übertragen.
  • Output Mappings lassen sich nur schreiben.
  • Input/Output Mappings erlauben hingegen Lesen und Schreiben.

Die gesetzten Mappings können über die GUI bei den Objekten 2310h, 2320h, und 2330h ausgelesen und überprüft werden. Für jedes Mapping sind maximal 16 Einträge erlaubt.

Über die Angabe der Linker-Section wird in Plug & Drive Studio gesteuert, ob eine Variable im Input-, Output- oder Datenbereich abgelegt wird.

NanoJ-Inputs und NanoJ-Outputs

Um mit dem NanoJ-Programm über die jeweilige Schnittstelle zu kommunizieren, können Sie folgende Objekte benutzen:

  • 2400h NanoJ Inputs: Array mit zweiunddreißig S32-Werten zum Übergeben von Werten an das NanoJ-Programm
  • 2410h NanoJ Init Parameters: Array mit zweiunddreißig S32-Werten. Dieses Objekt kann gespeichert werden, im Gegensatz zu 2400h.
  • 2500h NanoJ Outputs: Array mit zweiunddreißig S32-Werten, wo das NanoJ-Programm Werte ablegen kann, die über den Feldbus ausgelesen werden können

NanoJ-Programm ausführen

Zusammengefasst besteht das NanoJ-Programm bei der Ausführung eines Zyklus hinsichtlich des PDO-Mappings aus folgenden drei Schritten:

  1. Werte aus dem Objektverzeichnis lesen und in die Bereiche Inputs und Outputs kopieren
  2. Benutzerprogramm ausführen
  3. Werte aus den Bereichen Outputs und Inputs zurück in das Objektverzeichnis kopieren

Die Konfiguration der Kopiervorgänge ist dem CANopen-Standard angelehnt.

Zusätzlich kann über NanoJ-Funktionen auf Werte des Objektverzeichnisses zugegriffen werden. Dies ist im Allgemeinen deutlich langsamer und daher sind Mappings vorzuziehen. Die Anzahl an Mappings ist begrenzt (jeweils 16 Einträge in In/Out/InOut).

Tipp: Nanotec empfiehlt: Häufig genutzte und veränderte OD-Einträge mappen und auf weniger häufig genutzte OD-Einträge per NanoJ-Funktion zuzugreifen.

Eine Liste verfügbarer NanoJ-Funktionen findet sich im Kapitel NanoJ-Funktionen im NanoJ-Programm.

Tipp: Nanotec empfiehlt, entweder per Mapping oder NanoJ-Funktion mit od_write() auf ein und denselben OD-Wert zuzugreifen. Wird beides gleichzeitig verwendet, so hat die NanoJ-Funktion keine Auswirkung.

NanoJ-Programm OD-Einträge

Das NanoJ-Programm wird durch OD-Einträge im Objekt-Bereich 2300h bis 2330h gesteuert und konfiguriert (siehe 2300h NanoJ Control).

OD-Index Name und Beschreibung
2300h 2300h NanoJ Control
2301h 2301h NanoJ Status
2302h 2302h NanoJ Error Code
2310h 2310h NanoJ Input Data Selection
2320h 2320h NanoJ Output Data Selection
2330h 2330h NanoJ In/output Data Selection

Beispiel:

Um das Benutzerprogramm TEST1.USR zu starten, kann z. B. folgende Sequenz benutzt werden:

  • Überprüfen des Eintrags 2302h auf Fehlercode.
  • Wenn kein Fehler:

    NanoJ-Programm starten durch Beschreiben von Objekt 2300h, Bit 0 = "1" bzw. durch Neustarten der Steuerung.

    Anmerkung: Das Starten des NanoJ Programms kann bis zu 200 ms dauern.

  • Überprüfen des Eintrags 2302h auf Fehlercode und des Objekts 2301h, Bit 0 = "1".

Um ein laufendes Programm anzuhalten: Beschreiben des Eintrags 2300h mit dem Bit 0 Wert = "0".

Aufbau NanoJ-Programm

Ein Benutzerprogramm besteht aus mindestens zwei Anweisungen:

  • der Präprozessoranweisung #include "wrapper.h"
  • der Funktion void user(){}

In der Funktion void user() lässt sich der auszuführende Code hinterlegen.

Anmerkung: Die Dateinamen der Benutzerprogramme dürfen nicht länger als acht Zeichen sein und drei Zeichen im Suffix enthalten; Dateiname main.cpp ist zulässig, Dateiname einLangerDateiname.cpp ist nicht zulässig.
Anmerkung: In NanoJ-Programmen dürfen globale Variablen ausschließlich innerhalb von Funktionen initialisiert werden. Daraus folgt:
  • kein new Operator
  • keine Konstruktoren
  • keine Initialisierung von globalen Variablen außerhalb von Funktionen

Beispiele:

Die globale Variable soll erst innerhalb der Funktion void user() initialisiert werden:

unsigned int i; 
void user(){
 i = 1;
 i += 1; 
} 

Folgende Zuweisung führt zu einem Fehler beim Kompilieren:

unsigned int i = 1;
 void user() {
 i += 1; 
} 

NanoJ-Programmbeispiel

Das Beispiel zeigt das Programmieren eines Rechtecksignals in das Objekt 2500h:01h.

// file main.cpp
map S32 outputReg1 as inout 0x2500:1
#include "wrapper.h"

// user program
void user()
{
  U16 counter = 0;
  while( 1 ) 
  {
    ++counter; 

    if( counter < 100 )
     InOut.outputReg1 = 0;
    else if( counter < 200 )
      InOut.outputReg1 = 1;
    else
      counter = 0;

    // yield() 5 times (delay 5ms)
    for(U08 i = 0; i < 5; ++i )
      yield();
  }
}// eof

Weitere Beispiele finden Sie auf www.nanotec.de.

Mapping im NanoJ-Programm

Mit dieser Methode wird eine Variable im NanoJ-Programm direkt mit einem Eintrag im Objektverzeichnis verknüpft. Das Anlegen des Mappings muss dabei am Anfang der Datei stehen - noch vor der #include "wrapper.h"-Anweisung.

Tipp: Nanotec empfiehlt:
  • Benutzen Sie das Mapping, falls Sie den Zugriff auf ein Objekt im Objektverzeichnis häufiger benötigen, z. B. das Controlword 6040h oder das Statusword 6041h.
  • Für den einzelnen Zugriff auf Objekte bieten sich eher die Funktionen od_write() und od_read() an, siehe Zugriff auf das Objektverzeichnis.

Deklaration des Mappings

Die Deklaration des Mappings gliedert sich dabei folgendermaßen:

map <TYPE> <NAME> as <input|output|inout> <INDEX>:<SUBINDEX>

Dabei gilt:

  • <TYPE> 

    Der Datentyp der Variable; U32, U16, U08, S32, S16 oder S08.

  • <NAME>

    Der Name der Variable; wie sie im Benutzerprogramm verwendet wird.

  • <input|output|inout> 

    Die Schreib- und Leseberechtigung einer Variable: Eine Variable kann entweder als input, output oder inout deklariert werden. Damit wird festgelegt, ob eine Variable lesbar (input), schreibbar (output) oder beides ist (inout) und über welche Struktur sie im Programm angesprochen werden muss.

  • <INDEX>:<SUBINDEX> 

    Index und Subindex des zu mappenden Objekts im Objektverzeichnis.

Jede deklarierte Variable wird im Benutzerprogramm über eine der drei Strukturen In, Out oder InOut angesprochen, je nach definierter Schreib- und Leserichtung.

Anmerkung: Ein Kommentar ist nur oberhalb der jeweiligen Mapping-Deklaration im Code erlaubt, nicht in derselben Zeile.

Beispiel eines Mappings

Beispiel eines Mappings und der zugehörigen Variablenzugriffe:

// 6040h:00h is UNSIGNED16 
map U16 controlWord as output 0x6040:00
// 6041h:00h is UNSIGNED16
map U16 statusWord as input 0x6041:00

// 6060h:00h is SIGNED08 (INTEGER8)
map S08 modeOfOperation as inout 0x6060:00

#include "wrapper.h"

void user()
{
  [...]
  Out.controlWord = 1;
  U16 tmpVar = In.statusword;
  InOut.modeOfOperation = tmpVar;
  [...]
}

Möglicher Fehler bei od_write()

Eine mögliche Fehlerquelle ist ein schreibender Zugriff mittels der Funktion od_write() (siehe NanoJ-Funktionen im NanoJ-Programm) auf ein Objekt im Objektverzeichnis, welches gleichzeitig als Mapping angelegt wurde. Nachfolgend aufgelisteter Code ist fehlerhaft:

map U16 controlWord as output 0x6040:00
#include " wrapper.h"
void user()
{
 [...]
  Out.controlWord = 1;
  [...]
  od_write(0x6040, 0x00, 5 ); // der Wert wird durch das Mapping überschrieben
  [...]
}

Die Zeile mit dem Befehl od_write(0x6040, 0x00, 5 ); ist wirkungslos. Wie in der Einleitung beschrieben, werden alle Mappings am Ende jeder Millisekunde in das Objektverzeichnis kopiert.

Damit ergibt sich folgender Ablauf:

  1. Die Funktion od_write schreibt den Wert 5 in das Objekt 6040h:00h.
  2. Am Ende des 1 ms-Zyklus wird das Mapping geschrieben, welches ebenfalls das Objekt 6040h:00h beschreibt, allerdings mit dem Wert 1.
  3. Somit wird - aus Sicht des Benutzers - der od_write-Befehl wirkungslos.

NanoJ-Funktionen im NanoJ-Programm

Mit NanoJ-Funktionen ist es möglich, in der Firmware eingebaute Funktionen direkt aus einem Benutzerprogramm aufzurufen. Eine direkte Code-Ausführung ist nur in dem geschützten Bereich der geschützten Ausführungsumgebung möglich und wird über sogenannte Cortex-Supervisor-Calls (Svc Calls) realisiert. Dabei wird mit dem Aufruf der Funktion ein Interrupt ausgelöst und die Firmware hat so die Möglichkeit, temporär eine Code-Ausführung außerhalb der geschützten Ausführungsumgebung zuzulassen. Der Entwickler des Benutzerprogramms muss sich jedoch um diesen Mechanismus nicht kümmern - für ihn sind die NanoJ-Funktionen wie ganz normale C-Funktionen aufrufbar. Lediglich die Datei wrapper.h muss - wie üblich - eingebunden werden.

Zugriff auf das Objektverzeichnis

void od_write (U32 index, U32 subindex, U32 value)

Diese Funktion schreibt den übergebenen Wert an die angegebene Stelle in das Objektverzeichnis.

index Index des zu schreibenden Objekts im Objektverzeichnis
subindex Subindex des zu schreibenden Objekts im Objektverzeichnis
value zu schreibender Wert
Anmerkung: Es wird dringend empfohlen, nach dem Aufruf eines od_write() die Prozessorzeit mit yield() abzugeben. Der Wert wird zwar sofort ins OD geschrieben. Damit die Firmware jedoch davon abhängige Aktionen auslösen kann, muss diese Rechenzeit erhalten und somit das Benutzerprogramm beendet oder mit yield() unterbrochen worden sein.

U32 od_read (U32 index, U32 subindex)

Diese Funktion liest den Wert an der angegebenen Stelle aus dem Objektverzeichnis und gibt ihn zurück.

index Index des zu lesenden Objekts im Objektverzeichnis
subindex Subindex des zu lesenden Objekts im Objektverzeichnis
Rückgabewert Inhalt des OD-Eintrags
Anmerkung: Aktives Warten auf einen Wert im Objektverzeichnis sollte immer mit einem yield() verbunden werden.

Beispiel

while (od_read(2400,2) != 0) // wait until 2400:2 is set
{ yield(); }

Prozesssteuerung

void yield() 

Diese Funktion gibt die Prozessorzeit wieder an das Betriebssystem ab. Das Programm wird in der nächsten Zeitscheibe wieder an der Stelle nach dem Aufruf fortgesetzt.

void sleep (U32 ms)

Diese Funktion gibt die Prozessorzeit für die angegebene Zahl an Millisekunden an das Betriebssystem ab. Das Benutzerprogramm wird anschließend an der Stelle nach dem Aufruf fortgesetzt.

ms Zu wartende Zeit in Millisekunden

Debug-Ausgabe

Die folgenden Funktionen geben einen Wert in die Debug-Konsole aus. Sie unterscheiden sich anhand des Datentyps des zu übergebenden Parameters.

bool VmmDebugOutputString (const char *outstring)
bool VmmDebugOutputInt (const U32 val)
bool VmmDebugOutputByte (const U08 val)
bool VmmDebugOutputHalfWord (const U16 val)
bool VmmDebugOutputWord (const U32 val) 
bool VmmDebugOutputFloat (const float val) 
Anmerkung:

Die Debug-Ausgaben werden zunächst in einen eigenen Bereich des Objektverzeichnisses geschrieben und dann von dort von Plug & Drive Studio ausgelesen.

Dieser OD-Eintrag hat den Index 2600h und ist 64 Zeichen lang, siehe 2600h NanoJ Debug Output. In Subindex 00 ist immer die Anzahl der bereits geschriebenen Zeichen enthalten.

Ist der Puffer vollgeschrieben, so schlägt VmmDebugOutputxxx() zunächst fehl, das Benutzerprogramm wird dann nicht weiter ausgeführt und hält an der Stelle der Debug-Ausgabe an. Erst wenn die GUI den Puffer ausgelesen hat und danach Subindex 00 wieder zurückgesetzt hat, wird das Programm wieder fortgesetzt und VmmDebugOutputxxx() kehrt ins Benutzerprogramm zurück.

Anmerkung: Debug-Ausgaben dürfen daher nur während der Testphase bei der Entwicklung eines Benutzerprogramms verwendet werden.
Anmerkung: Nutzen Sie die Debug-Ausgabe nicht, wenn der AutoYield-Modus aktiviert ist (siehe Verfügbare Rechenzeit).

Einschränkungen und mögliche Probleme

Im Folgenden werden Einschränkungen und mögliche Probleme bei der Arbeit mit NanoJ aufgelistet:

Einschränkung/Problem Maßnahme
Wenn ein Objekt gemappt wird, z. B. 0x6040, wird das Objekt alle 1 ms auf seinen vorherigen Wert zurückgesetzt. Das macht die Steuerung dieses Objekts über den Feldbus oder das Plug & Drive Studio unmöglich. Greifen Sie stattdessen mit od_read / od_write auf das Objekt zu.
Wenn ein Objekt als Output gemappt wurde und der Wert des Objekts niemals vor dem Start des NanoJ-Programms festgelegt wird, kann der Wert dieses Objekts zufällig sein. Initialisieren Sie die Werte der gemappten Objekte in Ihrem NanoJ-Programm, damit es sich deterministisch verhält.
Die Array-Initialisierung darf nicht mit mehr als 16 Einträgen verwendet werden. Verwenden Sie stattdessen constant array.
Zu viele lokale Variablen und Arrays innerhalb von Funktionen können zu einem Stacküberlauf führen. Deklarieren Sie die Variablem global. Der Speicherbedarf wird bereits beim Kompilieren überwacht, es kommt nicht zu Fehlern zur Laufzeit.
Zu tief verschachtelte Funktionen können zu einem Stacküberlauf führen. Maximale Verschachtelungstiefe von 2 beachten.
float darf nicht mit Vergleichsoperatoren verwendet werden. Verwenden Sie stattdessen int.
double darf nicht verwendet werden.
Wenn ein NanoJ-Programm den Controller neu startet (entweder direkt durch einen expliziten Neustart oder indirekt, z. B. durch die Verwendung der Reset-Funktion), könnte der Controller in eine Neustartschleife geraten, der man nur schwer oder gar nicht entkommen kann.
math oder cmath können nicht einbezogen werden.
▶   weiter

Inhalt