„Warum geschickt, wenns umständlich geht?“ Das hat einmal einer meiner Mathelehrer gesagt. Ich finde den Spruch noch heute gut aber wie immer kommt es dabei auf die Sichtweise an. Diese Frage kann man sich auch beim Beispielprogramm Blink stellen. Ob sinnvoll, oder nicht. Interessant allemal.
Vielleicht ist es auch nicht immer der Unterschied zwischen geschickt und umständlich oder gut und schlecht. Sondern die Frage nach dem persönlichen Geschmack, wie man tickt oder ob man durch andere Wege nicht plötzlich auch die Chance hat, unerwartet Neues zu entdecken. Und deswegen ganz bewußt die Dinge anders macht, eben nicht am einfachsten bzw was andere dafür halten, am einfachsten zu sein. Eben beschwerlicher. Zunächst.
Blink. Ein gelernter Programmierer bin ich nicht. Faszinierend fand ich es aber schon immer, sowas zu können, und ja – ich hatte einen C64 und ich bildete mir ein, darauf Maschinensprache zu lernen. Quasi das Grunzen unter den Programmiersprachen. Ich scheiterte. Da muss man ganz neu denken lernen und verstehen, wie ein Computer von innen funktioniert. Das weiß ich bis heute nicht wirklich und es würde mir bei der täglichen Arbeit an einem der modernen Nachkommen vom C64 ohnehin keinen Meter weiterhelfen. Ganz tief unten jedoch sind es noch immer irgendwelche Zustände von AN und AUS, EINS und NULL, die unermüdlich ihre Arbeit tun. Soviel ist sicher und ich glaube, daran wird sich auch so bald nichts ändern.
Was aber kann das mit dem Blinken einer LED zu tun haben? Meine Antwort darauf ist: Weil man es so oder so machen kann. Oder so. Oder ganz anders.
Die, wie ich finde, Hardcore-Variante von Blink ist die Möglichkeit, direkt bestimmte Bits in den sogenannten Registern zu setzen und damit eine LED blinken zu lassen. Nicht, dass man das dafür unbedingt bräuchte. Aber ich bin fasziniert davon. Hat so seine ganz eigene Ästhetik. Je nach Sichtweise.
Zaubercode. Was passiert hier? Wie liest man das? Genau das habe ich mich selber schon oft gefragt. Einmal mehr ist etwas Hintergrundwissen gefragt. Aber wieviel davon? Ich denke, ein wenig sollte hier reichen um bereits eine Tür zu einer ganz anderen Welt aufzustoßen.
Viele Angelegenheiten werden in einem Mikrocontroller in Registern organisiert. Dort ist eine Anzahl von Bits zusammengefasst, von denen jedes Bit ein AN/AUS-Schalter für eine bestimmte Aufgabe darstellt. Ein Register hat einen Namen, den man im Programmcode wie eine Variable aufrufen kann. Zwei Register davon sind im gezeigten Blink-Code genannt:
DDRB: „Port B Data Direction Register“
PORTB: „Port B Data Register“
Die Bits im DDRB sind dafür zuständig, einen bestimmten Digital-Pin grundsätzlich entweder als Ausgang (TRUE) oder als Eingang (FALSE) zu definieren. Sofern ein bestimmter Pin als Ausgang definiert wurde, legt das entsprechende Bit im Register PORTB dann fest, ob am Pin eine Spannung anliegt (TRUE) und damit z.B. eine LED betrieben werden kann, oder ob keine Spannung anliegt (FALSE), d.h. die LED wäre dann aus.
Umgekehrt, also wenn im DDRB ein Pin als Eingang definiert ist (FALSE), kann geprüft werden, ob von außen ein bestimmter Spannungspegel anliegt. Das wird für andere Aufgaben benötigt, nicht jedoch für eine blinkende LED.
Die beiden Register haben je 8 Bits und sind dem sogenannten Port B des Mikrocontrollers zugeordnet. Dieser Port B ist für 8 der insgesamt 28 Pins zuständig. In sofern: es gibt noch weitere Ports, nämlich Port C und Port D. Ich habe keinen Schimmer, warum es keinen Port A gibt. Was nicht schlimm ist. Manche Pins werden für andere Dinge gebraucht, zum Beispiel um einen Quarz anzuschließen (siehe Pins 9/10 bzw PB6/PB7) und stehen nicht für den Anschluß etwa einer LED zur Verfügung. Man kann nicht alles haben. Der Mikrocontroller beim Arduino ist übrigens ein ATmega328. Das Kerlchen gibt es für ganz kleines Geld auch separat zu kaufen. Und ab da wird es dann für Selbstbauprojekte so richtig interessant. Das ist aber eine andere Geschichte…
Im folgenden Bild ist zu sehen, welche Pins am Microcontroller zu welchem Register gehören und zu welchem Steckplatz am Arduino (UNO):
Sicher gibt es gute Gründe, weshalb die dargestellte Zuordnung der Pins zu den drei Ports in dieser Form gewählt und festgelegt wurde. Ich denke, es hat mit der inneren Architektur zu tun. Verwirrend ist das nur auf den ersten Blick. Richtig konfus wird es erst, wenn noch die weiteren Nummerierungen und Aufgabenbezeichnungen der Pins hinzukommen. Was für den Moment jedoch überflüssig ist und einfach weggelassen wurde. Immerhin ist die Nummerierung der physikalischen (Metall-) Pins in einer strengen Reihenfolge von 1 bis 28 gehalten. Gezählt von oben links gegen den Uhrzeigersinn. Bemerkenswerterweise wird hier übrigens nicht von 0 bis 27 gezählt.
Also…die LED am Arduino-Pin9 (alias ATmega-Pin15) soll leuchten? Dann muss im Register DDRB das zweite Bit (was der Bitnummer 1 entspricht, da von 0 an gezählt wird) gesetzt sein, also auf „TRUE“ bzw „1“ stehen und das korrespondierende Bit im Register PORTB ebenfalls. Alles klar?
In Zaubercode verpackt sieht das vollständige(!) Programm dann so aus:
Die LED an Pin 9 wird schon im Setup eingeschaltet. Und das wars dann auch schon, denn im Loop bleibt nichts zu tun. Was etwas langweilig ist. Die LED leuchtet. Sofern sie nicht kaputt ist. Und richtig angeschlossen ist (Pin9 an Widerstand die eine Seite, Widerstand die andere Seite ans lange Beinchen (+) der LED, kurzes Beinchen der LED an GND). Für eine rote 5mm Standard LED mag ein Widerstand von 330 Ohm keine schlechte Wahl sein. Es gilt, den Strom zu begrenzen.
Spannend ist jetzt die Frage, wie man die Geschichte zum Blinken bringt. Ganz oben stand der Code bereits. Das wunderbare daran ist, dass man mit den Bits verschiedene Operationen durchführen kann. Zwei bestimmte Operationen sind hier von besonderem Interesse: bitweises OR und bitweises XOR.
Gesehen hat man sowas ja sicher schon irgendwo und oft. Mein Problem dabei ist, ich kann es mir nicht merken. Deswegen ist es gut, dass solche Verknüpfungsregeln aufgeschrieben werden. Oder man kann es in Worte fassen und versuchen, es sich auf diese Weise zu behalten. Kommt halt darauf an, wie man so tickt:
OR: nur wenn beide zu verknüpfende Bits FALSE sind, bleibt es bei FALSE. Ansonsten kommt immer TRUE dabei heraus.
XOR: sofern beide zu verknüpfende Werte gleich sind kommt FALSE dabei heraus. Sind sie unterschiedlich, ist das Ergebnis TRUE.
Damit kann man nun loslegen. Zunächst wäre sicherzustellen, dass das Bit für Pin 9 (am Arduino) auf TRUE gesetzt wird. Grundsätzlich könnte es sein, dass die anderen Bits aus bestimmten Gründen nicht auf FALSE stehen und dass dies auch so bleiben soll. Möglicherweise weil neben dem Blinken noch andere Dinge geschehen sollen und entsprechend codiert sind. In diesem Fall wäre es ungeschickt, das gesamte Register einfach mit
DDRB = B00000010;
einzustellen, wie wir das oben gemacht haben. Der Trick ist nun, bitweise eine OR-Verknüpfung zwischen dem wie auch immer zufällig gerade gefüllten Register und den (konstanten) 8 Bits [00000010] durchzuführen. Mit dem folgenden Bild wird das klar, wie ich denke:
Wie man leicht sieht, wird sichergestellt, dass an der gewünschten Stelle das Bit auf TRUE gesetzt wird (die rote Eins). Alles andere bleibt, wie es ist. Cool! Im Code wird jedoch nicht das „OR“ hingeschrieben, sondern ein senkrechter Strich „|“. Im Code wird den Bits, mit denen „gerechnet“ wird einfach ein B vorangestellt.
DDRB = DDRB | B00000010;
Und jetzt das Blinken. Was muss gemacht werden? In jedem Loop muss das Bit an der zweiten Stelle im Register PORTB von dem einen auf den anderen Zustand gesetzt werden. Es macht die Sache etwas leichter, dass es nur zwei Zustände gibt. Hin – und herschalten. Toggeln. Schönes Wort.
Im Code wird für das „XOR“ dann ein „^“ geschrieben. Was schließlich so aussieht:
PORTB = PORTB ^ B00000010;
Das muss im Loop stehen, so dass dies immer und immer wieder gemacht wird. Und weil der Microcontroller das ziemlich schnell umschaltet, muss er quasi künstlich eingebremst werden und darf solange nichts anderes machen als Millisekunden zählen. Andernfalls würden wir nur eine leuchtende LED sehen, da es unseren Augen nicht möglich ist, die unterschiedlichen Zustände von AN und AUS sauber zu unterscheiden. Und während die Milliselkunden also gezählt werden, bleibt der zuvor eingestelte Zustand eben so lange erhalten. Es blinkt und zwar mit mit gleichlangen Phasen AN und AUS.
void setup() {
DDRB = DDRB | B00000010;
}
void loop(){
PORTB = PORTB ^ B00000010;
delay(1000);
}
Geschafft! Die LED blinkt wieder im Sekundentakt. Geschickt oder umständlich? Kommt auf die Sichtweise an. Unnötig zu erwähnen, dass mein Mathelehrer mich gemeint hatte…