Unterrichts- und Lernmaterial für Mikrocontroller
Unterrichts- und Lernmaterial fürMikrocontroller

1 - Mehrkernprogrammierung

Ein Propeller Controller verfügt über acht Prozessoren, die man auch als Kerne oder Cogs bezeichnet und von 0 bis 7 durchnummeriert. In allen Programmen startet das Hauptprogramm automatisch immer Cog 0. Werden Bibliotheksdateien und daraus spezielle Funktionen aufgerufen, dann können diese Programme auch weitere Cogs starten und Aufgaben im Hintergrund erledigen. Man kann auch eigene Funktionen schreiben und sie von  einem neuen Cog ausführen lassen; dies geschieht über den Befehl cog_run.

Im folgenden Beispiel wird gezeigt, wie man eine cog_run Funktion aufruft. Das Hauptprogramm läuft im Cog 0, der den Funktionsaufruf cog_run ausführt. Der neue Cog 1 lässt dann die LED blinken.

Abbildung 1 - Programm multicore_1.c (Courtesy of Parallax Inc.)

2 - Die Funktion cog_run

Die Funktion enthält zwei Parameter:

  • function  gibt den Namen der Funktion an, die beim Start von cog_run gestartet wird.
  • stackSize ein Wert, der zusätzlichen Speicherplatz (stack space) für den Cog bereitstellt

 

Man sollte immer ausreichend Speicherplatz für den Cog bereitstellen. In diesem Beispiel ist ein Wert von 128 eingetragen, der bei dem kleinen Programm gar nicht benötigt wird.

Bei einer Funktion, die über cog_run gestartet wird, gibt es ein paar Regeln zu beachten. Sie

  • kann keine Parameterwerte übernehmen.
  • gibt keinen Funktionswert zurück
  • sollte alle Anweisungen in einer Endlosschleife ausführen, außer wenn das Programm darüber hinaus bewusst einen Cog anhält.
  • sollte keine print, scan oder andere Funktion, die das SimpleIDE Terminal benutzt, enthalten, außer wenn das Programm dies über zusätzliche Funktionen steuert.

Die Schaltung

In dieser ersten einfachen Übung werden die eingebauten LEDs an den fest verdrahteten I/O Ports 26 und 27 des Propeller Board of Education bzw. des Propeller Activity Boards genutzt.

Die Funktion cog_run
Material
  • 1x  Prop-BoE oder Prop-AB
  • 1x  USB Verbindungskabel
Aufgaben
  • Übertrage das Programm multicore_1.c (Abb. 1) in den Editor und speichere es ab.
  • Starte das Programm aus dem Hauptmenü über Load RAM & Run.
  • Überzeuge dich, dass die LED an P26 anfängt zu blinken.

Wie arbeitet das Programm multicore_1.c?

Zeile 8 enthält eine forward-Deklaration für die Funktion blink. Die Funktion blink wird erst nach der Funktion main (ab Zeile 15) spezifiziert; dem Compiler wird auf diese Weise mitgeteilt, dass eine  Funktion mit diesem Namen zu erwarten ist.

Die Hauptroutine enthält nur eine Befehlszeile

int main()

{

  cog_run(blink, 128);

}

 

Damit wird die Funktion blink gestartet und ein Stack von 128 32-Bit Wörtern bereit gestellt, um Berechnungen während der Ausführung des Programms blink erledigen zu können, auch wenn, wie in diesem Fall, der Stackbereich nicht benötigt oder nicht in dem Umfang benötigt wird.

Die aufgerufene Funktion blink (Programmzeile 15 - 24) folgt im Programm nach dem Hauptprogramm int main() und sie wird von einem anderen Cog ausgeführt. Sie enthält keinen Rückgabewert und keine Parameterliste und der auszuführende Befehlsblock befindet sich in einer Endlosschleife. Die LED an P26 wird also solange blinken, bis die Energiequelle ausgeschaltet wird. Der ausführende Cog und sein Stackbereich sind deshalb für eine andere Nutzung nicht verfügbar.

void blink()               
{
  while(1)               
  {
    high(26);              
    pause(100);       
    low(26);             
    pause(100);        
  }
}

Was du wissen solltest!

Wie viel Stackplatz wird benötigt? 10 ist das absolute Minimum für den Stack. Werden der Funktion blink weitere Anweisungen im Befehlsblock hinzugefügt, sollte dieser Wert erhöht werden. Erhöhe den Stack um den Wert

  • 1 für jede benutzte lokale Variable.
  • 2 für jeden Funktionsaufruf.
  • 1 für jeden Parameter und jeden Rückgabewert, der bei einem Funktionsaufruf angegeben ist.

Print, scan und anderen Funktionen, die mit dem SimpleIDE Terminal interagieren, können immer nur von einem Cog zur Zeit ausgeführt werden. Normaerweise werden diese Funktionen über das Hauptprogramm main() aufgerufen, auch wenn sie Werte ausgeben sollen, die von anderen Cogs verändert wurden. Aber die Funktion print kann auch von anderen Cogs ausgeführt werden, wie wir später sehen werden.

3 - Zwei Funktionen, zwei Cogs

Das Programm multicore_1.c wird jetzt so erweitert, dass zusätzlich die LED an P27 mit einer anderen Frequenz blinkt. Das dazu notwendige Programm ist simple: füge eine zweite Funktion dem Programm hinzu und übergib ihre Ausführung einem weiteren Cog über die Funktion cog_run. Dazu ist eine weitere forward-Deklaration notwendig und ein zweiter Cog-Pointer, der den Cog herunterfahren kann.

Zwei Funktionen - zwei Cogs
Material
  • 1x  Prop-BoE oder Prop-AB
  • 1x  USB Verbindungskabel
Aufgaben
  • Übertrage das Programm multicore_2.c in den Editor und speichere es ab.
  • Starte das Programm aus dem Hauptmenü über Load RAM & Run.
  • Überzeuge dich, dass die LEDs an P26 und P27 mit unterschiedlicher Frequenz blinken.

Das Programm multicore_2.c

Abbildung 2 - Programm multicore_2.c (Courtesy of Parallax Inc.)

Na ja, so spektakulär war das jetzt vielleicht nicht mit den beiden blinkenden LEDs. Aber schauen wir uns das Ergebnis einmal genauer an: eine LED blinkt alle 100ms, die andere alle 223ms. Wie müsste man eigentlich einen Arduino UNO oder eine BASIC Stamp programmieren, damit die LEDs mit unterschiedlichen Frequenzen blinken? Oder noch verrückter, wir lassen fünf oder sechs LEDs mit unterschiedlichen Frequenzen blinken. Dieses einfache Beispiel soll zeigen, wie man mit Multiprocessing zeitsensitive Abläufe relativ einfach bewältigen kann.

4 - Drei Cogs steuern drei Prozesse gleichzeitig

Um nachzuweisen, dass drei Cogs tatsächlich drei unterschiedliche Prozesse zur selben Zeit bearbeiten, lassen wir den ersten Cog (Cog 0) etwas mehr als nur den Start der anderen beiden Cogs ausführen.

Drei Cogs steuern drei Prozesse gleichzeitig
Material
  • 1x  Prop-BoE oder Prop-AB
  • 1x  USB Verbindungskabel
Aufgaben
  • Im Hauptprogramm werden drei print-Anweisungen eingefügt: eine vor, eine zwischen und eine weitere nach dem Funktionsaufruf von cog_run. Dazu wird ein Text im Terminal ausgegeben, wie in Abb. 3 gezeigt.
  • Übertrage die Änderungszeilen aus Abb. 4 in das Programm multicore_2.c und speichere es unter dem neuen Namen multicore_3.c ab.
  • Klicke auf Run with Terminal und überprüfe, ob der Programmcode fehlerfrei ausgeführt wird.
Abbildung 3 - Terminalausgabe zu Programm multicore_3.c

In das bestehende Programm mulitcore_2.c werden die print-Anweisungen (Programmzeilen 14, 16 und 18) eingefügt.

Abbildung 4 - Programmausschnitt aus dem Hauptprogramm main von multicore_3.c

5 - Die Funktion cog_end

Bis jetzt haben wir uns nur damit befasst, wie man einen Cog mit Hilfe der Funktion cog_run startet. Jetzt geht es darum, wie man einen Cog mit Hilfe der Funktion cog_end und die von ihm bearbeitete Funktion abschaltet.

Einen Cog herunterfahren
Material
  • 1x  Prop-BoE oder Prop-AB
  • 1x  USB Verbindungskabel
Aufgaben
  • Übertrage das Programm multicore_4.c in den Editor und speichere es ab.
  • Starte das Programm aus dem Hauptmenü über Load RAM & Run.
  • Überzeuge dich, dass die LED an P26 nach 3s aufhört zu blinken.

Das Programm multicore_4.c

Abbildung 5 - Programm multicore_4.c (Courtesy of Parallax Inc.)

Wie arbeitet das Programm multicore_4.c?

Programmzeilen 6 - 8

Die simpletools Bibliothek wird in das Programm eingebunden; damit sind unter anderen auch die Funktionen cog_run und cog_end verfügbar. In der folgenden Zeile erscheint die forward-Deklaration einer Funktion blink, die unterhalb der Hauptfunktion main spezifiziert wird.

 

Programmzeile 12

Die Funktion cog_run liefert einen Rückgabewert; in diesem Beispiel ist es eine lokale Variable, die mit

int *cog =

deklariert wird. Das Sternchen * weist darauf hin, dass cog ein Pointer ist, dass der in ihm abgelegte Wert eine Speicheradresse ist. Das ist die Stelle, bei der der Funktionsaufruf von cog_run Stackplatz einplant und die ID Nummer des gestarteten Kerns erfasst.

int *cog = cog_run(blink, 128);

 

Programmzeile 13

Mit pause(3000) verweilt das Programm für 3s in einer Pausenschleife. In der Zwischenzeit blinkt die LED an P26, da sie von einem anderen Cog gesteuert wird.

 

Programmzeile 14

Der Parameterwert der cog_end Funktion benutzt den Rückgabewert der Funktion cog_run. Erinnern wir uns, dass cog die Speicheradresse ist, bei der die ID Nummer des neu gestarteten Kerns abgelegt ist. Cog_end geht zur angegebenen Adresse, holt sich dort die ID Nummer und fährt den Cog herunter.

Die Funktion blink befindet sich unterhalb der Hauptfunktion main. Verwirrend mag sein, dass sich die Ausführungsroutine für die blinkende LED in einer Endlosschleife befindet und doch beendet wird. Das liegt daran, dass der Kern, der für die Ausführung dieser Programmzeilen verantwortlich zeichnete, mit der Funktion cog_end heruntergefahren wurde. Aufgerufen wurde diese Funktion vom Cog, der das Hauptmenü ausführt.

Recycle benutzte Cogs!

Fahre die Cogs, die nicht mehr benötigt werden in einem Programm, mit dem Aufruf der Funktion cog_end herunter; ganz egal, ob sie mit einer Endlosschleife arbeiten oder nicht. Damit wird Stackplatz freigegeben und der Cog ist für andere Aufgaben im Programm erneut einsetzbar.

 

Denke lokal und spare ein paar Bytes

In der eben vorgestellten Technik, bei der *cog als lokale Variable deklariert und von cog_end in der selben Funktion aufgerufen wurde, benötigt weniger Speicherplatz, als wenn man *cog global deklariert.

 

Werde global und delegiere

Wird *cog global deklariert, lässt er sich von jeder Funktion der Anwendung aufrufen, auch von denen, die von anderen Kernen gesteuert werden. Dafür benötigt man etwas mehr Programm-Speicherplatz, entlastet aber damit das Hauptprogramm von dieser Aufgabe.

6 - Shut-down mit globaler Variablen

Ein Cog-Pointer kann global oder lokal deklariert werden. Geschieht dies global, kann die Funktion cog_end von jeder Funktion einer Anwendung genutzt werden. Die cog_end Funktion kann zum Beispiel direkt in die Funktion blink eingebaut werden, so dass der Cog, der diese Funktion ausführt, sich selbst herunterfährt.

Shut-down mit globaler Variablen
Material
  • 1x  Prop-BoE oder Prop-AB
  • 1x  USB Verbindungskabel
Aufgaben
  • Lade das Programm multicore_4.c in den Editor und speichere es unter multicore_5.c ab.
  • Füge die Änderungszeilen aus Abb. 6 in das Programm ein.
  • Starte das Programm aus dem Hauptmenü über Load RAM & Run.
  • Überzeuge dich, dass die LED an P26 nach 3s aufhört zu blinken.

Programm

Abbildung 6 - Programm multicore_5.c

7 - Shut-down

Bis jetzt haben wir gezeigt, wie man einen Kern aus dem Hauptprogramm herunterfährt und wie ein Kern sich selbst herunterfährt. In der folgenden Übung werden zwei Cogs gestartet und es wird gezeigt, wie dieser Cog sich selbst und den anderen Cog herunterfährt.

Shut-down
Material
  • 1x  Prop-BoE oder Prop-AB
  • 1x  USB Verbindungskabel
Aufgaben
  • Lade das Programm multicore_4.c in den SimpleIDE Editor.
  • Schreibe eine Funktion blink2, die die LED an P27 in einer Endlosschleife alle 53ms an und aus gehen lässt.
  • Speichere das Programm unter dem Namen multicore_6.c ab.
  • Füge oberhalb des Hauptprogramms eine forward-Deklaration für blink2 ein und deklariere einen globalen Pointer *cog2.
  • Starte im Hauptprogramm die Funktion blink2 mit einem neuen Cog und lege den Rückgabewert der Funktion cog_run in der Variablen cog2 ab.
  • Füge in der Funktion blink einen weiteren cog_end Funktionsaufruf ein, der den Cog, der die Funktion blink2 steuert, herunterfährt.
  • Starte das Programm und überzeuge dich, dass es läuft: beide LEDs an P26 und P27 sollten ca. 3s blinken und nahezu zur gleichen Zeit aufhören.

Das Programm multicore_6.c

Abbildung 7 - Programm multicore_6.c
  • Durch eine einfache Umstellung kann man erreichen, dass P27 nicht aufhört mit dem Blinken. Schaue dir noch einmal die beiden cog_end Aufrufe in der Funktion blink an. Welcher wird zuerst ausgeführt? Wenn du eine Idee hast, ändere das Programm ab und starte es erneut. Blinkt die LED an P27 weiter und warum tut sie das?

8 - Kerne und globale Variable

Programmcode, der mit verschiedenen Cogs arbeitet, kann über die Installation globaler Variablen gemeinsame Daten verwalten. Anders als bei der Arbeit mit Funktionen muss bei der Multicore-Programmierung globalen Variablen das Schlüsselwort volatile vorangestellt werden. Praktisch sieht das dann so aus, dass statt

  • int globalVar

die Deklaration lautet

  • volatile int globalVar

Im folgenden Programmbeispiel wird eine globale volatile Variable mit Namen t deklariert, mit der die Hauptfunktion die Blinkrate einer LED steuert. Die Hauptfunktion legt den Wert von t fest und die Funktion blink benutzt den Wert von t, um die Pausenzeiten zwischen HIGH - LOW Zuständen zu setzen.

Kerne und Daten verwalten
Material
  • 1x  Prop-BoE oder Prop-AB
  • 1x  USB Verbindungskabel
Aufgaben
  • Übertrage das Programm multicore_7.c in den Editor und speichere es ab.
  • Starte das Programm mit Load RAM & Run.
  • Überprüfe, ob die LED für 2s mit einer bestimmten Frequenz blinkt und dann mit doppelter Frequenz noch einmal 2s. Anschließend geht die LED aus.

Das Programm multicore_7.c

Abbildung 8 - Programm multicore_7.c (Courtesy of Parallax Inc.)

Wie arbeitet das Programm?

Das Programm startet mit drei Deklarationen (Zeilen 6 - 8). Die erste gibt uns Zugang zu allen Funktionen der Bibliotheksfunktion simpletools, die zweite ist eine forward-Deklaration für die Funktion blink, die nach der Hauptfunktion beschrieben ist und die dritte deklariert eine globale volatile Variable int dt. Da sie außerhalb einer Funktion deklariert wurde, ist sie global und damit für alle Funktionen des Programms verfügbar. Sie ist vom Typ volatile und kann damit von allen Funktionen, die von unterschiedlichen Kernen gesteuert werden, benutzt werden.

Im Hauptprogramm wird in Programmzeile 12 die globale Variable dt mit dem Wert 100 initialisiert. Mit dem Aufruf von cog_run in Zeile 13 wird ein neuer Cog gestartet (Cog 1), der die Funktion blink mit dem globalen Variablenwert dt = 100 in einer Endlosschleife ausführt.

In Zeile 14 passiert für 2s beim Cog 0 gar nichts, während Cog 1 die LED an P26 im Takt von 100ms blinken lässt.

In Zeile 16 wird der volatilen globalen Variablen dt ein neuer Wert 50 zugewiesen. Die Funktion blink und mit ihr Cog 1 sind sofort davon betroffen. Der alte Pausenwert 100ms wird durch den neuen 50ms Wert ersetzt und ausgeführt, mit dem Erfolg, dass die LED an P26 jetzt doppelt so schnell blinkt.

Nach weiteren 2s (Zeile 17) stoppt das Hauptprogramm Cog 1 (Shut down), der die Funktion blink steuerte.

Unterhalb des Hauptprogramms befindet sich die Funktion blink, die bereits aus den vorherigen Übungen bekannt sein sollte.

9 - Ein Kern überwacht einen andern Kern

Im letzten Beispiel hat der Kern der Hauptfunktion das Verhalten einer Funktion, die von einem anderen Kern gesteuert wird, beeinflusst. Ein Kern kann das Verhalten eines anderen Kernes mit Hilfe einer Variablen überwachen. Im folgenden Beispiel wird geprüft, wie oft die Funktion blink eine LED ein- und ausgeschaltet hat.

Ein Kern überwacht einen andern Kern
Material
  • 1x  Prop-BoE oder Prop-AB
  • 1x  USB Verbindungskabel
Aufgaben
  • Speichere das Programm multicore_7.c unter dem neuen Namen multicore_8.c ab.
  • Füge dem Programm eine volatile int Variable wdh hinzu und initialisiere sie mit 0.
  • Füge zwei print-Befehle in die Hauptfunktion ein, die die Werte von wdh überprüfen und anzeigen.
  • Füge einen Befehl in die while-Schleife der Funktion blink ein, der den Wert von wdh nach jedem Schleifendurchlauf um 1 erhöht.
  • Speichere das Programm ab und starte es mit Run with Terminal.
  • Über die Hauptfunktion wird mit Hilfe des einen Kerns (Cog 0) die Anzahl der Wiederholungen (wdh) im Terminalfenster angezeigt, während die Funktion blink den Wert der Variablen wdh über einen zweiten Kern (Cog 1) aktualisiert.

Das Programm multicore_8.c

Abbildung 9 - Programm multicore_8.c (Courtesy of Parallax.Inc.)

Die entsprechende Terminalausgabe zum Programm multicore_8.c zeigt Abb. 10.

Abbildung 10 - Terminalausgabe des Programms multicore_8.c

10 - print-Befehl mit verschiedenen Kernen ausführen

Bisher wurde bei allen vorgestellten Programmen, in denen ein print-Befehl benutzt wurde, dieser in der Hauptroutine aufgerufen, die vom Cog 0 ausgeführt wird. Wie man einen Ausdruck auch von einem anderen Kern ausführen lassen kann, zeigt diese Übung.

Der Trick dabei ist, dass immer nur ein Kern zur Zeit mit dem SimpleIDE Terminal Verbindung aufnehmen kann und diese Verbindung geschlossen werden muss, bevor eine neue Verbindung aufgebaut wird.

Beim Prop-BoE und dem Prop-AB sind die I/O Ports P30 und P31 zum Programmieren und zur Kommunikation mit dem SimpleIDE Terminal über den USB Port vorgesehen.

print-Befehl mit verschiedenen Kernen ausführen
Material
  • 1x  Prop-BoE oder Prop-AB
  • 1x  USB Verbindungskabel
Aufgaben
  • Übertrage das Programm multicore_9.c in den Editor.
  • Speichere das Programm ab und starte es mit Run with Terminal.
  • Überzeuge dich, dass die in Abb. 11 dargestellte Meldung im Terminal erscheint.

Das Programm multicore_9.c

Abbildung 10 - Programm multicore_9.c

Wie arbeitet das Programm?

Mit dem Einbinden der Bibliothek simpletools.h (Programmzeile 4) stehen die Funktionen print, simpleterm_close und simpleterm_open zur Verfügung.

Die Hauptfunktion main, die vom Cog 0 gesteuert wird, startet mit einer print-Anweisung, die einen Text im Terminal ausgibt.

  • int main()
    {
         print("Cog 0 ist jetzt dran ...\n");
         simpleterm_close();
    }
    

Anschließend wird die Verbindung zum Terminal geschlossen (Zeile 12) und ein anderer Cog kann sie wieder öffnen. In Zeile 13 wird die Ausführung der Funktion anderer von einem neuen Cog übernommen. Der Rückgabewert wird in der Variablen andererCog abgelegt.

Die Funktion anderer beginnt ab Programmzeile 17. Sie läuft unter der Regie von Cog 1, der in Zeile 19 den Zugang zum Terminal erneut öffnet und dann einen Text ausgibt. Anschließend wird der Zugang wieder geschlossen und mit einem Shut down Cog 1 aus seinen Diensten entlassen.

Einen Text oder Werte auszudrucken kostet Stackplatz - Einen einfachen Text durch einen anderen Kern auszudrucken kostet nicht viel Stackplatz. Der vorgegebene Wert von 128 kann auf den Minimalwert 10 reduziert werden und das Programm funktioniert immer noch.

Sobald ein Kern aber den Wert globaler Variablen überprüft und das Programm auch mit lokalen Variablen arbeitet und die Ergebnisse ausgedruckt werden sollen, muss ein entsprechender Stackbereich zur Verfügung gestellt werden. In einem solchen Fall ist ein Wert von 128 das Minimum. Erscheinen die benutzerdefinierten Druckanweisungen nicht so im Terminal wie erwartet, kann der Stackplatz für den entsprechenden Cog erhöht werden.

 

Der Aufbau einer seriellen Verbindung benötigt Zeit - Sollten nicht alle print-Anweisungen so im Terminal erscheinen wie erwartet, dann kann eine pause(100) Befehl direkt nach dem simpleterm_open Aufruf möglicherweise Abhilfe schaffen.

 

Nicht nur PRINT! Schau nach in der Datei simpletext.h -  Es gibt eine Reihe von Funktionen in dieser Bibliothek(printi, scan, scani und alle Befehle, die mit put oder get beginnen), die mit dem SimpleIDE Terminal zusammenarbeiten. Wie schon beim print-Befehl gezeigt, können sie alle nur von einem Kern zur Zeit benutzt werden. Vorher muss die alte Terminalverbindung geschlossen und die neue geöffnet werden.  

Druckversion | Sitemap
© Reinhard Rahner - Gettorf