NP5 EtherCAT 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.

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.

Sandbox

Durch prozessorspezifische Eigenschaften wird eine sogenannte Sandbox generiert. Ein Benutzerprogramm in der Sandbox 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 Systemcalls
  • Aufruf sonstiger Systemcalls (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 NanoJEasy gesteuert, ob eine Variable im Input-, Output- oder Datenbereich abgelegt wird.

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 Systemcalls 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 Systemcall zuzugreifen.

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

Tipp: Nanotec empfiehlt, entweder per Mapping oder Systemcall mit od_write() auf ein und denselben OD-Wert zuzugreifen. Wird beides gleichzeitig verwendet, so hat der Systemcall 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".

    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 ist nicht korrekt :

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. Ein Kommentar oberhalb des Mappings ist erlaubt.

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.

Beispiel eines Mappings

Beispiel eines Mappings und der zugehörigen Variablenzugriffe:

map U16 controlWord as output 0x6040:00
map U08 statusWord as input 0x6041:00
map U08 modeOfOperation as inout 0x6060:00

#include "wrapper.h"

void user()
{
  [...]
  Out.controlWord = 1;
  U08 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 Systemcalls 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.

Systemcalls im NanoJ-Programm

Mit Systemcalls 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 Sandbox 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 Sandbox zuzulassen. Der Entwickler des Benutzerprogramms muss sich jedoch um diesen Mechanismus nicht kümmern - für ihn sind die Systemcalls 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
▶   next

Contents