Mo. Jul 15th, 2024

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 wichtigsten Bit-Manipulationsoperationen in der Sprache C 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.

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 Abbildung zu sehen. Hier erkennt man, dass die Pins nicht einzel angesteuert werden, sondern in mehrere Ports zusammengefasst sind.

Port B (digital pin 8 to 13)

Port C (analog input pins)

Port D (digital pins 0 to 7)

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.

Wenn über Standardfunktionen wie z.B. digitalRead() sieht es für den Benutzer so aus, als würde er nur auf den von ihm gefragten Pin zugreifen und den Zustand entweder abfragen oder ändern. Im Hintergrund werden aber bei jeder dieser Operationen die Pins eines gesamten Ports angesprochen und dann die entsprechenden Ports so manipuliert, dass es so aussieht, als könnte man einen eInzelnen Pin adressieren. Daher benötigt ein DigitalWrite() Aufruf um einen einzelnen Pin auf HIGH oder LOW zu setzen bis zu 50 mal länger, als die direkte Port-Manipulation.

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 D 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.

Beim AT Mega kommen dem selben Prinzip folgend noch zusätzliche Ports hinzu. Hier die Belegungen die ich bisher am meisten verwendet habe habe:

    //   ----    Arduino MEGA Portbelegung    ----

    //        Bit7| Bit6| Bit5| Bit4| Bit3| Bit2| Bit1| Bit0 
    // PortA:  P29 | P28 | P27 | P26 | P25 | P24 | P23 | P22 
    // PortL:  P42 | P43 | P44 | P45 | P46 | P47 | P48 | P49
    // PortF:  A7  | A6  | A5  | A4  | A3  | A2  | A1  | A0 
    // PortK:  A15 | A14 | A13 | A12 | A11 | A10 | A9  | A8
    

Aber es gibt noch weitere Ports bei AT Mega, die ich hier mal versucht habe in einer Übersicht darzustellen:

Port-Belegungen des Arduino AT Mega

Praktische Anwendung am Beispiel des Arduino Uno

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:

void setup(){
  DDRB = B11111111; // alle Bits als Ausgang
}
void loop (){
  PORTB = B10101010; // Wechselblinker mit allen Ausgaengen
  delay(300);
  PORTB = B01010101;
  delay(300);
}
//Weitere Beispiele:
DDRD = 0B11111111; // Alle PINs im Port D sind Output
PORTD = 0B11111111; // Alle PINs sind HIGH