CHF | EUR | USD

Argumente für die direkte Port-Manipulation

Da die Programmierung für die Massen immer zugänglicher wird, können einige grundlegende, aber nützliche Codiertechniken auf der Strecke bleiben oder vielleicht ganz vergessen werden, wie z.B. die direkte Portmanipulation (DPM). Manche Programmierer argumentieren, dass diese Befehle schwer zu lesen sind und für Anfänger verwirrend sein können, weswegen sie niemals mit vertrauteren Strukturen vermischt werden sollten. Doch warum sollte man nicht alle verfügbaren Tools nutzen, um den Code präzise und effizient zu gestalten?

Beispielsweise benötigt jeder Port eines 8-Bit-Mikrocontrollers drei 8-Bit-Register, um die Port-Aktivität zu steuern. Bei diesen Registern handelt es sich um das DDRx zur Festlegung von Eingang und Ausgang, das PORTx zur Steuerung des Pin-Logikpegels und das PINx mit den aktuellen Pin-Zuständen des Ports. Werfen wir einen Blick auf Port C. Beim Setup möchte ein Programmierer die Pins von Port C vielleicht als Ausgänge festlegen und zu Beginn auf LOW-Pegel setzen. Häufig wird hierfür eine übersichtliche „for-Schleife“ mit 3 oder 4 Zeilen sich wiederholendem Code geschrieben, der die Pins null bis sieben abarbeitet und dabei insgesamt 32 Zeilen geschriebenen Code ausführt. Durch das Einbinden von Bibliotheksfunktionen in die Schleife, wie beispielsweise pinMode() und digitalWrite(), werden mehr im Hintergrund ablaufende Aktivitäten aufgerufen, die zu einer Fülle an zusätzlich ausgeführtem Code führen.

Schließlich werden die acht Bits des Richtungsregisters für Port C (DDRC) auf HIGH-Pegel gesetzt und die acht Bits des Registers für den Logikpegel von Port C (PORTC) auf LOW-Pegel. Genau das lässt sich auch dadurch erreichen, dass man die Bits mithilfe der folgenden direkten Registerbefehle für den Port auf HIGH oder LOW setzt:

DDRC = 0xFF; //Pins von Port C als Ausgänge festlegen (Binär: DDRD = 0b11111111;)

PORTC = 0x00; //Pins von Port C auf LOW-Pegel setzen (Binär: PORTC = 0b00000000;)

Die Pins von Port A sollen als Eingänge festgelegt werden?

DDRA = 0x00; //Pins von Port A als Eingänge festlegen (Binär: DDRA = 0b00000000;)

Und schließlich sollen die Pins von Port D gemischt als Ein- und Ausgänge festgelegt werden:

DDRD = 0x0F; //Die oberen vier Bits von Port D als Eingänge und die unteren vier Bits als Ausgänge festlegen (Binär: DDRD = 0b00001111);

Die Verwendung eines Hexadezimalwerts wie 0xFF gibt einen interessanten Einblick in die Zuweisung von Bit-Werten im Register. Hierbei ist „0x“ der Hexadezimal-Indikator, der erste Wert „F“ repräsentiert die oberen vier Bits eines 8-Bit-Registers und der zweite Wert „F“ die unteren vier Bits.

Alle möglichen Kombinationen aus auf HIGH oder LOW gesetzten Bits können durch eine hexadezimale oder binäre Zeichenfolge (Zahl) dargestellt werden. Die Verwendung binärer Zeichenfolgen ist visueller, da alle acht Bits während des Schreibens des Codes sichtbar sind. Beide Varianten sind akzeptabel, sofern sie vom Compiler unterstützt werden. Allerdings ist die hexadezimale Schreibweise kürzer und wird daher häufiger verwendet.

Hinweis: Die Verwendung binärer Zeichenfolgen ist kein universeller Standard in C/C++.

Gehen wir noch einen Schritt weiter

Nachdem die Ports A und C konfiguriert sind, sind nur noch wenige Zeilen zusätzlicher Code mit DPM in der Hauptschleife erforderlich, um digitale Geräte zu lesen und andere digitale Geräte zu steuern. In der Praxis können zwei Schrittmotoren mit Endschaltern, die über einen digitalen Joystick gesteuert werden, problemlos mit einer minimalen Anzahl an Registerbefehlen und einer Lookup-Tabelle programmiert werden. Abbildung 1 zeigt das Hardware-Setup für dieses Szenario.

Abbildung 1: Das Hardware-Setup für Joystick/Schrittmotoren mit markierten Ports A und C des Mikrocontrollers. (Bildquelle: Digi-Key Electronics)

Hardware:

Ein einzelner digitaler Joystick, dessen vier mit VCC verbundenen Arbeitskontakte auch mit den Pins 0 bis 3 von Port A verbunden sind. Die Ausgänge sind am Mikrocontroller auf LOW gezogen. Wenn die Kontakte schließen, werden die Pins des Ports auf HIGH gesetzt. Die Kontakte stehen für die Bewegungsrichtungen AUF, AB, LINKS und RECHTS und sind so konfiguriert, dass zwei benachbarte Kontakte gleichzeitig aktiviert werden können. Hierdurch ergeben sich acht mögliche Kombinationen für den Schalterausgang. Ein neunter Ausgang steht für ALLE ANHALTEN. In diesem Fall ist der Joystick zentriert und alle Kontakte sind offen.

Die Arbeitskontakte von vier Endschaltern sind sowohl mit der Masse als auch mit den verbleibenden Pins von Port A verbunden, die der Joystick-Konfiguration für AUF, AB, LINKS und RECHTS entsprechen. Die Schalterausgänge sind am Mikrocontroller auf HIGH gezogen. Wenn die Kontakte schließen, werden die Pins auf LOW gesetzt.

Die Schrittmotor-Treiberkarten erzeugen eine mechanische Bewegung, indem sie drei Steuer-Pins zwischen HIGH und LOW umschalten, um die Schritt-, Richtungs- und Haltefunktion zu erleichtern. Für dieses Beispiel betreiben alle acht Bits von Port C zwei Treiber, obwohl zwei Pins nicht verwendet werden.

Programmierung:

Um den korrekten Ausgang für die Treiber zu generieren, wird eine Lookup-Tabelle verwendet, um die vier Joystick-Bits in die acht Treiber-Bits umzuwandeln. Eine einzelne Zeile Code in der Hauptschleife ruft die Funktion „get_output()“ auf und übermittelt den Inhalt des PINA-Registers an die Funktion. Der von der Funktion zurückgegebene Wert wird direkt in das PORTC-Register geschrieben:

PORTC = get_output(PINA);

In der Funktion wird mit „lookup_output[ ]“ auf die Lookup-Tabelle zugegriffen und ein indizierter Wert wird zurückgegeben. In dieser Funktion geschieht jedoch noch etwas anderes im Zusammenhang mit den Endschaltern. Der indizierte Wert aus der Lookup-Tabelle in den eckigen Klammern der Funktion „lookup_output[ ]“ wird durch einen Ausdruck für Bit-Shifting und -Masking dargestellt. Die hieraus resultierende Indexvariable ist das bitweise AND der oberen 4 Bits des Eingangsregisters (Endschalterwerte) und der unteren 4 Bits (Joystick-Werte). Wenn ein beliebiger Endschalterkontakt schließt und bei einem beliebigen der vier oberen Bits ein Nullwert vorhanden ist, wird das entsprechende untere Bit gelöscht.

Hinweis: Da vier Bits 16 einzigartige Bit-Kombinationen ergeben können, werden die sieben nicht verwendeten Indexpositionen in der Lookup-Tabelle in 0x00 umgewandelt, um Fehler zu vermeiden.

Kopierenuint8_t get_output(uint8_t porta_val)
{
  return lookup_output[(porta_val >> 4) & (porta_val & 0x0F)];         
}

Beispiel:

Befindet sich der Joystick in der Position AUF und RECHTS und sind alle Endschalter offen, ergibt sich für das PINA-Register der Binärwert 0b11111001 (oder 0xF9 in Hexadezimalschreibweise), der an die Funktion übergeben wird. Die Funktion „eliminiert“ die oberen vier Bits mithilfe eines bitweisen AND von 0b00001111 (0x0F), wodurch sich 0b00001001 (0x09) für den Indexwert in der Lookup-Tabelle ergibt. Vergleicht man dieses Ergebnis mithilfe eines weiteren bitweisen AND mit dem verschobenen Endschalter-Statuswert 0b00001111 (0x0F), bleibt der vorherige Wert unverändert und gibt 0b00001001 (0x09) als abschließenden Indexwert zurück, der auf 0b00100001 (0x21) in der Lookup-Tabelle zeigt. Das ist die Umwandlung des Schrittmotortreibers für AUF und RECHTS. Siehe Abbildung 2.

Abbildung 2: Die Auswertung des Mikrocontrollers der Eingänge von Port A und Port C, wenn sich der Joystick in der Position AUF und RECHTS befindet. (Bildquelle: Digi-Key Electronics)

Wäre der AUF-Endschalter erreicht worden und hätte sich dadurch ein Nullbit ergeben, wäre der verschobene Wert 0b00000111 (0x07) anstelle von 0b00001111 (0x0F). Dies würde den entsprechenden AUF-Joystick-Wert negieren, wodurch sich ein abschließender Indexwert von 0b00000001 (0x01) anstelle von 0b00001001 (0x09) ergeben würde. Der umgewandelte Wert für 0b00000001 (0x01) ist 0x26 in der Lookup-Tabelle. Das ist die Umwandlung des Schrittmotortreibers nur für RECHTS. Siehe Abbildung 3.

Abbildung 3: Die Auswertung des Mikrocontrollers der Eingänge von Port A und Port C, wenn sich der Joystick in der Position AUF und RECHTS befindet, während gleichzeitig der AUF-Endschalter ausgelöst wird. (Bildquelle: Digi-Key Electronics)

Fazit

Wenn sich ein Programmierer nicht sicher ist, ob die DPM ein brauchbares Tool für das Konfigurieren, Lesen oder Schreiben von Port-Daten darstellt, dann spricht eine beträchtliche Minimierung des benötigten Codes doch sehr dafür. Dieselben Operationen mit standardmäßigen Bibliotheksfunktionen erfordern sehr viel mehr Code und können einen beträchtlichen Teil des verfügbaren Programmspeichers belegen. Der unten angegebene Code belegt weniger als 1 % des Speichers des am Prüfstand verwendeten Mikrocontrollers ATMEGA328P. Das richtige Kommentieren des Codes ist wichtig für das Verstehen der DPM-Funktion im geschriebenen Code und erleichtert Programmierern mit beliebigem Kenntnisstand ihre Verwendung und das Debuggen.

Hardwarebeispiele von Digi-Key:

Schrittmotoren – https://www.digikey.com/short/pdnfp4

Schrittmotor-Controller – https://www.digikey.com/short/pdnf4r

Joystick – https://www.digikey.com/short/pdnf57

Endschalter – https://www.digikey.com/short/pdnfwm

Beispielcode:

Kopierenconst uint8_t lookup_output[16] = {
0x09,   //Index 0  All Stop. Apply hold current
0x26,   // Index 1 Right			 
0x34,   // Index 2 Left
0x00,   // Index 3 Unused
  	0x36,   // Index 4 Down
  	0x0C,   // Index 5 Down/Right
  	0x31,   // Index 6 Down/Left
  	0x00,   // Index 7 Unused
  	0x24,   // Index 8 Up
  	0x21,   // Index 9 Up/Right
  	0x0E,   // Index 10 Up/Left
  	0x00,   // Index 11 Unused
  	0x00,   // Index 12 Unused
  	0x00,   // Index 13 Unused
  	0x00,   // Index 14 Unused
  	0x00    // Index 15 Unused
};

void setup() {
     // Set all bits in port A direction register as INPUTs; 
    // Limits (up, down, left, right) Joystick (up, down, left, right)
    DDRA = 0x00;

    // Set all bits of port C direction register as OUTPUTs;
   // Motor control (Not Used, Mot_1, Dir_1, En_1, Not Used, Mot_2, Dir_2, En_2
   DDRC = 0xFF; 
}

void loop() {
    //Send the port A values to the function. Write the return value to port C.
   PORTC = get_output(PINA);   
}


/***** Input Value Translation Function *******/ 
uint8_t get_output(uint8_t porta_val)
{
    // Compare the limit switch and joystick values. Retrieve and return the translated value.
    return lookup_output[(porta_val >> 4) & (porta_val & 0x0F)];    
}

Über den Autor

Image of Don Johanneck

Don Johanneck, Technical Content Developer bei Digi-Key Electronics, arbeitet seit 2014 für das Unternehmen. Seit dem Wechsel in seine aktuelle Position ist er für das Verfassen von Beschreibungen zu Videos und Produkten verantwortlich. Er hat seinen Abschluss als Associate of Applied Science im Bereich Elektronik und automatisierte Systeme vom Northland Community & Technical College im Rahmen des Digi-Key-Stipendienprogramms erworben. Er genießt die Modellierung von Funksteuerungen, die Restaurierung von alten Maschinen und das Basteln.

More posts by Don Johanneck