Arduino Port-Manipulation

By | 7. Februar 2017

Es kann bei manchen Anwendungen vorkommen, dass die Arduino-Input/Output-Funktionen, wie analogRead() oder digitalWrite() zu langsam sind, und den Programmfluss stören. In solchen Fällen kann man auf die Hardware-Ports des ATmega 328 direkt zugreifen. Da diese Form der Programmierung die Manipulation einzelner Bits erfordert, ist dazu die Kenntnis der elementaren Bit-Manipulations-Operationen notwendig. Der Vorteil dieser Mühe liegt aber in sehr kompakten Programmen und vor allem erfolgt der Ablauf etwa 50mal schneller als mit den Standard Arduino-Input/Output-Funktionen.

Daten-Ausgabe

Beim Arduino Uno mit dem ATmega 328 werden sämtliche Ein- und Ausgänge zu Ports zusammengefasst. Die komplette Pin-Belegung des ATMega328 ist in der nebenstehenden Abbildung  zu sehen. Hier erkennt man, dass die Pins nicht einzel angesteuert werden, sondern in mehrere Ports zusammengefasst sind. Es stehen bei diesem Prozessor drei Ports zur Verfügung, die mit den Buchstaben B, C und D bezeichnet werden. Port D beinhaltet am Arduino die digitalen Pins 0 bis 7, Port B die digitalen 8 bis 13 und Port C die analogen Pins 0 bis 5. In der folgenden Abbildung  ist diese Zuordnung noch einmal in der Übersicht schematisch dargestellt:

 

Die Ports werden von jeweils 3 Registern gesteuert, die mit DDRx,  PORTx und PINx bezeichnet werden. Wobei das x für die Ports B,C und stehen. Diese drei Register existieren für jeden Port, also zum Beispiel existiert für Port B ein DDRB-, ein PORTB- und ein PINB-Register. Analog gilt das auch für Port C und Port D. Das jeweilige DDRx-Register (DDR = Data Direction Register) legt fest, ob die PINs im entsprechenden Port als Input oder Output konfiguriert sein sollen. Das PORTx-Register legt bei der Ausgabe von Daten fest, ob die PINs HIGH oder LOW sind.

Für die Ausgabe von Daten wird das entsprechende Bit im DDRx-Register auf 1 (= Ausgabe) gesetzt. Somit kann nun die Ausgabe dadurch erfolgen, dass das korrespondierende Bit im zugeordneten PORTx-Register auf den gewünschten Wert gesetzt wird.

Für die Daten-Eingabe wird im DDRx-Register das jeweilige Bit auf 0 (= Eingabe) gesetzt. Die an den Eingangs-Pins anliegenden Daten werden an die korrespondierenden Bits im PIN-Register übertragen und können dort abgerufen werden.

Die einzelnen Register sind in der Arduino-Entwicklungsumgebung bereits als Namen vor definiert, man kann diese somit mit den entsprechenden Bezeichnungen wie beispielsweise PORTB, PINC usw. ansprechen. Beispielsweise setzt der Befehl DDRD = B00111010 die PINs D0, D2, D6 und D7 als Eingänge sowie die PINs D1, D3, D4 und D5 als Ausgänge.

Wie schon erwähnt, wird über das PORTx Register fest gelegt, ob die Ausgangs-PINs auf LOW oder HIGH geschaltet werden sollen. Dabei steht das x immer symbolisch für einen der drei Ports. Um beispielsweise die PINs 8, 10 und 12 auf HIGH und PIN 9, 11 und 13 im Port B auf LOW setzen wollen, schreiben wir:

Einige weitere Beispiele sollen die Möglichkeiten Ein- und Ausgänge zu definieren illustrieren:

Nicht nur die Ports und Datenrichtungsregister (DDRx, PORTx, PINx) sind als Konstante bereits vordefiniert, sondern auch die Werte der einzelnen Bits. So werden die Bits der verschiedenen  Register wie folgt bezeichnet:

Es sind im Prinzip nur symbolische Namen für die einzelnen Bits. mit dem Ziel die Programme leichter lesbar zu machen.

Blink-Beispiel

Das folgende Beispiel-Programm im Listing  zeigt wie eine Blink-Funktion realisiert wird, in dem LEDs zyklisch an und ausgeschaltet werden. Dazu wird der Port B verwendet, dessen Pins im SetUp auf Ausgabe geschaltet werden. Im Loop-Bereich werden dann die PortB-Register wechselseitig auf 0 und 1 gesetzt. Wenn man nun LEDs an die Ausgänge D8 bis D13 anschliesst, wird man den visuellen Blinkeffekt sehen.

Beispiel-Anwendung

Eine Siebensegment-Anzeige ist ein Anzeige-Element aus sieben separat schaltbaren, sichtbaren LED-Balken, die in Form zweier übereinander stehender, häufig quadratischer Rechtecke angeordnet sind. Normalerweise werden die Segmente mit den lateinischen Buchstaben A bis G bezeichnet, wobei oben begonnen, im Uhrzeigersinn fortgefahren und das mittlere Segment zuletzt benannt wird.

Der nachfolgend dargestellte Quelltext nutzt die Standard Ausgabe-Funktionen des Arduino in Verbindung mit einer speziellen Array-Struktur in der die Ansteuer-Kodierung der Leucht-Segmente abgelegt ist, um die Ausgangs-Pins entsprechend anzusteuern.

 

 

Daten-Eingabe

Neben der Ausgabe von Daten können auch Daten über die Port-Manipulation eingelesen werden. Wie das Einlesen von Daten realisiert wird soll an einem einfachen Beispiel vorgestellt werden. Ziel ist es innerhalb eines Programms den Status zweier Schalter einzulesen. Auch hier soll zum Vergleich eine konventionelle Lösung und die Realisierung über die Portmanipulations-Mechnismen vorgestellt werden.

Um einen Schalter abzufragen, kann dieser nicht direkt an einen Arduino-Eingang angeschlossen werden, da so kein definierter HIGH- oder LOW-Pegel sichergestellt werden kann. Auch hierzu benötigt man einen sogenannten Pull-Up oder Pull-down-Widerstand. Dieser hat in der Digitaltechnik üblicherweise einen Wert von 10 kOhm und die Funktion die Spannung auf Masse (Pull-down) oder auf +5V (Pull-Up) zu ziehen solange der Schalter nicht geschlossen ist.

Somit hat der digitale Eingang bei offenem Schalter und geschlossenen Zustand einen definierten Pegel, der auch eindeutig softwaremässig erkannt werden kann. Nun benötigt man nur noch ein kurzes Programm um die Schaltstellung einzulesen. Die konventionelle Methode wie mit Hilfe der Standard IO-Operationen des Arduino die beisen Schalter abgefragt werden können, zeigt das nachfolgende Listing:

Ein entsprechender Versuchsaufbau ist in der nachfolgenden Abbildung dargestellt. Er zeigt ein Gehäuse aus dem 3D-Drucker mit 2 Schaltern, die zu beliebigen Steuer-Aufgaben eingesetzt werden können. Im dargestellten Aufbau werden zur Funktionsüberprüfung 2 LEDs angesteuert die natürlich jederzeit durch beliebige andere Aktionen ersetzt werden können.

Lösung mit Hilfe der Port-Manipulations-Funktionen

Um über die Arduino Ports Daten einzulesen, wird das Data Direction Register (DDRx) des jeweiligen Ports auf Daten-Input konfiguriert. Dies erfolgt über das setzen des Bits auf 0. Der Status der Eingabe-Pins des jeweiligen Ports kann dann über das PINx Register abgefragt werden.
Das PINx Register stellt ein genaues Abbild der Eingabe-Pins dar. Ist beispielsweise das Bit2 auf 1 gesetzt, so ist auch das 2. Bit im PINx-Register auf 1 gesetzt. Dadurch ist eine einfache Möglichkeit gegeben den Status der definierten Eingabe-Pins festzustellen.

Für unser Beispiel wählen wir uns das Bit0 und das Bit1 des PortsB um die Schalter-Stellungen abzufragen. Das entspricht laut Tabelle  den Arduino Pins D8 und D9. An diese werden somit die beiden Schalter angeschlossen. Die Aufgabe besteht nun darin das PortB-Register abzufragen und zu prüfen ob einer der Schalter betätigt wurde oder nicht.

Um einen bestimmten Eingangs-Pin auszulesen greift man auf Bit-Manipulations-Operationen zurück. Wie aus der Port-Belegung (siehe Abbildung)  hervorgeht, sind in Port B die Arduino-Pins D8 bis D13 zusammen gefasst. Dabei entspricht Bit0 dem Pin D8, Bit1 D9 usw. D.h. die beiden obern Bits 6 und 7 sind nicht belegt.

Für die Abfrage ob ein Bit gesetzt ist oder nicht, verwendet man die oben vorgestellte Methode der Maskierung. Dazu kann man eine Kombination aus einer UND-Verknüpfung und einer Shift-Left-Operation verwenden: Über die Anweisung PINB & (1 < < BitNr) erhält man den aktuellen Status der Bit-Stelle zurück. Möchte man beispielsweise wissen, welche Status an Bit2 anliegt, würde man PINB &(1<< 2) schreiben und falls ein HIGH- Pegel vorliegt, den Wert 4 zurück erhalten. Nachfolgend sind einige Abfrage-Möglichkeiten und die resultierenden Rückmeldungen aufgeführt.

Anhand einer Maskierung können die einzelnen Schritte dieser binären Operation noch einmal im Detail nachvollzogen werden. Ausgangspunkt ist das Register PINB, das alle Eingänge des Ports B wieder spiegelt. In der Graphik ist eine beliebige Belegung angenommen. Möchte man nun wissen, welchen Status beispielsweise das Bit3 besitzt, d.h. welcher Eingangswert an diesem Pin anliegt, verwendet man das Maskierungs-Prinzip in dem als man als Maske ein Byte mit dem Wert 1 nimmt und dieses einer shift left -Operation um genau die Anzahl von Schritten unterzieht, die der Position des Bits entspricht, von dem man den Status wissen möchte.

Eine weitere Möglichkeit besteht darin, eine einfache UND-Verknüpfung zu verwenden, und das Ergebnis einer Variable vom Typ boolean zuzuweisen. Über diese lässt sich dann mit einfachen if-then-Abfragen der Programmlauf entsprechend beeinflussen.

Man erkennt an diesem Beispiel sehr gut, welche Möglichkeiten in der Port-Manipulation steckt. Wenn man sich etwas näher mit den Bit-Manipulations Möglichkeiten der Sprache C vertraut gemacht hat, kann man damit sehr effiziente Programme schreiben.