Neuron-C - TU Dresden

Transcrição

Neuron-C - TU Dresden
Neuron-C
Fakultät Informatik
Institut für Angewandte Informatik
Professur Technische Informationssysteme
Inhaltsverzeichnis
1.
1.1
1.2
1.3
1.4
1.5
Allgemeines zur Verwendung von Neuron-C
Programmaufbau in Neuron-C
Datentypen in Neuron-C
Speicherklassen
Spezifikation von Speicherbereichen
Weitere Unterschiede von Neuron-C zu ANSI-C
2.
2.1
2.2
Das ereignisgesteuerte Betriebssystem
Der Scheduler
Ereignisse
3.
3.1
3.2
Die Neuron-Chip-Hardware
IO-Geräte
Timer
4.
4.1
4.2
4.3
Programmierung des Neuron-Chips als Single-System
Erste Schritte
Programmierung der Timer
IO-Pins und IO-Kanäle
5.
5.1.
5.2.
5.3.
5.4.
5.5.
5.6.
Kommunikation über Netzwerkvariablen
Allgemeines
Ereignisse für Netzwerkvariable
Synchrone Netzwerkvariablen
Netzwerkvariablen mit Abfrage (Polling)
Überwachung von Netzwerkvariablen
Authentication - Autorisierung einer Übertragung
6.
6.1.
6.2.
6.3.
6.4.
6.5.
6.6.
6.7.
Kommunikation mittels expliziter Botschaften
Schichten der NEURON Chip Software
Allgemeines zu Botschaften
Fertig-Ereignisse für Botschaften
Botschaften und der Preemption Modus
Asynchrone und direkte Ereignisverarbeitung
Anfrage - Antwort - Mechanismus (Request - Response)
Puffer
7.
7.1.
7.2.
7.3.
Bemerkungen zum Speicher-Management
Adreß-Tabellen
Allokieren von Puffern
Anzahl der Empfangs-Transaktionen
8.
8.1.
8.2.
8.3.
8.4.
Verschiedenes
Scheduler-Reset-Mechanismus
Der Watchdog-Timer
Zusätzliche vordefinierte Ereignisse vom Netzwerk Management Tool
Fehler-Behandlung
9.
Komplexes Beispiel
2
1. Allgemeines zur Verwendung von Neuron-C
Neuron-C ist ein an die speziellen Anforderungen des Neuron-Chips angepaßtes Programmiersystem, welches
stark an die Programmiersprache C angelehnt ist und wesentliche Elemente dieser enthält. Erweitert wurde das
System durch sprachliche Möglichkeiten zur Bedienung von internen und externen Ereignissen, sowie zur
Kommunikation zwischen den Neuron-Chips mittels LONWORKS-Protokoll. Es sollte deshalb leicht fallen,
Neuron-C zu erlernen, wenn man über Erfahrungen in der C-Programmierung verfügt.
Aus diesem Grunde wird im Folgenden nur auf die Besonderheiten von Neuron-C eingegangen.
1.1 Programmaufbau in Neuron-C
Anders als in C gibt es in Neuron-C keine main()-Prozedur, da das System vollständig ereignisorientiert ist.
Auf Ereignisse kann mit Hilfe der when()-Funktion reagiert werden.
1.2 Datentypen in Neuron-C
In Neuron-C werden standartmäßig 8-Bit- und 16-Bit-Datentypen unterstützt.
Folgende Tabelle gibt Aufschluß über die verschiedenen Typen:
Typ
[ signed | unsigned ] long int
[ signed | unsigned ] char
[ signed | unsigned ] [ short ] int
enum ( int-Datentyp)
Datenbreite
16 Bit
8 Bit
8 Bit
8 Bit
Eine Gleitkomma-Bibliothek ist verfügbar und bei Bedarf nachladbar. Für deren Benutzung sei auf entsprechende Literatur verwiesen.
1.3 Speicherklassen
In Neuron-C sind ähnlich wie in ANSI-C folgende Speicherklassen verwendbar:
auto
const
extern
static
Zusätzlich folgende Klassen:
config
network
system
plaziert Konfiguration im chip-eigenen EEPROM
Netzwerkvariable
erlaubt Zugriff auf Firmware-Funktion
1.4 Spezifikation von Speicherbereichen
Im Unterschied zu ANSI-C kann in Neuron-C genau spezifiert werden, wo im Neuron-Chip Daten und Code
abgelegt werden sollen. Dazu existieren folgende Präfixe:
eeprom
far, ram
belegt chip-eigenen EEPROM-Speicher
belegt chip-eigenen RAM-Speicher
1.5 Weitere Unterschiede von Neuron-C zu ANSI-C
- es wird standartmäßig keine Gleitkomma-Arithmetik unterstützt
- short int wird als 8-Bit-Datentyp statt als 16-Bit-Datentyp realisiert
long int wird als 16-Bit-Datentyp statt als 32-Bit-Datentyp realisiert
- register und volatile Klassen werden nicht unterstützt
- struct und unions können nicht als Rückkehrwerte, Prozedurparameter oder automatic-Variablen
verwendet werden
- es sind keine Felder als automatic-Variablen möglich
- in Verwendung mit Netzwerkfunktionen können keine Pointer verwendet werden
- für Bezeichner sind die ersten 16 Zeichen relevant
- standartmäßig sind nur die Bibliotheken bzw. Header-Dateien STDDEF.H, STDLIB.H, LIMITS.H installiert
3
2. Das ereignisgesteuerte Betriebssystem
Auf dem Neuron-Chip läuft eine fest installierte Firmware, die ein komfortables ereignisorientes Betriebs-system
darstellt. Ereignisorientiert heißt dabei, daß das Betriebssystem Timer, Ein-/Ausgabe-Ereignisse und
Netzwerkereignisse verwaltet und als Hochspracheninterface dem Programmierer zur Verfügung stellt. Eine der
wichtigsten Programmfunktionen stellt dabei der Task-Scheduler dar, welcher in einer festen Reihenfolge alle
möglichen Ereignisse auf deren Auftreten hin untersucht und gegebenfalls in entsprechende Funktionen
verzweigt.
2.1 Der Scheduler
Der Scheduler stellt das Herz der Neuron-Firmware dar. Er verwaltet alle eintreffenden Meldungen und
Ereignisse auf folgende Weise:
RESET
TASK
TASK
TASK
T
PRIORITY
WHEN
TASK
T
F
PRIORITY
WHEN
T
F
PRIORITY
WHEN
F
T
TASK
WHEN
F
TASK
SCHEDULER
T
TASK
WHEN
F
...
...
WHEN
Der Scheduler prüft in der Reihenfolge des Auftretens von when-Bedingungen im Programm, ob eine solche
erfüllt ist, und wenn dies der Fall ist, so verzweigt er in die zugehörige TASK-Routine. Eine when-Bedingung
und deren TASK-Routine ist also die Antwort auf ein Ereignis.
Nach Abfrage und eventueller Abarbeitung einer when-Bedingung wird getestet, ob eine priority whenBedingung erfüllt ist und diese dann entsprechend verarbeitet. Die priority when-Bedingung stellt somit
eine höher privilegierte when-Bedingung dar, welche dazu genutzt werden kann, um besonders zeitkritische
Routinen zu verwirklichen.
2.2 Ereignisse
Das Betriebssystem der Neuron-Chips stellt Ereignisse in Form von logischen Variablen zur Verfügung. Somit
kann auf alle Ereignisse durch einen entsprechenden Vergleich reagiert werden.
Es sollte bevorzugt in den when-Bedingungen auf Ereignisse reagiert werden, um das betriebssystemeigene
Scheduling nicht zu unterbrechen. Da when-Bedingungen nicht schachtelbar sind, ist es jedoch denkbar,
Ereignisvariable auch in for, if und while-Ausdrücken zu verwenden.
4
Folgende Ereignisse werden vom System standartmäßig unterstützt:
-Timer-Ereignisse werden generiert, wenn die auf dem Chip vorhandenen Counter und Timer einen Nulldurchlauf haben
-IO-Ereignisse treten auf, wenn an den externen IO-Ports Änderungen stattfinden
-Netzwerk-Ereignisse werden erzeugt, falls im angeschlossenen Netzwerk Übetragungen stattfinden
-System-Ereignisse entstehen bei Ausführung spezieller Systemroutinen wie beispielsweise der RESET-Routine
Eins der wichtigsten Ereignisse ist das RESET-Ereignis, welches auftritt, wenn der Neuron-Chip intitialisiert
bzw. durch ein externes RESET-Signal rückgesetzt wird. Es tritt somit zu Beginn jedes neuen Programmstarts
auf und sollte verwendet werden, um alle wichtigen Systemvariablen zu reinitialisieren .
Andere benutzereigne Ereignisse können definiert und benutzt werden.
Weiterhin kann bei vielen Ereignissen auch auf entsprechende Rückkehrwerte zugegriffen werden, welche zum
Ereignis zugehörige Werte enthalten.
5
3. Die Neuron-Chip-Hardware
3.1 IO-Geräte
Die IO-Geräte auf dem Neuron-Chip stellen die Vebindung zur Außenwelt her.
Folgendes Schema gibt einen Überblick über die Organisation dieser Geräte auf einem LON-Works-Knoten:
IO_10
frei
IO_9
frei
IO_8
frei
IO_7
Counter/Timer 1
IO_6
4 zu 1
MUX
IO_5
IO_4
IO_3
frei
IO_2
frei
Counter/Timer 2
IO_1
IO_0
Hierbei ist zu beachten, daß die IO-Leitungen 0..1 sowie 4..7 schon hardwaremäßig vorbelegt sind. Auf den
LON-Works-Knoten sind dabei die Leitung 0 als Ausgabe an eine sichtbare LED gekoppelt, während
Leitung 4 eine Eingabe mittels Taster realisiert.
Alle anderen Ein- und Ausgabeleitungen sind frei verwend- und konfigurierbar.
3.2 Timer
Auf den Neuron-Chips existieren zwei Typen von Timern: Millisekunden- und Sekunden-Timer.
Beide Zähler sind 16 Bit breit und können dementsprechend von 1 .. 65535 ms bzw. 1 .. 65535 s zählen.
6
4. Programmierung des Neuron-Chips als Single-System
4.1 Erste Schritte
Die Programmierung des Neuron-Chip ist komfortabel über die dem ANSI-C ähnliche Programmiersprache
´Neuron-C´ möglich.
Zu beachten ist jedoch, daß die Neuron-Chip-Software ereignisgesteuert ist und sich somit die Struktur der
Neuron-C Programme erheblich von ANSI-C Programmen unterscheidet. Auf weitere Unterschiede wurde schon
weiter vorn hingewiesen.
Das erste Programm:
#include "first.h"
int index;
when (reset)
{
index = 0;
}
In diesem Programm wird in der when-Bedingung, welche auf das RESET-Ereignis reagiert, die Variable
index initialisiert. Weiterhin wird die Header-Datei "first.h" und damit evtl. entsprechende Objekt-Dateien zum
endgültigen Programm hinzugelinkt.
Aus diesem Beispiel ist die allgemeine Struktur der Neuron-C Programme erkennbar:
[ #include ".." ]
<- Header einbinden
[ Variablendeklaration ]
<- Variablen deklarieren
[ when ( Ereignis1 ) Task1 ]
[ when ( Ereignis2 ) Task2 ]
...
<- Reaktion auf Ereignisse
Syntax der when-Bedingung:
[priority] when ( Ereignis) task;
Bei Eintritt von Ereignis wird die Routine task ausgeführt. Durch das Schlüsselwort priority kann die
Ausführung höher privilegiert werden als die normale Ausführung.
4.2 Programmierung der Timer
Bis jetzt kann das Programm noch nicht auf irgendwelche andere Ereignisse als dem RESET-Ereignis reagieren.
Um einen zeitabhängigen Programmablauf zu realisieren, existieren die Timer bzw Zähler.
Das Betriebssystem stellt insgesamt 15 Timer zur Verfügung, welche als Sekunden- und Millisekunden-Timer
konfiguriert werden können. Die Zeitdauer kann bei beiden Timertypen mit einem 16-Bit Wert dargestellt
werden, so daß sich Zeiten von 0..65535s bzw. 0..65535ms einstellen lassen. Weiterhin ist es möglich, Timer als
einmaligen Ablauf oder sich wiederholende Folge zu realisieren.
Ist ein Timer abgelaufen, so wird ein entsprechendes Ereigniss generiert, auf welches in einer
when-Bedingung reagiert werden kann.
Syntax der Timerdeklaration:
mtimer [ repeating ] timer_name;
stimer [ repeating ] timer_name;
// ms-Timer
// s-Timer
Mit diesen Anweisungen können Timer deklariert werden, wobei durch Zusatz von repeating ein sich selbst
wiederholender Timer definiert werden kann.
7
Start eines Timers:
timer_name = start_wert;
// Zeitdauer = startwert in [s/ms]
Stop eines Timers:
timer_name = 0;
Ablesen eines Timers:
neu_wert = timer_name;
Reaktion auf ein Timerereignis:
timer_expires
timer_expires( timer_name )
// wird TRUE, wenn irgendein Timerereignis auftrat
// wird TRUE, wenn der Timer timer_name
abgelaufen ist
Beispiel für Timerverwendung:
int index;
mstimer repeating timer_one;
stimer timer_two
when (reset)
{ timer_one = 200;
timer_two = 2;
index = 0;
}
// wiederholender Timer mit Periodendauer 200ms
// einmaliger Timer mit Zeitdauer von 2s
when ( timer_expires )
// auf jedes Timerereignis wird reagiert
{ if ( timer_expires( timer_one)) index++;// explizite Testung auf Timer timer_one
else index = 0;
}
4.3 IO-Pins und IO-Kanäle
Mit Hilfe der nach außen geführten Pins bzw. IO-Leitungen kann ein direkter Kontakt mit der Umwelt
hergestellt werden. Insgesamt unterstützt das Betriebssystem 16 IO-Kanäle.
Um eine der IO-Leitungen anzusprechen, ist eine entsprechende Variable zu deklarieren. Dabei sind INPUT und
OUTPUT-Variablen zu unterscheiden. Es ist aber möglich, einen OUTPUT-Kanal zusätzlich als INPUT-Kanal
zu deklarieren und hat damit die Möglichkeit, den zuletzt ausgegebenen Wert zurückzulesen.
Syntax einer IO-Kanals-Deklaration:
io_kanal input|output [ model] device_name [= initial_level];
Es existieren folgende Modelle, die ein komfortables Interface unterstützen:
Für direkte IO-Kanäle sind verwendbar:
-bit .. weist dem Kanal ein 1-Bit Ein- oder Ausgabe-Modell zu
-nibble .. faßt die IO-Leitungen 4-7 zu einem 4-Bit breitem Modell zusammen und ermöglicht ein paralleles
Interface zur Komunikation
8
-byte .. benutzt 8 IO-Leitungen für das paralleles Übertragen eines Bytes
-bitshift .. ermöglicht serielle Ein- / Ausgabe über eine 2-Draht-Leitung
Unter Verwendung der Timer-IO sind verwendbar:
-oneshot .. stellt ein Modell dar, bei dem eine IO-Leitung nach einer Ausgabe auf diesem Kanal nach
einstellbarer Triggerzeitauf zu ihrem Ausgangspegel zurückkehrt. Es wird also eine Monoflop-Triggerung
realisiert.
-pulsewidth .. ist ein Modell, bei dem folgende Impulsfolgen ausgegeben werden können:
-quadrature .. bestimmt Drehrichtung und Schritte eines Drehdecoders
-frequency .. zur Generation eines Rechtecksignals mit bestimmbarer Frequenz
-period .. mißt die Periodenzeit eines Eingangssignal
-ontime .. mißt die High/Low-Periodenzeit eines Eingangssignal
-pulsecount .. zählt Eingangsimpulse
-triac .. ein Modell speziell für Phasenanschnittsteuerungen
Eingabe über einen IO-Kanal:
return_value = io_in( device_name [, args]); // liest den aktuellen Pegel vom Kanal
device_name ein
Außerdem ist es möglich, über die vordefinierte Variable input_value den aktuellen Rückgabewert der
letzten io_in(), io_changes() oder io_update_occurs()-Anweisung zu ermitteln oder
über die vordefinierte bool´sche Variable input_is_new eine Änderung des Eingangspegel zu erkennen.
Ausgabe auf einen IO-Kanal:
io_out( device_name, output_value [, args]); // Ausgabe von output_value auf den
Kanal device_name
Reaktion auf ein IO-Ereignis:
io_changes [ by const_exp|to const_exp] // wird TRUE, wenn irgendeine Änderung auf
den Kanälen auftrat, wobei noch spezifiziert werden kann, welche Änderung Berücksichtigung findet
io_changes( device_name) [ by const_exp|to const_exp]
// wird TRUE, wenn eine Änderung auf dem
Kanal device_name auftrat, sonst wie oben
9
Achtung! Falls in einer when-Bedingung mittels io_changes() auf ein IO-Ereignis gewartet wird, so ist es
oft günstig, den Rückgabewert nicht mittels io_in() zu ermitteln, sondern auf die vordefinierte Variable
input_value zurückzugreifen, welche den aktuellen Wert enthält. Dieses Vorgehen vermeidet einen
zweifachen IO-Kanal-Zugriff und spart damit Rechenzeit.
10
5. Kommunikation über Netzwerkvariablen
5.1. Allgemeines
Eine Netzwerkvariable ist ein Objekt, das mit verschiedenen Knoten verbunden werden kann. Sie wird zuerst auf
einfachen Knoten unabhängig von anderen Knoten definiert.
* Einfache Deklarationen sehen folgendermaßen aus:
network output SNVT_lev_disc Var_Name1
network input SNVT_lev_disc Var_Name2
* Vollständiger Syntax:
network inputoutput [netvar-modifier] [class] type [connection-info]
identifier [= initial-Wert];
network inputoutput [netvar-modifier] [class] type [connection-info]
identifier [array-grenze] [= initial-Wert];
dabei bedeutet:
- input .. Variable lesen
- output .. Variable schreiben
Die auf den einzelnen Knoten definierten Netzwerkvariablen werden vom Network Management Tool unter dem
Bezeichner "Temperatur" (siehe Grafik) verbunden.
Auf einem Knoten können bis zu 62 Netzwerkvariablen definiert werden.. Mit dem LONBUILDER
Microprocessor Interface Program ist es möglich, bis zu 4096 Netzwerkvariablen zu definieren.
Netzwerkvariablen können auch in Form von eindimensionalen Arrays vereinbart werden.
Ein Knoten, der die Netzwerkvariable schreibt, kann mit mehreren Knoten, die die Netzwerkvariable lesen,
verbunden werden. Der Empfang einer aktualisierten Netzwerkvariable erfolgt nicht gleichzeitig. Ein LeseKnoten kann eine Netzwerkvariable intern ändern, ohne daß die anderen Knoten davon erfahren. Man kann auch
für eine Netzwerkvariable zwei Schreib-Knoten vereinbaren. Dabei ist zu beachten, daß eine Änderung der
Netzwerkvariable, die Schreib-Knoten Nr. 1 vornimmt, vom Schreib-Knoten Nr. 2 nicht erkannt werden kann.
Netzwerkvariablen werden nicht sofort gesendet bzw. empfangen, sondern erst nach Ablauf einer critical
section.
Modifikationen von Netzwerkvariablen - [netvar-modifier]
sync / synchronized: bei mehrmaligem Update einer Netzwerkvariablen innerhalb eines Tasks wird nur der
letzte Wert geschickt.
polled: kann nur für Netzwerkvariablen verwendet werden, die als output deklariert sind. Eine Netzwerkvariable
wird nur über das Netz geschickt, wenn vom Empfänger eine entsprechende Aufforderung eingeht. Es
erfolgt demzufolge kein automatisches Verschicken eines neuen Wertes.
11
Klassen von Netzwerkvariablen - [class]
Diese Klassen sind spezielle Speicherklassen.
const : Netzwerkvariablen, die nicht vom Programm während der Laufzeit geändert werden können.
eeprom: Netzwerkvariablen, die so deklariert sind, werden im EEPROM gespeichert, so daß sie bei einer
Stromabschaltung nicht verlorengehen.
config: spezifiziert eine konstante Netzwerkvariable, die nur vom Network Management Tool bzw. Network
Controller geändert werden kann.
Netzwerkvariablen, die ohne Spezifikationen deklariert werden, sind global. Sie werden im RAM des Neuron
Chips gespeichert.
Informationen über Verbindungen von Netzwerkvariablen - [connection-info]
connection-info: Optionales Feld, das zusätzliche Attribut für Netzwerkvariablen-Verbindungen enthält. Kann
nur vom Management Tool geändert werden, wenn sie als nonconfig deklariert wurde.
-> Für zusätzliche Informationen zur Deklaration siehe Neuron C Programmer's Guide Seite 3-10 und C-76ff.
Inititalisierung von Netzwerkvariablen
Beim Programmstart sollten alle Variablen auf einen vernünftigen Wert initialisiert werden. Dies ist besonders
für input - Netzwerkvariablen wichtig. Auch für eeprom und config deklarierte Variablen ist dies notwendig.
Initialisierungen werden nur beim Einschalten oder bei einem Reset (wenn nicht als const, eeprom oder config
deklariert) vorgenommen.
network input SNVT_temp nv_temp = 2692;
Typen von Netzwerkvariable
Diese Typen sind bei der Compilierung eines Programms wichtig, da in diesem Fall der Compiler evtl. Fehler
erkennen kann. Für das Verbinden der Knoten nach der Erstellung des Programms sind gleiche Typen für
verbundene Netzwerkvariable erforderlich.
ACHTUNG: Netzwerkvariablen dürfen nicht als Pointer deklariert werden. Auch die Verwendung des '&' ist
unzulässig.
Eine Netzwerkvariable kann eine maximale Größe von 31 Bytes haben. Für ein Array gilt: Jedes Array-Element
kann maximal 31 Bytes groß sein.
Wird ein Array-Element über das Netz aktualisiert, so erfolgt dies erst im nächsten Task und zwar nur für das
geänderte Element.
[signed] long int
unsigned long int
signed char
[unsigned] char
[signed] [short] int
unsigned [short] int
enums (int type)
structs und unions der oben genannten Typen, die Arrays, aber keine Zeiger enthalten dürfen.
eindimensionale Arrays von oben genannten Typen mit bis zu 62 Elementen.
Standard-Netzwerk-Variablen-Typen SNVTs
Diese SNVTs sind im LONTALK-Protokoll vordefiniert. Sie sollten, wann immer es möglich ist, benutzt
werden. Sie verbessern das Zusammenwirken, vereinfachen die Installation. Über den Compiler ist eine Selbst12
Identifikation (SI) und auch die Selbst-Dokumentation (SD) möglich. Identifiziert werden bestimmte Daten, z.B.
der SNVT-Index.
SNVTs sind in snvt_lev.h enthalten.
#include <snvt_lev.h>
Beispiele:
(1) Einfache Deklarationen:
network input SNVT_temp temp_set_point
network output SNVT_lev_disc primary_heater
network output int current_lamp
(2) Prioritäts-Deklarationen
network output boolean bind_info(priority) fire_alarm
network output boolean bind_info(priority(nonconfig)) fire_alarm
(3) Wenn keine Bestätigung benötigt wird:
network input SNVT_lev_contin bind_info(unackd) control_dial;
Programm-Beispiel:
//lamp.nc - Generic Program for a lamp. Eine input-Netzwerkvariable
kontrolliert den Status der Lampe
#include<snvt_lev.h>
IO_0 output bit io_lamp_control;
//Deklaration der Netzwerkvariable
network input SNVT_lev_disc nv_lamp_state;
//Konfiguration - nur vom Netzwerk Management Tool änderbar
network input config SNVT_lev_disc nv_lamp_default = ST_OFF;
//Ereignisgesteuerter Code
when(reset)
{
//Setzen der Variable, wie in der Konfiguration festgelegt
io_out(io_lamp_control, (nv_lamp_default == ST_OFF) ? 0:1);
}
when (nv_update_occurs(nv_lamp_state))
{
io_out(io_lamp_control, (nv_lamp_state == ST_OFF) ? 0:1);
}
Die Funktion is_bound()
Sie findet heraus, ob eine Netzwerkvariable mit einer anderen Netzwerkvariablen verbunden ist.
Eine nichtverbundene Netzwerkvariable kann ein nv_update_succeeds Ereignis auslösen, obwohl kein aktuelles
Update vorkam.
Mit der is_bound() Funktion wird dies vermieden, der entsprechende Code wird nur ausgeführt, wenn eine
Verbindung tatsächlich besteht.
network output SNVT_lev_disc heater_2;
void turn_on_heater_2(void)
{
if (is_bound(heater_2)) heater_2 = ST_ON;
}
13
5.2. Ereignisse für Netzwerkvariable
nv_update_occurs [(Netzwerk-Variable)]
nv_update_succeeds [(Netzwerk-Variable)]
nv_update_completes [(Netzwerk-Variable)]
nv_update_fails [(Netzwerk-Variable)]
nv_update_occurs kann nur für input-Variablen verwendet werden, die anderen drei Ereignisse sind nur für
output-Variablen vorgesehen. Die Ereignisse können mit Variablennamen qualifiziert werden. Bei Arrays tritt
das Ereignis für jedes Element einmal ein.
=> Wird kein qualifizierter Variablenname angegeben, so wird das Ereignis für jede Netzwerkvariable wahr.
nv_update_occurs
Dieses Ereignis ist TRUE, wenn ein neuer Wert für eine Netzwerkvariable eingeht.
when (nv_update_occurs(nv_lamp_state))
{
io_out(io_lamp_control,(nv_lamp_state != ST_OFF) ? 1:0);
}
nv_update_fails
Update oder poll einer Variable war fehlerhaft, wenn dieses Ereignis TRUE ist. Für mehrfache
Netzwerkvariablen wird das Ereignis für jede Netzwerkvariable TRUE, die fehlerhaft beim Übertragen war.
when (nv_update_fails(nv_switch_state))
{
// Korrekturen, andere Aktionen
}
nv_update_succeeds
Diese Ereignis ist TRUE, wenn das Übertragen der Netzwerkvariablen erfolgreich war.
nv_update_completes
Dieses Ereignis ist TRUE, wenn ein Update einer output-Variablen entweder erfolgreich oder mißlungen war.
ACHTUNG: Wenn eine Netzwerkvariable mit nv_update_completes oder nv_update_succeeds geprüft wird, so
müssen auch alle anderen Netzwerkvariablen auf diese Ereignisse geprüft werden. Dies ist einfach über eine
unqualifizierte when(...) Abfrage möglich:
when (nv_update_completes) { /*Anweisungen*/ }
when (nv_update_succeeds) { /*Anweisungen*/ }
ACHTUNG: Wenn nach einem unqualifizierten Check (nonpriority) qualifizierte when-Fälle für ein
bestimmtes Ereignis folgen, dann muß das Programm mit
#pragma scheduler_reset
am Anfang spezifiziert werden. Dadurch können die when-Fälle vom Scheduler in geeigneter Reihenfolge
bewertet werden.
Wenn nur auf nv_update_fails geprüft wird, ist ein Prüfen der anderen output-Netzwerkvariable nicht
notwendig.
weiterführendes Beispiel Neuron C Programmer's Guide S. 3-28f
14
5.3. Synchrone Netzwerkvariablen
Bei nicht-synchronen Netzwerkvariablen geschieht folgendes: Updates können sich bei einer input-Variablen
schneller ereignen, als sie vom Scheduler verschickt werden können. In diesem Fall wird nur der letzte Wert
geschickt. Dies kann mit synchronen Netzwerkvariablen vermieden werden.
Deklaration:
network input sync SNVT_temp Name
Wenn auf einem Knoten eine Netzwerkvariable als synchron deklariert wurde, muß die damit verbundene
Variable auf dem anderen Knoten nicht zwingend als synchron deklariert sein. Der LONBUILDER erzeugt in
diesem Fall aber eine Warnung.
Beispiel für Variable, die verschiedene Alarmzustände enthält:
network output synchronized enum {
none,
high_temperature,
high_pressure,
low_flow,
control_failure,
} sensor alarm;
Vorteile und Nachteile synchroner Netzwerkvariablen
Synchrone Netzwerkvariablen sollten nur benutzt werden, wenn dies unumgänglich ist. Die meisten
Applikationen kommen mit nicht-synchronen Netzwerkvariablen aus. Ständige Updates von synchronen
Netzwerkvariablen können die Ausführungsgeschwindigkeit verlangsamen, wenn dem Programm die Puffer
ausgehen. Übermäßiges Nutzen von synchronen Netzwerkvariablen wirkt sich nachteilig auf die Performance
aus.
Synchrone Netzwerkvariablen werden benutzt, wenn der Mittelwert der Aktualisierungen festgehalten werden
muß. Wird nur der absolute Wert eine Netzwerkvariablen gebraucht, werden keine synchronen
Netzwerkvariablen benutzt.
Bei nicht-synchronen Netzwerkvariablen geht eine Aktualisierung nur auf das Netz, wenn ein Puffer im
Empfänger bereitsteht. Erfolgen auch Aktualisierungen, bevor ein Puffer frei ist, so gehen diese verloren. Bei
synchronen Netzwerkvariablen wird das Programm angehalten, bis ein Puffer im Empfänger frei ist. Der
Scheduler geht in diesem Fall in den "Preemption Mode". Ein nv_update_occurs-Ereignis wird bei jedem
Empfang einer Netzwerkvariablen TRUE, wenn ein neuer Wert auf der Netzwerkvariablen anliegt und ein InputPuffer frei ist. Im Gegensatz dazu wird bei nicht-synchronen Netzwerkvariablen ein nv_update_occurs-Ereignis
nur einmal TRUE, obwohl mehrere Updates dieser Variablen vorliegen können.
Aktualisierung von synchronen Netzwerkvariablen
Synchrone Netzwerkvariablen werden immer am Ende einer critical section aktualisiert. Ist kein Puffer frei, geht
das System in den Preemption Mode.
(Nicht-synchrone Netzwerkvariablen werden am Ende einer critical section nur aktualisiert, wenn der Scheduler
Zeit hat und ein Puffer frei ist, d.h. am Ende einer critical section wird nicht immer aktualisiert.)
5.4. Netzwerkvariablen mit Abfrage (Polling)
Normalfall: Die Aktualisierung wird vom Schreib-Knoten initiiert.
Mit Abfrage bedeutet: Der Lese-Knoten fordert vom Schreib-Knoten, daß der aktuelle Wert eine
Netzwerkvariablen geschickt werden soll.
Der Lese-Knoten kann jede input-Variable zu jeder Zeit anfordern (einschließlich Einschalten und Umschalten
von offline zu online). Es kann aber passieren, daß beim Einschalten viele Knoten Abfragen verschicken
(demzufolge zur gleichen Zeit), so daß ein Netzwerk-Stau die Folge ist.
Im Programm stellt der Leseknoten die Anfrage mittels der poll-Funktion:
15
poll ([network-var]);
Ist network-var ein Array, so wird für alle Array-Elemente die Abfrage ausgelöst. Wird keine spezielle Variable
angegeben, so wird die Anfrage für alle Netzwerkvariablen ausgelöst. Es ist zu beachten, daß der Wert der
Netzwerkvariablen nicht sofort nach Aufruf von poll(Netzwerkvariablenname) verfügbar ist. Der Eingang des
neuen Wertes wird mittels nv_update_occurs in einer when-Schleife abgefragt.
Achtung: Man kann im Programm des Schreibknotens nicht explizit auf eine poll-Abfrage vom Leseknoten
reagieren.
* Deklaration einer Netzwerkvariablen als polled im Schreib-Knoten:
network output polled type Netzwerkvariablenname;
Diese Deklaration bewirkt, daß der Schreib-Knoten den neuen Wert der Variablen nicht automatisch bei einer
Änderung verschickt. Es wird nur auf eine spezielle Anfrage eines Lese-Knotens der neue Wert übergeben.
* Deklaration im Leseknoten:
network input type Netzwerkvariablenname;
Beachte: Der Typ polled wird nur im Schreibknoten angegeben.
Die Abfrage vom Leseknoten und die darauffolgenden Aktionen sehen z.B. folgendermaßen aus:
when (...)
{
poll(Netzwerkvariablenname);
}
when (nv_update_occurs(Netzwerkvariablenname))
{...};
5.5. Überwachung von Netzwerkvariablen
Netzwerk-Überwachung erfolgt mittels eines LONWORKS-Knotens, der viele andere Knoten überwachen muß.
Z.B. muß ein Knoten, der einen Alarm anzeigt, viele Alarmsensoren überwachen. Die Sensorknoten haben eine
output-Netzwerkvariable, die mit SNVT_lev_disc deklariert wurde, der Überwachungs-Knoten hat eine inputNetzwerkvariable, die ebenfalls als SNVT_lev_disc deklariert wurde. Über diesen einen Eingang laufen alle
Sensorereignisse ein. Bei Änderung des Eingangs am Überwachungsknoten ist es demzufolge notwendig, den
entsprechenden Sensorknoten zu finden, der für die Änderung der Variablen verantwortlich ist. Dazu kann man
sich folgender Techniken bedienen:
(1) Die input-Netzwerkvariable am Überwachungsknoten wird als Array definiert, jedes Array-Element wird mit
einem Sensor verbunden. Bei Aktualisierung eines Array-Elements tritt ein nv_update_occurs Ereignis auf. Der
Sender kann mittels nv_array_index herausgefunden werden.
network input SNVT_lev_disc nv_alarm_array[50];
SNVT_lev_disc alarm_value;
unsigned int alarm_node;
when (nv_update_occurs(nv_alarm_array))
{
alarm_node = nv_array_index;
alarm_value = nv_alarm_array[alarm_node];
//........
}
Die Größe des Array ist begrenzt. Mit dieser Methode können maximal 62 Sensoren auf NEURON-Chipbasierten Knoten und 4096 Sensoren auf MIP-basierten Knoten überwacht werden.
(2) Der Eingang am Überwachungsknoten wird als einfacher input deklariert, die Ausgänge der Sensoren
werden als polled deklariert. Es wird eine einfache Verbindung der input-Variablen mit allen Sensor-outputVariablen hergestellt. Während des Betriebes ruft der Überwachungsknoten nun der Reihe nach jeden Sensor
unter Benutzung expliziter Adressen und expliziter Meldungen auf.
Diese Methode ist nur verwendbar, solange die Verzögerung durch die Polling-Schleife akzeptabel ist.
16
(3) Die Netzwerkvariablear im Überwachung wird als einfacher Eingang deklariert, es wird eine einfache
Verbindung zu allen Sensoren hergestellt. Ändert ein Sensor-Knoten den Wert der Netzwerkvariablen, die wird
ein nv_update_occurs Ereignis im Überwachungsknoten ausgelöst. Den Absender des Updates kann man nun
folgendermaßen ermitteln: Mit nv_in_addr kann der Sensor gefunden werden. Dabei werden LONTALKnetwork-management-Botschaften benutzt, um den Knoten zu identifizieren.
network input SNVT_lev_disc nv_alarm_in;
SNVT_lev_disc alarm_value;
nv_in_addr_t alarm_node_addr;
when(nv_update_occurs(nv_alarm_in))
{
alarm_node_addr = nv_in_addr;
alarm_value = nv_alarm_in;
// alarm_node_addr und alarm_value verarbeiten
// Network Management Botschaften nutzen, um Informationen vom Knoten
// alarm_node_addr zu bekommen
}
Read memory message wird benutzt, um den location string des Sensor-Knotens zu lesen.
Diese Methode ist für eine beliebige Anzahl von Sensoren geeignet.
5.6. Authentication - Autorisierung einer Übertragung
Dies ist eine spezieller Service zwischen einem Schreib-Knoten und 1 bis 63 Lese-Knoten. Hiermit kann die
Identität eines Schreibknotens geprüft werden. Z.B. kann dies bei einem elektrischen Schließsystem verwendet
werden, um zu prüfen, ob die Botschaft, die Tür zu öffnen, vom Eigentümer oder von einem Einbrecher stammt.
Diese Überprüfung der Autorisierung führt zu einer Verdopplung der Transaktionen. Autorisierte Botschaften
erfordern 4 Transaktionen (die anderen nur 2: update und Bestätigung). Dies beansprucht natürlich wieder die
Rechenzeit und die Kapazität.
Einrichten eines Knoten mit autorisierten Netzwerkvariablen
Die Netzwerkvariable wird mittels des reservierter Worte authenticated oder kurz auth in den VerbindungsInformationen deklariert.
bind_info ([authenticated | nonauthenticated] [(config | nonconfig)])
zum Beispiel:
network output boolean bind_info(auth(nonconfig)) nv_safe_lock;
Das Aufnehmen des Wortes config hat zur Folge, daß das Management Network Tool den Autorisierungs-Status
dieser Netzwerkvariablen ändern kann, nachdem der Knoten installiert wurde. Wird dagegen nonconfig gesetzt,
ist ein Ändern mittels nv_safe_lock nicht möglich.
Spezifizieren des Autorisierungsschlüssels
Dies wird mittels eine hex-Zahl in der Workbench unter "App Node / Node Specs" vorgenommen. Gehört der
Knoten zu 2 Domänen, muß für jede Domäne ein Schlüssel angegeben werden. Alle Knoten, die die autorisierte
Variable lesen oder schreiben, müssen denselben Schlüssel besitzen. Der Schlüssel ist 48 bit lang. Das Network
Management Tool kann die Schlüssel mittels eines network management commands ändern.
Arbeitsweise bei der Übertragung
- Knoten A schickt ein Update an eine Netzwerkvariablen, die in Knoten B mittels Autorisierung deklariert ist.
Empfängt A nicht die Rückfrage von B, wird die Initialisierung wiederholt.
17
- Knoten B generiert eine 64 bit Zufallszahl und schickt diese zusammen mit anderen Daten in einem
Forderungspaket an Knoten A. Die Zufallszahl wird mit einem Kodierungsalgorithmus erzeugt, in dem der 48
bit lange Zugriffsschlüssel transformiert wird.
- Knoten A nutzt denselben Kodierungsalgorithmus, um eine Zufallszahl seines Schlüssels und den Daten von B
zu erzeugen. Knoten A sendet diese Transformation an B zurück.
- Knoten B vergleicht die Transformation. Wenn eine Gleichheit besteht, ist die Identität des Senders (Knoten A)
geprüft. Knoten B führt nun die geforderte Aktion aus und schickt eine Bestätigung an A. Waren die Schlüssel
nicht gleich, wird die Aktion nicht ausgeführt, es wird ein Fehler in die Fehlertabelle eingetragen.
War bei einer Botschaft das Prüfen der Autorisierung bereits erfolgreich, dann wird bei Verlust der Bestätigung
und dem nachfolgendem Wiederholen der Botschaft die Autorisierung nicht nochmals geprüft, die Aktion wird
sofort gestartet.
Ist ein Ausgang mit mehreren Lese-Knoten verbunden, die eine Autorisierung fordern, dann wird die 64-bitZufallszahl von jedem Lese-Knoten erzeugt. Der Schreib-Knoten muß demzufolge für jeden Leseknoten die
Zufallszahl transformieren und zurückschicken.
=> Bemerkung zur Sicherheit des Schlüsselwortes: Bei der Installation sollte der Knoten direkt mit dem
Network Management Tool verbunden werden und damit der Schlüssel installiert werden. Der Schlüssel wird
nicht über das Netz geschickt und kann demzufolge nicht von Fremdpersonen gelesen werden. Wird der
Schlüssel später über das Netz verändert, so wird nur eine Zahl geschickt, die zum bereits vorhandenen
Schlüssel addiert wird.
18
6. Kommunikation mittels expliziter Botschaften
Zur Übermittlung von Nachrichten zwischen den Knoten kann man nicht nur die Netzwerkvariablen verwenden,
der Datenaustausch über explizite Botschaften ist ebenfalls möglich.
Explizite Botschaften haben folgende Eigenschaften:
- die Größe der Datenfelder ist variabel
- sie erfordern einen Anfrage-Antwort-Mechanismus, der komplexer als beim Polling ist
- sie stellen die Basis für entfernte Prozeduraufrufe dar, d.h. ein Knoten kann eine Aktion auf einem anderen
Knoten aufrufen
- es werden komplexere Methode als bei Netzwerkvariablen benötigt
- sie benötigen weniger EEPROM-Speicher als Netzwerkvariablen, dafür mehr Platz für den Programmcode.
6.1. Schichten der NEURON Chip Software
Sie besteht aus 3 Schichten:
- der Applications-Schicht einschließlich Scheduler
- der Netzwerk-Schicht
- der Media-Access-Control-Schicht (MAC)
Jede Schicht korrespondiert mit ein oder mehreren Schichten über das LONTALK-Protokoll und wird von einem
separatem Prozessor kontrolliert. Nur die Applicationsschicht kann programmiert werden. Es besteht außerdem
Zugang zu einigen Informationen der Netzwerkschicht.
6.2. Allgemeines zu Botschaften
Konstruieren einer Botschaft
Das zu konstruierende Botschaftsobjekt trägt den vordefinierten Namen msg_out. Zu einer bestimmten Zeit sind
nur eine eingehende und eine ausgehende Botschaft verfügbar. Paralleles Versenden von 2 Botschaften ist also
nicht möglich.
Definition des msg_out-Objekts:
typedef enum {FALSE, TRUE} boolean;
typedef enum {ACKD, UNACKD_RPT, UNACKD, REQUEST} service_type;
struct {
boolean priority_on;
msg_tag tag;
int code;
int data[MAXDATA];
boolean authenticated
FALSE
service_type service;
msg_out_addr dest_addr
} msg_out
//
//
//
//
//
TRUE, wenn Priority-Botschaft, Standard FALSE
ID-Nummer, notwendig
Code der Botschaft, notwendig
Daten der Botschaft, Standard none
TRUE, wenn Autorisierung gefordert, Standard
// Service-Typ, Standard ACKD
// optional, siehe msg_addr.h
Zur Erläuterung der Angaben:
19
priority_on: TRUE setzen, wenn die Botschaft als Priority verschickt werden soll.
tag: wird vom Nutzer spezifiziert als Identifikation der Botschaft. Es ist notwendig, bei jedem Versenden einer
Botschaft tag einen Wert zuzuweisen! Ist dieser Wert einmal zugewiesen, kann die Botschaft nur noch
geschickt oder gelöscht werden.Die Variable wird für die Adressierung und für das Verbinden des ErfolgsEreignisse und der Antwort.
code: numerischer Kode der Botschaft. Siehe Punkt Deklaration der Botschafts-Kodes
int data[MAXDATA]: Array, das die zu sendenden Daten aufnimmt
authenticated: TRUE setzen, wenn Autorisierung gefordert wird.
service: ACKD - Bestätigung mit Wiederholungs-Funktion
UNACKD - keine Bestätigung
UNACKD_RPT - keine Bestätigung für Wiederholungen (Botschaft wird mehrmals gesendet)
REQUEST - Anfrage/Antwort - Funktion. Der Empfänger-Knoten schickt eine Antwort zum SendeKnoten. Der Sende-Knoten verarbeitet die Antwort. Siehe spätere Kapitel für eine genauere
Beschreibung.
dest_addr: Dieses Feld ist optional. Wenn die Botschaft mittels explizierter Adressierung verschickt wird, wird
die Botschaft mit diesem Wert verbunden.
Deklaration der Botschafts-Identifikations-Nummern (Message Tags)
In der dieser Deklaration können optional Verbindungsinformationen angegeben werden.
msg_tag [connection-info] tag-identifier [,tag-identifier...];
connection-info wird in der Form bind_info (options) angegeben.
Für options kann stehen:
nonbind: Botschaft, die keine Adreßinformationen trägt und demzufolge nicht in der Adreßtafel steht. Kann für
optimalere Nutzung der Resourcen genutzt werden, wenn die Adressierung nicht erforderlich ist.
rate_est (const_ausdruck):
geschätzte Verzögerungsrate in Zehntelsekunden, die die Botschafts für ihre Versendung erwartet. (Wert
zwischen 0 und 18780).
Max_rate_est(const_Ausdruck):
geschätzte maximale Rate in Zehntelsekunden, die die Botschaft für ihre Versendung erwartet. (0-18780).
tag_identifier: NEURON C Identifikator für message tag.
Botschafts-Kodes
Für die Nutzung durch die Applikation stehen die Werte 0-62 und 64-78 zur Verfügung. Der untere Bereich wird
für applikationsspezifische Botschaften, der obere Bereich für Übergänge zu anderen Netzwerken genutzt.
Nummer 63 wird nur von Antworten genutzt um anzuzeigen, daß der Empfänger im Zustand offline ist und nicht
anworten kann. Für Netzwerk-Diagnose werden die Nummern 80 bis 95, für Netzwerk-Management die
Nummern 96 bis 127, für Netzwerkvariablen 128 bis 255 verwendet.
Beispiel:
msg_tag motor;
#define MOTOR_ON 0
#define ON_FULL 100
msg_out = motor;
msg_out.code = MOTOR_ON;
msg_out.data[0] = ON_FULL;
Block Transfer von Daten
Hier wird die memcpy-Funktion, die Adresse des msg_out-Objekts bzw. resp_out-Objekts (siehe auf folgenden
Seiten) benutzt
Syntax der memcpy-Funktion:
memcpy(msg_out.data &s, sizeof(s));
20
msg_out.data: Zielort der Kopie. Dies kann auch ein spezielles Feld des Botschaftsobjekts sein (z.B.
msg_out.data[3])
&s: Zeiger auf eine Struktur, die die zu kopierenden Daten enthalten. Das Feld kann ein Zeiger, ein Array, ein
Zeiger auf ein Element eines Arrays (&a[5]) sein.
sizeof(s): Größe der Struktur s.
Diese Funktion kann auch für msg_in und resp_in Objekte verwendet werden:
memcpy(&s, msg_in.data, sizeof(s))
Dabei ist &s wieder ein Zeiger auf den Zielpuffer, msg_in.data sind die Quelldaten, sizeof(s) die Größe des
Zielpuffers.
Beispiel:
msg_tag motor;
#define MOTOR_ON 0
typedef enum {
MOTOR_FWD,
MOTOR_REV
} motor_dir;
struct {
long
motor_speed;
motor_dir motor_direction;
int
motor_ramp_up_rate;
} motor_on_message;
msg_out.tag = motor;
msg_out.code = MOTOR_ON
motor_on_message.motor_direction = MOTOR_FWD;
motor_on_message.motor_speed = 500
motor_on_message.motor_ramp_up_rate = 100
memcpy(msg_out.data, &motor_on_message, sizeof(motor_on_message));
Senden der Botschaft
Nachdem alle Daten im msg_out-Objekt eingetragen wurden, wird dieses Objekt mittels
msg_send();
geschickt.
Das Senden des Objekts muß vor dem Ende eines Tasks erfolgen, da am Taskende das msg_out-Objekt
automatisch gelöscht wird. Soll das msg_out-Objekt von Taskende gelöscht werden, kann dies mittels der
Funktion msg_cancel() erfolgen.
msg_tag motor;
#define MOTOR_ON 0
#define ON_FULL 100
21
msg_out = motor;
msg_out.code = MOTOR_ON;
msg_out.data[0] = ON_FULL;
msg_send();
Empfang einer Botschaft
Dazu existieren 2 Möglichkeiten:
(1) Bei jedem Eingehen einer Botschaft gilt: msg_arrives = TRUE. msg_arrives kann durch einen BotschaftsKode qualifiziert werden. Nichtqualifizierte msg_arrives-Ereignisse werden beim Eingang jeder Botschaft wahr.
#pragma scheduler_reset
when (msg_arrives(1))
{
io_out(sprinkler, ON);
}
when (msg_arrives(2))
{
io_out(sprinkler, OFF);
}
when (msg_arrives)
{
}
//Botschaft mit Message-Code1
//Standard-Fall für alle unerwarteten Botschaften.
(2) Die Abfrage erfolgt durch die msg_receive()-Funktion.
boolean msg_receive(void);
Ist keine Nachricht eingegangen, wird diese Zeile übergangen, er wird nicht gewartet. Diese Funktion wird
benötigt, wenn in einem Task mehr als eine Botschaft erwartet wird, z.B im Bypass-Modus.
Format der eingehenden Botschaft
Alle Botschaften stehen nach Eingang in einem msg_in-Objekt zur Verfügung.
typedef enum {FALSE, TRUE} boolean;
typedef enum {ACKD, UNACKD_RPT, UNACKD, REQUEST} service_type;
struct {
int code;
int len;
int data[MAXDATA];
boolean authenticated;
msg_in_addr addr
} msg_in;
//Botschafts-Kode
//Länge der Daten
//Daten
//TRUE wenn autorisiert
//siehe msg_addr.h include-Datei
code: numerischer Kode siehe Erläuterung msg_out.
len: Länge der Daten der Botschaft
data: Daten der Botschaft, ist nur belegt, wenn len>0. MAXDATA = app_buf_in_size - 6 oder MAXDATA =
app_buf_in_size - 17 (für explizite Adressierung).
authenticated: TRUE, wenn autorisiert.
service: ACKD - Bestätigung mit Wiederholungs-Funktion
UNACKD - keine Bestätigung
UNACKD_RPT - keine Bestätigung für Wiederholungen (Botschaft wird mehrmals gesendet)
REQUEST - Anfrage/Antwort - Funktion. Der Empfänger-Knoten schickt eine Antwort zum SendeKnoten. Der Sende-Knoten verarbeitet die Antwort. Siehe spätere Kapitel für eine genauere
Beschreibung.
addr: optional.Wird Bestimmen der Quelle und des Bestimmungsorts der Botschaft verwendet.
22
"Default"-When-Fälle
Jedes Programm, das Nachrichten erhält, muß auch einen when-Zweig enthalten, der unerwünschte Nachrichten
behandelt.
when (msg_arrives) {...};
Ist dieser Zweig nicht vorhanden, würde eine Nachricht, die nicht abgefordert wird, ständig am Anfang der
Warteschlange stehen und die nachfolgenden Botschaften blockieren.
Ist ein das Programm in einem Knoten nicht für den Botschafts-Empfang ausgelegt (es existiert kein
msg_arrives und kein msg_receive()), löscht der Scheduler selbst eingehende Botschaften.
Beispiel:
//Lampen-Beispiel, das explizite Botschaften verwendet
#pragma
#define
#define
#define
#define
scheduler_reset
LAMP_ON 1
LAMP_OFF 2
OFF 0
ON 1
IO_0 output bit io_lamp_control
when (msg_arrives(LAMP_ON))
{ //Einschalten
io_out(io_lamp_control, ON);
}
when (msg_arrives(LAMP_OFF)
{ //Ausschalten
io_out (io_lamp_control, OFF);
}
when (msg_arrives ) { }
Programm für den Kontroll-Knoten:
#define LAMP_ON 1
#define LAMP_OFF 2
IO_4 input bit io_switch_in;
msg_tag TAG_OUT;
when (reset)
{
io_change_init(io_switch_in);
}
when(io_changes(io_swich_in))
{
msg_out.code = (input_value == ON) ? LAMP_ON : LAMP_OFF;
msg_out.tag = TAG_OUT;
msg_send();
}
Verbinden der Botschafts-Identifikations-Kennzeichen (Message-Tags)
Jeder Knoten besitzt ein msg_in-Kennzeichen. Dieses muß beim Senden angegeben werden, damit der
Empfänger ermittelt werden kann. Die Identifikationskennzeichen werden mit msg_in verbunden, beim Senden
wird tag-out mit dem msg_in Kennzeichen verbunden.
23
ACKD-Service
Wird eine Botschaft über den ACKD-Service verschickt, wie oben beschrieben, so muß das Bestätigungen nicht
von der Applikationsschicht übernommen werden. Es braucht daher nicht im Programm beachtet werden.
6.3. Fertig_Ereignisse für Botschaften
msg_completes [(msg_tag_name)]
wird TRUE, wenn Bearbeitung einer ausgehenden Nachricht beendet ist.
msg_succeeds [(msg_tag_name)]
wird TRUE, wenn das Verschicken der Botschaft erfolgreich war.
msg_fails [(msg_tag_name)]
wird TRUE, wenn das Senden einer Botschaft fehlerhaft war.
Regeln für das Bearbeiten von Fertig-Ereignissen für Botschaften
(1) msg_succeeds und msg_fails müssen immer paarweise verwendet werden.
msg_completes kann auch allein stehen.
(2) wenn ein Fertig-Ereignis für eine spezielle Botschaft (Identifikations-Nummer = Tag) verwendet wird, dann
muß jedes Fertig-Ereignis für diese Botschaft bearbeitet werden.
Im Gegensatz dazu ist es nicht notwendig, für alle anderen Botschaften (andere Identifikations-Nummer =
Tag) die Fertig-Ereignisse zu testen.
(3) Wird ein unqualifiziertes Fertig-Ereignis verwendet, so müssen auch alle Botschaften nach dem Senden auf
Fertig-Ereignisse geprüft werden. Dies ist entweder über unqualifizierte Tests der anderen Fertig_Ereignisse
oder über qualifizierte Ereignisse nach jedem Senden einer Botschaft realisierbar.
int failures[2], success;
msg_tag TAG1, TAG2;
when (io_changes(toggle))
{
msg_out.tag = TAG1;
msg_out.code = TOGGLE_STATE;
msg_out.data[0] = input_value;
msg_send();
msg_out.tag = TAG2;
msg_out.code = TOGGLE_STATE;
msg_out.data[0] = input_value;
msg_send();
}
when (msg_fails(TAG1))
{
failures[0]++;
}
when (msg_fails(TAG2))
{
failures[1]++;
}
24
when (msg_succeeds)
{
success++;
}
//für jede Botschaft
6.4. Botschaften und der Preemption Modus
Das System geht in den Preemption Modus, wenn für eine zu verschickende Botschaft kein Puffer frei ist. Das
Programm wartet in diesem Fall und behandelt nur noch Fertig-Ereignisse, ankommende Botschaften und
Antworten. Der Watchtimer wird während dieser Zeit ständig aktualisiert, so daß die zu versendende Botschaft
nicht durch das Ende des Tasks verlorengeht.
Wenn sich das System im Preemption Modus befindet und eine weitere Botschaft verschickt werden soll (z.B. in
einem when ( msg_completes )-Zweig), wird der entsprechende Knoten zurückgesetzt.
when (TOGGLE_ON)
{
//erstellen der Botschaft und verschicken
}
when (msg_completes)
{
msg_out.tag = t;
msg_out.code = 1;
}
Richtig: msg_send() wird nicht in einem when ( msg_completes )-Zweig verwendet.
6.5. Asynchrone und direkte Ereignisverarbeitung
Der Test von Ereignissen in einem when-Task in asynchron. Direkte Ereignisverarbeitung bedeutet: Der Test
findet innerhalb eines Tasks mittels if oder while statt.
Man soll kein msg_completes-Ereignisse in einem Task verwenden, der mit eine msg_completes-Ereigniszweig
verbunden ist:
when (msg_completes)
{
post_events();
if msg_completes) x = 4;
}
//nicht empfehlenswert
Asynchrone und direkte Ereignisverarbeitung in einem Programm ist möglich. Asynchrone Ereignisverarbeitung
ist typischer, da die Programmgröße in diesem Fall kleiner ist.
Vor dem Wechsel von asynchroner zu direkter Verarbeitung ist es notwendig, die Funktion flush_wait()
aufzurufen. Dieser Aufruf sichert, daß noch alle ausstehenden Fertig-Ereignisse und Antworten vor dem
Umschalten in die direkte Verarbeitung bearbeitet werden.
msg_tag motor;
#define MOTOR_ON 0
when (x==3)
{
flush.wait();
msg_out.tag = motor;
msg_out.code = MOTOR_ON;
msg_send();
while (!msg_succeeds(motor))
{
post_events();
if (msg_fails(motor)) node_reset();
}
25
}
6.7. Anfrage - Antwort - Mechanismus (Request - Response)
Eine Applikation auf einem Knoten fordert Daten von einer Applikation auf einem anderen Knoten an. Diese
Funktion wird automatisch benutzt, wenn mit polled input Variablen gearbeitet wird. Man kann diesen
Mechanismus auch für explizite Botschaften benutzten.
Es gibt einen wichtigen Unterschied zu polled deklarierten Netzwerkvariablen: Bei Netzwerkvariablen kann
man nicht explizit im Programm auf eine Anfrage vom Leseknoten reagieren. Bei Botschaften vom RequestResponse-Typ muß man auf eine Anfrage direkt reagieren, d.h. den Programmcode für eine Anfrage selbst
schreiben. Wo liegen die Vorteile? Ein Beispiel: Ein Lüftermotor soll angeschaltet werden. Hier muß man
sichergehen, daß der Motor auch läuft. Man schickt also an diesen Knoten eine Botschaft mit dem RequestService. Der Knoten muß nun auf diese Botschaft antworten. Er kann also ein OK oder eine Fehlermeldung
zurücksenden. Der Sender erhält also immer eine Antwort vom Empfänger-Knoten, ob der Lüftermotor
funktioniert oder nicht.
Eine Anfrage kann dabei den vorbereiteten Request-Service des Neuron Chip benutzten. Die Anfrage fordert
vom Antwortknoten, die Variable zur Zeit der Anfrage zu liefern. Es werden Funktionen analog zu den oben
beschriebenen Botschaftsfunktionen genutzt.
msg_tag motor;
#define MOTOR_STATE 1
when (io_changes(switch1) to 2)
{
msg_out.tag = motor;
msg_out.service = REQUEST;
msg_out.code = MOTOR_STATE;
msg_send();
}
Das Programm auf dem Empfängerknoten empfängt die Anfrage in einem when-Zweig oder über eine
msg_receive() Funktion, der Knoten muß darauf antworten.
Erstellen einer Antwort
Eine Antwort ist nicht gleichzusetzen mit einer Bestätigung! Das ausgehende Objekt heißt resp_out. Die
Antwort übernimmt dabei die Priorität und die Autorisierungskennzeichen der Anfrage. Die BotschaftsIdentifikation (message tag) ist nicht notwendig, da die Antwort automatisch zum anfragenden Knoten
zurückgeschickt wird.
Definition resp_out:
struct {
int code;
int data[MAXDATA];
} resp_out;
code: numerischer Kode. siehe Erläuterungen vorhergehende Seiten.
data: Daten der Botschaft. MAXDATA ist Funktion von app_buf_in_size. Siehe vorhergehende Erläuterungen
msg_in.
Senden einer Antwort
Das erstellte resp_out-Objekt wird mit der Funktion resp_send() verschickt. Es ist zu beachten, daß die Antwort
in derselben critical section geschickt wird, in der die Anfrage eintraf. Während dieser Zeit können keine
weiteren Anfragen oder Antworten bearbeitet werden.
Empfangen einer Antwort
(1) Mit Hilfe des resp_arrives - Ereignisses:
26
when ( resp_arrives [(msg-tag-name)] )
(2) Mittels der Funktion resp_receives():
boolean resp_receives(void);
BEACHTE: Die Antwort wird am Ende des Tasks, der die Antwort erhielt, verworfen.
Format der Antwort
Nach Eingang der Antwort steht ein resp_in-Objekt zur Verfügung, das die Daten enthält.
struct {
int code;
int len;
int data[MAXDATA];
resp_in_addr addr;
} resp_in;
code: Numerischer Kode der Botschaft
len: Länge der Daten.
data: Daten der Botschaft. siehe MAXDATA Erläuterungen in msg_in.
addr: optional
Beispiel für Anfrage-Knoten: Erstellen und Senden der Anfrage - asynchroner Empfang der Antwort
when (io_changes(toggle))
{
msg_out.tag = TAG1
msg_out.code = 1;
msg_send();
}
//code für Antwortknoten wichtig
when (msg_arrives(TAG1))
{
if (resp_in.code == OK)
process_response (resp_in.data[0]);
}
Beispiel für den Antwortknoten: Erstellen einer Antwort auf eine Anfrage
when (msg_arrives (1))
{
int x, y;
x = msg_in.data[0];
y = get_response(x);
resp_out.code = OK;
resp_out.data[0] = y;
resp_send();
}
//Antwort auf Botschaft mit Code 1
//Botschaft existiert ab hier nicht mehr
Beispiel für Anfrage-Knoten: Erstellen und Senden der Anfrage und direkter Empfang
int x;
msg_tag motor;
#define MOTOR_ON 0
#define DO_MOTOR_ON 3
when (command == DO_MOTOR_ON)
{
msg_out.tag = motor;
msg_out.code = MOTOR_ON;
msg_out.service = REQUEST;
27
msg_send();
//Warten auf Antwort
while (!msg_succeeds(motor))
{
post_events();
if (msg_fails(motor)) node_reset();
else if (resp_arrives(motor))
{
x = x + resp_in.data[0];
resp_free();
}
}
}
Vergleich der resp_arrives / msg_succeeds-Ereignisse
Es können sowohl resp_arrives als auch die Fertig-Ereignisse (msg_succeeds, msg_completes, msg_fails)
verwendet werden, da sie unterschiedliche Informationen liefern.
Als Beispiel dient eine Anfrage, die an 6 Knoten gestellt wird. Drei Knoten empfangen die Anfrage, die anderen
drei nicht. Daraus ergibt sich folgendes Verhalten:
- resp_arrives wird 3 Mal TRUE
- msg_arrives wird nie TRUE, da nicht alle Anfrage ankamen
- msg_fails wird TRUE, wenn die Antworten nicht in der vorgesehenen Zeit eintreffen.
Die Antworten treffen dabei immer vor den Fertig-Ereignissen (msg_succeeds, msg_completes, msg_fails) für
die Botschaften ein.
6.8. Puffer
Die Anzahl der E/A-Puffer wird während der Übersetzungszeit festgesetzt. Folgende Belegung ist als Standard
vorgesehen:
- 2 priority output
- 2 nonpriority output
- 2 input
Für eine effiziente Verarbeitung von Antworten sollte die Anzahl der Puffer gleich der Anzahl der eingehenden
Antworten sein. Ist dies nicht der Fall, kann es passieren, daß einige Antworten nie ankommen, da die
Antworten in der gleichen critical section ankommen müssen, in der die Anfrage geschickt wurde (s.o.). Stehen
nicht ausreichend Puffer zur Verfügung, kommt es zu einer Zeitüberschreitung, die das Ende der critical section
bedeutet.
Es ist zu beachten, daß Puffer nicht nur für eingehende Antworten, sondern auch für eingehende Botschaften
genutzt werden. Bei der direkten Ereignisverarbeitung ist dafür zu sorgen, daß die Puffer wieder frei werden,
indem alle Botschaften und Antworten abgefragt werden.
Allokieren von Puffern
Puffer werden normalerweise automatisch vom Scheduler angefordert und nach Beenden einer critical section
wieder freigegeben. Puffer können aber auch explizit allokiert werden:
boolean msg_alloc(void)
boolean msg_alloc_priority(void)
void msg_free(void)
Priority-Puffer haben bessere Aussichten, im Netz schnell verschickt zu werden als nonpriority-Puffer. Die
beiden Funktionen msg_alloc und msg_alloc_priority liefern TRUE zurück, wenn ein msg_out-Objekt zur
Verfügung steht. Über die Compiler-Optionen #pragma... kann die maximale Anzahl von Puffern festgelegt
werden (siehe dort).
Die Funktion msg_free löscht ein msg_in-Objekt.
28
Wenn in ein msg_out-Objekt ein Wert eingetragen wird, aber kein msg_out-Objekt zur Verfügung steht (d.h. es
ist kein Puffer frei), geht das System in den Preemption Modus. Um dies zu vermeiden, ist es nützlich, die oben
genannten Funktionen zu benutzen, da sie beim Fehlen von freien Puffern nur FALSE zurückliefern, aber nicht
in den Preemption Modus übergehen.
BEACHTE: Ein Puffer, der über msg_alloc allokiert wurde, kann nicht mittels msg_free wieder freigegeben
werden. Nur über die Funktion msg_send kann der Puffer wieder freigegeben werden. msg_free kann nur Puffer
freigeben, die durch ein msg_in-Objekt als Folge von msg_receive belegt wurden.
Analog dazu funktionieren auch die beiden Funktionen
boolean resp_alloc(void)
void resp-free(void)
die für den Anfrage-Antwort-Mechanismus verwendet werden können.
msg_tag motor1; msg_tag motor2;
#define MOTOR_ON 0
when (x == 2)
{
if (msg_alloc() == FALSE) return;
msg_out.tag = motor1;
msg_out.code = MOTOR_ON;
msg_send();
if (msg_alloc() == FALSE) return;
msg_out.tag = motor2;
msg_out.code = MOTOR_ON;
msg_send();
}
29
7. Bemerkungen zum Speicher-Management
7.1. Adreß-Tabellen
#pragma num_addr_table_entries n
Die Adreßtabelle enthält eine Liste von Netzwerkadressen. Jeder Eintrag belegt 5 byte im EEPROM. Die
maximale Anzahl für einen Knoten ist gleich der Anzahl der Verbindungen, die dieser Knoten zu anderen
Knoten besitzt.
7.2. Allokieren von Puffern
Die Puffergröße für explizite Botschaften muß für die längste eingehende Botschaft ausreichend sein.
- Applikations-Puffer-Größe:
Botschaftsgröße + 5 byte system overhead
- Netzwerk-Puffer-Größe:
Botschaftsgröße + 6 byte system overhead + 20 byte protocol overhead
Ist die Größe der Puffer nicht ausreichend bemessen, entstehen während der Laufzeit zu den Fehlermeldungen
APP_BUF_TOO_SMALL und NET_BUF_TOO_SMALL.
Ausgangs-Applikations-Puffer
#pragma app_buf_out_size n
Für ausgehende priority und nonpriority Botschaften verwenden.
#pragma app_buf_out_count n
Anzahl der Applikations-Puffer für ausgehende explizite nonpriority Botschaften und nonpriority
Netzwerkvariablen.
#pragma app_buf_out_priority_count n
Anzahl der Applikations-Puffer für ausgehende explizite priority Botschaften und nonpriority
Netzwerkvariablen.
Ausgangs-Netzwerk-Puffer
#pragma net_buf_out_size n
Netzwerkpuffergröße für ausgehende nonpriority und priority explizite Botschaften und Netzwerkvariabeln. Die
Größe n sollte größer als 34 sein.
#pragma net_buf_out_count n
Anzahl der Netzwerk-Puffer für ausgehende explizite nonpriority Botschaften und nonpriority
Netzwerkvariablen.
#pragma net_buf_out_priority_count n
Anzahl der Netzwerk-Puffer für ausgehende explizite priority Botschaften und nonpriority Netzwerkvariablen.
Eingehende-Netzwerk-Puffer
#pragma net_buf_in_size n
Größe der Netzwerkpuffer für eingehende explizite Botschaften und Netzwerkvariablen. Die Größe n sollte
größer als 50 sein.
30
#pragma net_buf_in_count n
Anzahl der Netzwerkpuffer für eingehende explizite Botschaften und Netzwerkvariablen.
Eingangs-Applikations-Puffer
#pragma app_buf_in_size n
Größe der Applikationspuffer für eingehende explizite Botschaften und Netzwerkvariablen. Die Größe n sollte
größer als 22 sein.
#pragma app_buf_in_size n
Anzahl der Applikationspuffer für eingehende explizite Botschaften und Netzwerkvariablen.
Beachte dazu Tabellen 6-1 und 6-2
7.3. Anzahl der Empfangs-Transaktionen
Damit kann die Anzahl der Einträge eines Arrays festgesetzt werden, die gleichzeitig bedient werden können.
#pragma receive_trans_count n
n ist die Anzahl der Einträge im Receive Transaction Array.
31
8. Verschiedenes
8.1. Scheduler-Reset-Mechanismus
#pragma scheduler_reset
Das Verwenden dieser Zeile im Programm schaltet den Mechnismus ein. Der round-robin-Teil des Schedulers
wird auf eine reguläre when-Abfrage gesetzt, wenn folgendes passiert:
- eine aktualisierte Netzwerkvariable erscheint am Kopf der Warteschlange
- ein Zeitgeber ist abgelaufen
- eine neue Botschaft steht am Kopf der Warteschlange
8.2. Der Watchdog-Timer
Der Watchdog-Timer verhindert, daß ein Task unendlich lange läuft und demzufolge die Verarbeitung anderer
Botschaften und Ereignisse verhindert. Ist es notwendig, einen sehr langen Task zu verwenden, der die
vorgesehene Zeit überschreitet (0,84 bis 1,68 Sekunden), so wird dieser Task auch abgebrochen, obwohl er
einwandfrei funktioniert. Um dies zu verhindern, muß der Watchdog-Timer rechtzeitig zurückgesetzt werden:
watchdog_update()
8.3. Zusätzliche vordefinierte Ereignisse vom Netzwerk Management Tool
offline
Ein offline management command ereignet sich, wenn es vom Netzwerk Management Tool verschickt wurde. Ist
der Knoten danach offline geschaltet, empfängt er nur noch Management commands.
online
Ein online management command ereignet sich, wenn es vom Netzwerk Management Tool verschickt wurde.
wink
Das wink-Ereignis ist ein spezielles Kommando, das vom Netzwerk Management Tool verschickt werden kann.
Im Programm ist mit diesen Ereignissen folgendermaßen umzugehen:
when ( offline ) {...}
when ( online ) {...}
8.4. Fehler-Behandlung
Fehler können folgendermaßen behandelt werden:
- Zurücksetzen des Knotens und Neustart der Applikation
- Eintrag des Fehlers in die Fehlertabelle und Fortsetzung des Programms
(1) Zurücksetzen des Knotens
Die Funktion node_reset() wird aufgerufen. Dies bewirkt, daß alle 3 Prozesse zurückgesetzt werden (siehe
Schichtmodell des NEURON Chips).
Nach dem Zurücksetzen wird die Initialisierungs-Sequenz ausgeführt. Alle Zustandsinformationen, die nicht im
EEPROM gespeichert sind, gehen verloren. Dabei kann es zu Problemen mit dem Netzwerk-Prozessor kommen.
Nachrichten können doppelt entgegengenommen werden oder verlorengehen.
32
(2) Neutstart der Applikation
Aufruf über die Funktion application_restart(). In diesem Fall wird nur der Applikations-Prozessor neu
gestartet. Nach dem Neustart werden alle Zeitgeber zurückgesetzt, E/A-Objekte, Netzwerkvariablen und
statische Variablen werden initialisiert, danach wird der when(reset)-Zweig aufgerufen.
Statusinformationen des Netzwerkes gehen nicht verloren, da die beiden anderen Prozessoren weiterlaufen.
(3) Eintragen von Programmfehlern
In diesem Fall muß die Funktion error_log() aufgerufen werden. Diese Funktion trägt eine Fehlernummer
zwischen 1 und 127 in die Fehlertabelle ein. Die Fehlernummer befindet sich also in einem speziell dafür
vorgesehenen EEPROM-Bereich. Die Fehlertabelle kann durch das Management Tool gelesen werden.
(4) System-Fehler
Programmierfehler, Netzwerkfehler, Widersprüche werden genauso wie Programmfehler in die Fehlertabelle
eingetragen.
Zugang zum Fehlerstatus eines Knotens
Das Programm hat einen lokalen Zugang zum Fehlerstatus, das Management Tool hat diesen Zugang ebenfalls.
Die Statusinformationen sind in einer Statusstruktur gespeichert. Mit folgender Funktion kann sie gelesen
werden:
void retrieve_status(statusstruct * status_p)
Die Funktion clear_status() löscht die Statusinformationen
33
9. Komplexes Beispiel
Hier nun ein Beispielprogramm, in dem viele der vorher beschriebenen Netzwerkfunktionen angewandt wurden.
Das Programm stellt einen Flugrechner für Segelflugzeuge dar, dessen einzelne Komponenten Messung,
Steuerung, Rechnung und Ausgabe auf verschiedenen Knoten laufen. Die Knoten tauschen untereinander Daten
aus.
Doch zuerst das Konzept:
Der Sensorknoten erfaßt die Meßwerte und bildet stets über die letzten 8 Meßwerte einen Mittelwert. Mit
diesem Mittelwert ist die Variable Messwert ständig belegt. Da diese Variable als polled deklariert ist, wird
einen Änderung dieser nicht über das Netz geschickt. Hierbei ist zu bedenken, daß im Programm dieses Knotens
nicht auf eine Abfrage des Messwertes reagiert werden kann, so daß der Mittelwert ständig gebildet werden
muß. Man kann nicht programmieren, was passieren soll, wenn der Steuerknoten den Wert mittels poll abfragt.
Der Quelltext enthält Befehle, die für die Erfassung des Meßwertes bestimmt sind. Diese werden hier nicht
erklärt.
1)
2)
/* Quelltext des Meßknotens */
#include <SNVT_lev.h>
3)
4)
5)
IO_0 output bit pin0;
IO_1 output bit pin1;
IO_4 input period mux clock(7) io_switch_4;
6)
mtimer repeating MessTimer;
/*Millisekunden-Timer*/
34
7)
network output polled int Messwert;
/*OutputVariable Polled*/
8)
9)
10)
11)
int MessFeld[8];
int MessZaehler;
long int Summe;
int nv_value1;
12)
13)
14)
15)
16)
17)
18)
19)
when ( reset )
{
int i;
io_out (pin0,1); io_out (pin1,0);
MessTimer = 250;
/*aller 250ms auslösen*/
MessZaehler = 0;
for (i = 0; i < 8; i++) MessFeld[i] = 0;
}
20)
21)
22)
23)
24)
25)
26)
27)
28)
29)
when ( timer_expires(MessTimer) )
{
int i;
MessFeld[MessZaehler] = nv_value1;
MessZaehler = MessZaehler + 1;
if (MessZaehler == 8) MessZaehler = 0;
Summe = 0;
for (i = 0; i<8; i++) Summe = Summe+MessFeld[i];
Messwert = Summe >> 3;
/*Wertzuweisung an OutputVariable*/
}
30)
31)
32)
33)
when (io_update_occurs(io_switch_4)) /*neuer Meßwert liegt an*/
{
nv_value1 = -((input_value/3*2)-20);
}
In Zeile 7) wird die Output-Variable Messwert als polled deklariert, in Zeile 27) wird der Wert dieser Variablen
immer wieder neu belegt. Der in Zeile 6) deklariert Messtimer wird in Zeile 16) auf 250 Millisekunden gestellt.
Immer wenn diese Variable nach Ablauf der Zeit den Wert TRUE erhält, wird auch das Ereignis timer_expires
in Zeile 20) wahr und der neue Wert für Messwert wird berechnet.
Der Steuerknoten regelt den Datenfluß zwischen den einzelnen Knoten. Er fragt den Meßwert vom
Sensorknoten ab, schickt den Meßwert zum Rechenknoten, empfängt die berechneten Werte und schickt diese,
wenn gefordert, an den Ausgabeknoten. Der Steuerknoten fragt den Meßwert vom Sensorknoten mittels pollFunktion ab. Danach erstellt er eine explizite Botschaft, die er an den Rechenknoten schickt. Der Steuerknoten
empfängt eine Botschaft vom Rechenknoten, in der die berechneten Werte als Daten enthalten sind. Diese Daten
speichert der Steuerknoten und erstellt bei Anfrage vom Ausgabeknoten eine entsprechene Antwort, in der das
gewünschte Datum enthalten ist.
34)
35)
36)
/*Quelltext des Steuerknotens*/
#include <SNVT_lev.h>
#pragma scheduler_reset
37)
38)
39)
40)
41)
network input int Messwert;
IO_0 output oneshot Led;
stimer repeating Timer1;
msg_tag Rechnung;
long int Ausgabefeld[4];
42)
43)
44)
45)
when (reset)
{
Timer1 = 2;
}
/*Eingangsvar für Meßwert*/
/*Sekundentimer*/
/*MsgTag für Botschaft*/
/*Speicher für berechnete Werte*/
/*alle 2 Sekunden auslösen*/
35
46)
47)
48)
49)
when ( timer_expires(Timer1))
{
poll(Messwert);
}
50)
51)
52)
53)
54)
55)
56)
57)
/* Update der Variable Messwert ist erfolgt*/
when (nv_update_occurs(Messwert))
{
msg_out.tag = Rechnung;
/*Vorbereiten einer exp.
Botschaft*/
msg_out.code = 1;
msg_out.data[0] = Messwert+25;
msg_send();
}
58)
59)
60)
61)
62)
/*Fehler bei Schicken der Botschaft mit msg_out.tag = Rechnung*/
when (msg_fails(Rechnung))
{
io_out(Led, 10000);
}
63)
64)
65)
66)
67)
/*Erfolg bei Schicken der Botschaft mit msg_out.tag = Rechnung*/
when (msg_succeeds(Rechnung))
{
io_out(Led, 250);
}
68)
/*eine Botschaft mit msg_in.code = 2 kommt
an*/
when (msg_arrives(2))
{
int i;
long int ZW;
/*Eingehende Werte aus msg_in.data in Array Ausgabefeld*/
memcpy(&Ausgabefeld, msg_in.data, sizeof(Ausgabefeld));
io_out(Led, 250);
}
69)
70)
71)
72)
73)
74)
75)
76)
77)
78)
79)
80)
81)
82)
83)
84)
85)
/*Update der Variable Messwert fordern*/
/*eine Botschaft mit msg_in.code = 10 kommt
an*/
when (msg_arrives(10))
{
int Status;
/*da Request vom Ausgabeknoten vorliegt*/
Status = msg_in.data[0];
/*Erstellen der Antwort*/
resp_out.code = 1;
/*Kopieren der Daten des Array Ausgabefeld in resp_out.data*/
memcpy(resp_out.data,&Ausgabefeld[Status],sizeof(Ausgabefeld[Status])
);
86)
87)
88)
89)
resp_send();
}
/*when-Zweig, der alle nicht beantworteten Botschaften
verarbeitet*/
when (msg_arrives) {};
In Zeile 4) wird die Variable Messwert deklariert, die mit der Variable Messwert des Sensorknotens verbunden
ist. Die gleichen Namen haben hier keine Bedeutung. Immer wenn Timer1 abgelaufen ist, also den Wert TRUE
enthält, wird Zeile 15) ausgeführt. Hier wird der aktuelle Wert von Messwert vom Sensorknoten angefordert.
36
Der Eingang des neuen Wertes wird dann in Zeile 18) festgestellt. Hier wird danach eine Botschaft erstellt,
indem die in Zeile 7) deklarierte Variable Rechnung als Message-Tag verwendet wird. Der Botschafts-Code 1 ist
wichtig, da im Knoten, an den die Botschaft geschickt wird, nur ein msg_in-Objekt zur Verfügung steht. Die
Unterscheidung zwischen den einzelnen Botschaften erfolgt über msg_in.code (vgl. Zeile 115).
In Zeile 69) wird auf das Ereignis Botschaft mit Code = 2 reagiert. Da auch hier nur ein msg_in-Objekt zur
Verfügung steht, ist die Unterscheidung nur mittels msg_in.code möglich. Die Botschaft wurde im
Rechenknoten Zeile 134)ff erstellt. In Zeile 74) werden die Daten aus dem msg_in-Objekt in das Array
Ausgabefeld kopiert. Dabei ist zu beachten, daß beim Erstellen der Botschaft das Array Ergebnisse im
Rechenknoten 104) die gleiche Struktur hat wie das Array Ausgabefeld im Steuerknoten, da die memcpyFunktion eine Binärumwandlung vornimmt und die Struktur der Daten nicht mit übertragen wird.
In Zeile 78) wird auf eine eingehende Botschaft mit dem Code 10 reagiert. Da diese Botschaft vom
Ausgabeknoten mittels msg_out.service = REQUEST (Zeile 163) erstellt wurde, muß für die Beantwortung ein
resp_out-Objekt verwendet werden.
Der Rechenknoten nimmt die notwendigen Berechnungen vor. Er speichert dabei alle wichtigen Werte. Er
empfängt eine Botschaft vom Steuerknoten und schickt eine Botschaft mit den berechneten Werten an den
Steuerknoten zurück.
90)
91)
92)
93)
/*Quellcode des Rechenknotens*/
#pragma scheduler_reset
#include "polare.h"
#define AnzahlSteigWerte 150
94)
95)
96)
97)
98)
99)
100)
101)
102)
103)
104)
IO_0 output oneshot Led;
msg_tag Zurueck;
/*MsgTag für Botschaft*/
long int Hoehe;
long int DirektesSteigen;
long int far DirektSteigFeld[AnzahlSteigWerte];
long int far MittelSteigFeld[30];
long unsigned SteigZaehler;
int MittelZaehler;
long int MittelSteigen;
long int Sollfahrt;
long int Ergebnisse[4];
105) when ( reset )
106)
{
107)
long int i;
108)
SteigZaehler = 0;
109)
MittelZaehler = 0;
110)
Hoehe = 0;
111)
for (i = 0; i < AnzahlSteigWerte; i++) DirektSteigFeld[i] = -2;
112)
for (i = 0; i < 30; i++) MittelSteigFeld[i] = -2;
113)
MittelSteigen = -2;
114)
}
115) when (msg_arrives(1))
/*Msg mit Msg.Code = 1 trifft
ein*/
116)
{
117)
long int Messwert, i, ZwischenWert;
118)
Messwert = msg_in.data[0];
119)
DirektesSteigen = Messwert-25;
120)
Hoehe += (200*DirektesSteigen)/36;
121)
if (Hoehe < 0) Hoehe = 0;
122)
DirektSteigFeld[SteigZaehler] = DirektesSteigen;
123)
SteigZaehler = (SteigZaehler+1)%AnzahlSteigWerte;
124)
125)
126)
127)
MittelSteigen = 0;
for (i=0; i<AnzahlSteigWerte; i++)
MittelSteigen += DirektSteigFeld[i];
MittelSteigen = MittelSteigen/AnzahlSteigWerte;
37
128)
129)
MittelSteigFeld[MittelZaehler] = MittelSteigen;
MittelZaehler = (MittelZaehler+1)%30;
130)
131)
132)
133)
ZwischenWert = DirektesSteigen-MittelSteigen+20;
if (ZwischenWert<0) ZwischenWert = 0;
if (ZwischenWert>39) ZwischenWert = 39;
Sollfahrt = PolareFeld[ZwischenWert]/10;
134)
135)
136)
137)
138)
139)
140)
141)
142)
143)
144)
mg_out.tag = Zurueck;
/*Erstellen einer
Botschaft*/
mg_out.code = 2;
Ergebnisse[0] = Hoehe/10;
/*Ausfüllen des Array*/
Ergebnisse[1] = 100*DirektesSteigen/36;
Ergebnisse[2] = 100*MittelSteigen/36;
Ergebnisse[3] = Sollfahrt;
/*Kopieren in msg_out.data*/
memcpy(msg_out.data, &Ergebnisse, sizeof(Ergebnisse));
io_out(Led, 250);
msg_send();
}
145) when (msg_arrives) {}
Der Rechenknoten berechnet die Werte, die für die Anzeige benötigt werden. Das geschieht in den Zeilen 118)
bis 133). Hier ist nur Zeile 118) interessant, da hier der eingehende Meßwert in eine Variable des Rechenknotens
übertragen wird. Ab Zeile 135) wird eine Botschaft mit dem Code 2 erstellt, auf die der Steuerknoten reagiert.
Erläuterungen siehe oben beim Steuerknoten.
Der Ausgabeknoten besitzt einen Schalter, mit dem der Nutzer zwischen den 4 Anzeigewerten umschalten
kann. Das LCD kann immer nur einen Wert anzeigen. Der Knoten fordert vom Steuerknoten die aktuellen Daten,
wenn eine bestimmte Zeitspanne abgelaufen ist, oder der Nutzer andere Informationen auf dem LCD wünscht.
Hierbei wird der Request - Response - Mechanismus verwendet.
146)
/*Quelltext für den Ausgabeknoten*/
147) #pragma scheduler_reset
148)
149)
150)
151)
152)
msg_tag AusAnforderung;
int AusgabeStatus;
stimer repeating AusgabeTimer;
long int Ausgabe;
IO_4 input bit Schalter;
153) when(reset)
154)
{
155)
AusgabeStatus = 0;
156)
AusgabeTimer = 3;
157)
}
158) void neueAusgabe()
159)
{
160)
msg_out.tag = AusAnforderung;
161)
msg_out.code = 10;
162)
msg_out.data[0] = AusgabeStatus;
163)
msg_out.service = REQUEST;
164)
msg_send();
165)
}
/*MsgTag für Request Botschaft*/
/*Sekundentimer*/
/*Taster für Umschalten*/
/*neue Anzeige alle 3 Sek*/
/*allgemeine C-Funktion*/
/*Erstellen einer Anforderung*/
166) when (timer_expires(AusgabeTimer))
167)
{
168)
neueAusgabe();
169)
}
38
170) when (io_changes(Schalter) to 1)
171)
{
172)
AusgabeStatus = (AusgabeStatus+1)%3;
173)
neueAusgabe();
174)
}
175)
/*der Rückgabewert der Anforderung kommt an*/
176) when(resp_arrives(AusAnforderung))
177)
{
178)
/*Kopieren der Daten in Array Ausgabe*/
179)
memcpy(&Ausgabe, msg_in.data, sizeof(Ausgabe));
180)
io_out(Led, 1000);
181)
}
Der Knoten fordert neue Werte an, wenn 3 Sekunden (AusgabeTimer) abgelaufen sind oder wenn der Nutzer den
Knopf betätigt und so die Anzeige umschaltet. Dafür wird eine Botschaft AusAnforderung erstellt, bei der der
REQUEST-Mechanismus verwendet wird. D.h., der Steuerknoten muß auf diese Botschaft reagieren (78-87). Im
Feld msg_out.data wird dabei der Array-Index übergeben, in dem der gewünschte Wert im Array Ausgabefeld
im Steuerknoten steht. Es ist wieder zu beachten, daß der Botschafts-Code 10 im Ausgabeknoten und im
Steuerknoten übereinstimmen müssen. Die ankommende Antwort auf die Anfrage wird ab Zeile 176)
verarbeitet. Die Zuständigkeit wird über den Message-Tag AusAnforderung deklariert. Hier wird nicht der
Botschafts-Code benötigt wie bei msg_arrive, sondern die Funktion resp_arrives, da es zu jeder Anfrage
(verbunden mit einem Message-Tag) nur eine Antwort geben kann (verbunden mit demselben Message-Tag).
Die Variable Schalter wird verwendet, um auf ein Drücken des Schaltknopfes reagieren zu können.
Bemerkung: Die Led-Anweisungen wurden nur eingesetzt, um die Transaktionen im Netz auch optisch sichtbar
zu machen.
Diese Arbeit wurde im Rahmen des Proseminars "Technische Informationssystem" an der TU Dresden / Fakultät
Informatik von Alexander Görnitz und Thomas Florstedt erstellt.
Aktualisiert am 17.01.2006 an der TU Dresden / Fakultät Informatik
39