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.
sin
Funktion zu berechnen.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:
- Werte aus dem Objektverzeichnis lesen und in die Bereiche Inputs und Outputs kopieren
- Benutzerprogramm ausführen
- 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).
Eine Liste verfügbarer NanoJ-Funktionen findet sich im Kapitel NanoJ-Funktionen im NanoJ-Programm.
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:
- Umbenennen der Datei
TEST1.USR
invmmcode.usr
. - Kopieren der Datei
vmmcode.usr
über USB auf die Steuerung. - NanoJ-Programm starten durch Beschreiben von Objekt 2300h, Bit 0 = "1" bzw. durch Neustarten der Steuerung.
- Überprüfen des Eintrags 2302h auf Fehlercode und des Objekts 2301h, Bit 0 = "1" (NanoJ-Programm läuft).
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.
main.cpp
ist zulässig, Dateiname
einLangerDateiname.cpp
ist nicht zulässig.- 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.
- 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()
undod_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
oderinout
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:
// 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:
- Die Funktion
od_write
schreibt den Wert5
in das Objekt 6040h:00h. - Am Ende des 1 ms-Zyklus wird das Mapping geschrieben, welches ebenfalls
das Objekt 6040h:00h beschreibt, allerdings mit
dem Wert
1
. - 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 |
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 |
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)
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.
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 . |
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. |