02 | 00 Der Mikrocontroller und der Maschinencode  

Heutzutage verfügt fast jedes elektronische Gerät über einen Mikrocontrollereinheiten (MCU = Micro Controller Unit). Sehr viele Echtzeitanwendungen verwenden einen Mikrocontroller anstelle eines Mikroprozessors.

Ein Mikrocontroller wie das Arduino ist, wie der Name schon sagt, ein winziges Gerät, das vom User zugewiesene Aufgaben ausführt. Dabei kann es sich um  Standard-Berechnungen wie Addition, Subtraktion, Division und Fließkommaberechnungen handeln. Darüber hinaus steuert, verarbeitet und speichert der Mikrocontroller Informationen im Speicher.

Tatsächlich ist ein Mikrocontroller wie der Arduino Uno ein kleiner Ein-Chip-Computer. Der Unterschied zu PC-Prozessoren besteht darin, dass bei einem Mikrocontroller die Komponenten meistens auf einem einzigen Chip integriert sind. Als Kompakt-Computer auf kleinstem Raum besitzt es somit all die Peripherie, die auch ein Heim-Computer hat: eine Zentrale-Recheneinheit, Ein- und Ausgabeports, Arbeitsspeicher, Datenspeicher, Taktgeber … usw.)

Werfen wir einen Blick auf das vereinfachte Blockschaltbild eines Mikrocontrollers und sehen uns ihre Funktionen an.

Im Blockschaltbild sind verschiedene Komponenten zu sehen, die alle eine spezifische Aufgabe haben. Um miteinander zu kommunizieren, teilen sich die Blöcke ein Datentransportsystem.

Die Zentrale Recheneinheit

Die Central Processing Unit (CPU) ist die zentrale Recheneinheit, das Herz und das mathematische Gehirn eines Mikrocontrollers, deren Hauptfunktion die Dekodierung und Ausführung von Befehlen ist. Maßgeblich für die Rechenleistung ist seine maximale Taktfrequenz mit der die Aufgabenstapel abgearbeitet werden. Außerdem adressiert sie den Speicher, verwaltet die Ein- und Ausgänge und reagiert auf Interrupts.

Interrupt-Steuerung

Ein Interrupt (kurz IRQ = Interrupt Request) ist eine sehr nützliche Funktionalität, die den laufenden Rechenzyklus unterbrechen und auf ein bestimmtes Ereignis reagieren kann. Hier ein Szenario für ein Interrupt: Dein Roboter stößt gegen ein Objekt und vollzieht ein Ausweichmanöver, indem es rückwärts fährt, sich dreht und wieder gerade aus fährt. Stell dir vor, es befindet sich hinter dem Roboter ein Abgrund (Treppe) und er fährt rückwärts darauf hinzu. Ein Sensor registriert den Abgrund im letzten Moment. Sofort springt die Interrupt-Steuerung ein, unterbricht das laufende Programm und leitet ein Notfall-Programm ein. Dabei ist es vollkommen egal was der Mikrocontroller gerade berechnet oder macht, er bricht alles sofort ab. Demnach ist ein Interrupt eine Unterbrechungsanforderung.

Der Datenbus

Tatsächlich können wir uns den Datenbus als eine Busmetapher vorstellen, der Daten zwischen den Blöcken hin und her transportiert. Ein typisches Szenario: Die CPU benötigt Daten aus dem Speicher, die auf Anforderung auf den Datenbus gelegt und zur CPU geschickt werden. Die CPU berechnet ein Ergebnis, legt die Daten oder Anweisungen auf den Bus. Das Ziel der Anweisung könnte ein Ausgangsport sein, an dem ein Motor angesteuert wird.

Der Oszillator

Das Arduino braucht, wie jeder andere Prozessor auch, einen Taktgeber. Mithilfe der Taktung werden Berechnungsimpulse vorgegeben, damit jede elektronische Komponente auf dem Board mit der gleichen Geschwindigkeit (Takt) arbeitet. Das Taktsignal wird beim Arduino mit einem externen Quarzoszillator zugeführt. Ein Quarzoszillator ist eine elektronische Schaltung, das sehr genaue Frequenzen (Anzahl von Schwingungen pro Sekunde) erzeugt. Die CPU führt Befehle mit einer Geschwindigkeit von MegaHertz (MHz) oder GigaHertz (GHz) aus. Das Arduino Uno hat beispielsweise eine Taktfrequenz von 16MHz.

Speicherbereiche

Der Speicher des Arduino ist vergleichbar mit dem Speicher eines Computers. Man unterscheidet zwischen einem flüchtigen und nicht flüchtigen Speicher. Auf dem Computer werden nach dem Ausschalten alle Daten aus dem RAM-Speicher gelöscht. Sie müssen bei Neustart aus der Festplatte in den RAM transferiert werden.

Beim Arduino ist das ähnlich. Er besitzt einen Programmspeicher und einen Datenspeicher. Wie der Name schon andeutet beinhaltet der Programmspeicher unser Programm, dass die CPU abarbeiten soll. Jedes Mal, wenn du ein Sketch auf das Board hochlädst wird er in den Programmspeicher transferiert. Man könnte sagen er verhält sich wie eine Festplatte.

Im Gegensatz zum Programmspeicher haben wir den flüchtigen Datenspeicher, der alle Daten verliert, sobald das Board vom Strom getrennt wird. Hier werden z. B. Variablen und andere Daten die zur Laufzeit des Programms anfallen, zwischen gespeichert.

Die Ein- bzw. Ausgabeports

Du hast schon mitbekommen, das die Pins des Mikrocontrollers die Tür zur Außenwelt sind. Pins werden auch Ports genannt. Ein- und Ausgabeports werden in digital und analog unterteilt:

Digital OUTPUT
Digital INPUT
Analog OUTPUT
Analog INPUT

Was passiert hinter den Kulissen?

Mittlerweile hast du schon erste Programme geschrieben. Doch was passiert eigentlich mit dem Sketch hinter den Kulissen? Vom Eintippen des Codes, über das Hochladen zum Arduino bis zur Ausführung passiert einiges. Es ist nämlich nicht so, dass der Text aus deinem Sketch einfach rüber kopiert wird – es kommt etwas anderes an.

Das Arduino-Projekt bedient sich der C/C++ Sprache. Sie wird als Hochsprache bezeichnet. Da Computer nur das Binärsystem verstehen, muss die Hochsprache für den Computer in eine verständliche Form umgewandelt werden.

Die untere Abbildung ist eine vereinfachte Übersicht.

Maschinen Code

Maschinensprache, auch nativer Code genannt, basiert auf dem Binärsystem. Maschinencode, rein aus Zahlenwerten zusammengefügt, sind Befehlsblöcke, die direkt von der CPU verstanden und ausgeführt werden können. Maschinensprache ist für den Menschen nur sehr schwer zu lesen.

“Ein in Maschinensprache geschriebenes Programm zu betrachten, ist ungefähr vergleichbar mit der Betrachtung eines DNA-Moleküls Atom für Atom.”

— Douglas Hofstadter

Hochsprache

Viel verständlicher als Maschinencode, ist die Hochsprache. Hochsprachen lehnen sich an die menschliche Sprache an. Wenn wir Menschen kommunizieren, benutzen wir gewisse Regeln, in der Hoffnung, dass uns die andere Seite versteht. Die Grammatik enthält bestimmte Elemente und folgt Regeln (Satzbau, Adjektive, Verben …). Auch Hochsprachen besitzen eine Grammatik, die Syntax genannt wird. Die Arduino IDE ist besonders streng bei der Einhaltung von Syntaxregeln und verweigert bei Regelbruch jeden Dienst.

Der Kompiler

Wenn es auf der einen Seite eine Hochsprache gibt und auf der anderen eine CPU, die nur Maschinencode in binärform versteht, brauchen wir zwischen den beiden einen Übersetzer. Sobald du in der IDE den Upload- oder Überprüfen-Button klickst, checkt der Compiler die Syntax der Hochsprache, erzeugt effizienten und schnellen Maschinen-Code, führt die Laufzeitorganisation durch und formatiert den Code in komprimierter ausführbarer Form. Falls vorhanden erzeugt er darüber hinaus Warnungen in verständlicher Weise.

Machen wir ein Negativ-Beispiel. Tippe in der Arduino IDE zum Beispiel folgenden Text „ich bin kein C-Code“ und überprüfe (CMD+R) den Code. Der Compiler wird sich gleich melden und dir die folgende Fehlermeldung geben:

„ich bin kein C-Code“ does not name a type

Das geschriebene ist eben kein C-Code und wird nicht in Maschinen-Code umgewandelt.

Lass uns noch ein Beispiel machen. Schreibeüberhaupt nichts (auch kein void setup oder void loop) in die IDE und klicke auf den Überprüfen-Button. Es erscheint eine etwas längere Fehlermeldung, in der sich unter anderem folgende Zeilen befinden

undefined reference to `setup'
undefined reference to `loop'

Das bedeutet für uns, dass das Fehlen von void setup() und void loop() nicht akzeptiert wird. Beide Funktionen müssen bei jedem Sketch platziert werden. Das ist die Minimal-Version eines Arduino-Sketches.