Pfad: Home => AVR-Überblick => Programmiertechniken => Ports

Programmiertechnik für Anfänger in AVR Assemblersprache

Was ist ein Port?

Ports sind eigentlich ein Sammelsurium verschiedener Speicher. In der Regel dienen sie der Kommunikation mit irgendeiner internen Gerätschaft wie z.B. den Timern oder der Seriellen Schnittstelle oder der Bedienung von äußeren Anschlüssen wie den Parallel-Schnittstellen des AVR. Der wichtigste Port wird weiter unten besprochen: das Status-Register, das an das wichtigste interne Gerät, nämlich den Akkumulator, angeschlossen ist.

Port-Organisation

Es gibt insgesamt 64 direkt adressierbare Ports, die aber nicht bei allen AVR-Typen auch tatsächlich physikalisch vorhanden sind. Bei größeren ATmega gibt es neben den direkt adressierbaren Ports noch indirekt adressierbare Ports, doch dazu später mehr.

Je nach Größe und Ausstattung des Typs sind eine Reihe von Ports sinnvoll ansprechbar. Welche der Ports in welchem Typ tatsächlich vorhanden sind, ist letztlich aus den Datenblättern zu erfahren. Hier ein Ausschnitt aus den Ports des ATmega8 (von ATMEL zur allgemeinen Verwirrung auch "Register" genannt:

Ports ATmega8

Ports haben eine feste Adresse (in dem oben gezeigten Ausschnitt z. B. 0x3F für den Port SREG, 0x steht dabei für hexadezimal!), über die sie angesprochen werden können. Die Adresse gilt teilweise unabhängig vom AVR-Typ, teilweise aber auch nicht. So befindet sich das Statusregister SREG immer an der Adresse 0x3F, der Ausgabeport der Parallelschnittstelle B immer an der Portadresse 0x18. Ports nehmen oft ganze Zahlen auf, sie können aber auch aus einer Reihe einzelner Steuerbits bestehen. Diese einzelnen Bits haben dann eigene Namen, so dass sie mit Befehlen zur Bitmanipulation angesteuert werden können. In der Portliste hat auch jedes Bit in dem jeweiligen Port seinen speziellen symbolischen Namen, z. B. hat das Bit 7 im Port "GICR" den symbolischen Namen "INT1".

Port-Symbole, Include

Diese Adressen und Bit-Nummern muss man sich aber nicht merken. In den Include-Dateien zu den einzelnen AVR-Typen, die der Hersteller zur Verfügung stellt, sind die jeweiligen verfügbaren Ports und ihre Bits mit wohlklingenden Namen belegt. So ist in den Include-Dateien die Assemblerdirektive

.EQU PORTB, 0x18

angegeben und wir müssen uns fürderhin nur noch merken, dass der Port B PORTB heißt. Für das Bit INT1 im Port GICR ist in der Include-Datei definiert:

.EQU INT1, 7

Die Include-Datei des AVR ATmega8515 heißt "m8515def.inc" und kommt mit folgender Direktive in die Quellcode-Datei:

.INCLUDE "m8515def.inc"
oder, wenn man nicht mit dem Studio arbeitet,
.INCLUDE "C:\PfadNachIrgendwo\m8515def.inc

und alle für diesen Typ bekannten Portregister und Steuerbits sind jetzt mit ihren symbolischen Alias-Namen leichter ansprechbar.

Ports setzen

In Ports kann und muss man Werte schreiben, um die betreffende Hardware zur Mitarbeit zu bewegen. So enthält z.B. das MCU General Control Register, genannt MCUCR, eine Reihe von Steuerbits, die das generelle Verhalten des Chips beeinflussen (siehe im Ausschnitt oben bzw. die Beschreibung des MCUCR im Detail). MCUCR ist ein mit Einzelbits vollgepackter Port, in dem jedes Bit noch mal einen eigenen Namen hat (ISC00, ISC01, ...). Wer den Port benötigt, um den AVR in den Tiefschlaf zu versetzen, muss sich im Typenblatt die Wirkung dieser Sleep-Bits heraussuchen und durch eine Folge von Instruktionen die entsprechende einschläfernde Wirkung programmieren, für den ATmega8 also z.B. so: ...

.DEF MeinLieblingsregister = R16
    LDI MeinLieblingsregister, 0b10000000
    OUT MCUCR, MeinLieblingsregister
    SLEEP


Der Out-Befehl bringt den Inhalt meines Lieblingsregisters, nämlich ein gesetztes Sleep-Enable-Bit SE, zum Port MCUCR und versetzt den AVR gleich und sofort in den Schlaf, wenn er im ausgeführten Code auf eine SLEEP-Instruktion trifft. Da gleichzeitig alle anderen Bits mitgesetzt werden und mit Sleep-Mode SM=0 als Modus der Halbschlaf eingestellt wurde, geht der Chip nicht völlig auf Tauchstation. In diesem Zustand wird die Befehlsausführung eingestellt, die Timer und andere Quellen von Interrupts bleiben aber aktiv und können den Halbschlaf jederzeit unterbrechen, wenn sich was Wichtiges tut.

Ports transparenter setzen

Weil "LDI MeinLieblingsregister, 0b10000000" eine ziemlich intransparente Angelegenheit ist, weil zum Verständnis dafür, was eigentlich hier gemacht wird, ein Blick in die Portbits im Datenblatt nötig ist, schreibt man dies besser so:

    LDI MeinLieblingsRegister, 1<<SE

"1<<SE" nimmt eine binäre Eins (= 0b00000001) und schiebt diese SE mal links (<<). SE ist im Falle des ATmega8 als 7 definiert, also die 1 sieben mal links schieben. Das Ergebnis (= 0b10000000) ist gleichbedeutend mit dem oben eingefügten Bitmuster und setzt das Bit SE im Port MCUCR auf Eins. Wenn wir gleichzeitig auch noch das Bit SM0 auf 1 setzen wollen (was einen der acht möglichen Tiefschlafmodi einschaltet, dann wird das so formuliert:

    LDI MeinLieblingsRegister, (1<<SE) | (1<<SM0)

Der vertikale Strich zwischen beiden Teilergebnissen ist ein binäres ODER, d. h. dass alle auf Eins gesetzten Bits in einem der beiden Teilausdrücke in Klammern Eins werden, also sowohl das Bit SE (= 7) als auch das Bit SM0 (= 4) gesetzt sind, woraus sich 0b10010000 ergibt.

Noch mal zum Merken: die Linksschieberei wird nicht im Prozessor vollzogen, nur beim Assemblieren. Die Instruktion LDI wird auch nicht anders übersetzt, wenn wir die Schieberei verwenden, und ist auch im Prozessor nur eine einzige Instruktion. Von den Symbolen SE und SM0 hat der Prozessor selbst sowieso keine Ahnung, das ist alles nur für den Quellcode-Schreiber von Bedeutung.

Die Linksschieberei hat den immensen Vorteil, dass nun für jeden aufgeklärten Menschen sofort erkennbar ist, dass hier die Bits SE und SM0 manipuliert werden. Nicht dass jeder sofort wüsste, dass damit der Schlafmodus eingestellt wird, aber es liegt wenigstens in der Assoziation näher als 0b10010000.

Noch ein Vorteil: das SE-Bit liegt bei anderen Prozessoren woanders im Port MCUCR als in Bit 7, z. B. im AT90S8515 lag es in Bit 5. SM0 ist bei diesem Typ gar nicht bekannt, bei einer Portierung unseres Quellcodes für den ATmega8 auf diesen Typ kriegen wir dann eine Fehlermeldung (SM0 ist dort nicht definiert) und wir wissen dann sofort, wo wir suchen und nacharbeiten müssen.

Ports lesen

Umgekehrt lassen sich die Portinhalte mit dem IN-Befehl in beliebige Register einlesen und dort weiterverarbeiten. So lädt

.DEF MeinLieblingsregister = R16
    IN MeinLieblingsregister, MCUCR


den lesbaren Teil des Ports MCUCR in das Register R16. Den lesbaren Teil deswegen, weil es bei vielen Ports auch nicht belegte Bits gibt, die dann immer als Null eingelesen werden.

Oft will man nur bestimmte Portbits setzen und die Porteinstellung ansonsten so lassen. Das geht mit Lesen-Ändern-Schreiben (Read-Modify-Write) z. B. so:

    IN MeinLieblingsregister, MCUCR
    SBR MeinLieblingsRegister, (1<<SE) | (1<<SM0)
    OUT MCUCR,MeinLieblingsRegister


Braucht aber halt drei Instruktionen.

Auf Portbits reagieren

Noch öfter als ganze Ports einlesen muss man auf Änderungen bestimmter Bits der Ports prüfen. Dazu muss nicht der ganze Port gelesen und verarbeitet werden. Es gibt hierfür spezielle Sprungbefehle, die aber im Kapitel Springen vorgestellt werden. Umgekehrt kommt es oft vor, dass ein bestimmtes Portbit gesetzt oder rückgesetzt werden muss. Auch dazu braucht man nicht den ganzen Port lesen und nach der Änderung im Register dann den neuen Wert wieder zurückschreiben. Die beiden Befehle heissen SBI (Set Bit I/O-Register) und CBI (Clear Bit I/O-Register). Ihre Anwendung geht z.B. so:

.EQU Aktivbit=0 ; Das zu manipulierende Bit des Ports
    SBI PortB, Aktivbit ; Das Bit wird Eins
    CBI PortB, Aktivbit ; Das Bit wird Null

Die beiden Instruktionen haben einen gravierenden Nachteil: sie lassen sich nur auf Ports bis zur Adresse 0x1F anwenden, für Ports darüber sind sie leider unzulässig. Für Ports oberhalb des mit IN und OUT beschreibbaren Adressraums geht das natürlich schon gar nicht.

Porteinblendung im Speicherraum

Für den Zugang zu den Ports, die im nicht direkt zugänglichen Adressraum liegen (z. B. bei einigen großen ATmega und ATxmega) und für den Exotenprogrammierer gibt es wie bei den Registern auch hier die Möglichkeit, die Ports wie ein SRAM zu lesen und zu schreiben, also mit dem LD- bzw. der ST-Instruktion. Da die ersten 32 Adressen im SRAM-Speicherraum schon mit den Registern belegt sind, werden die Ports mit ihrer um 32 erhöhten Adresse angesprochen, wie z.B. bei

.DEF MeinLieblingsregister = R16
    LDI ZH,HIGH(PORTB+32)
    LDI ZL,LOW(PORTB+32)
    LD MeinLieblingsregister,Z


Das macht nur im Ausnahmefall einen Sinn, geht aber halt auch. Es ist der Grund dafür, warum das SRAM erst ab Adresse 0x60 beginnt (0x20 für die Register, 0x40 für die Ports reserviert), bei großen ATmega erst bei 0x100.

Zum Seitenanfang

Details wichtiger Ports in den AVR

Die folgende Tabelle kann als Nachschlagewerk für die wichtigsten gebräuchlichsten Ports im AT90S8515 dienen. Sie enthält nicht alle möglichen Ports. Insbesondere die Ports der MEGA-Typen und der AT90S4434/8535 sind der Übersichtlichkeit halber nicht darin enthalten! Bei Zweifeln immer die Originaldokumentation befragen!  
GerätLinkRegisterLink
AkkumulatorSREG Status RegisterSREG
StackSPL/SPH StackpointerSPL/SPH
Ext.SRAM/
Ext.Interrupt
MCUCR MCU General Control RegisterMCUCR
Ext.Int.INT Interrupt Mask RegisterGIMSK
Flag RegisterGIFR
Timer InterruptsTimer Int. Timer Int Mask RegisterTIMSK
Timer Interrupt Flag RegisterTIFR
Timer 0Timer 0 Timer/Counter 0 Control RegisterTCCR0
Timer/Counter 0TCNT0
Timer 1Timer 1 Timer/Counter Control Register 1 ATCCR1A
Timer/Counter Control Register 1 BTCCR1B
Timer/Counter 1TCNT1
Output Compare Register 1 AOCR1A
Output Compare Register 1 BOCR1B
Input Capture RegisterICR1L/H
Watchdog TimerWDT Watchdog Timer Control RegisterWDTCR
EEPROMEEPROM EEPROM Adress RegisterEEAR
EEPROM Data RegisterEEDR
EEPROM Control RegisterEECR
SPISPI Serial Peripheral Control RegisterSPCR
Serial Peripheral Status RegisterSPSR
Serial Peripheral Data RegisterSPDR
UARTUART UART Data RegisterUDR
UART Status RegisterUSR
UART Control RegisterUCR
UART Baud Rate RegisterUBRR
Analog ComparatorANALOG Analog Comparator Control and Status RegisterACSR
I/O-PortsIO-Ports   


Zum Seitenanfang

Das Statusregister als wichtigster Port

Der am häufigste verwendete Port für den Assemblerprogrammierer ist das Statusregister mit den darin enthaltenen acht Bits. In der Regel wird auf diese Bits vom Programm aus nur lesend/auswertend zugegriffen, selten werden Bits explizit gesetzt (mit dem Assembler-Befehl SEx) oder zurückgesetzt (mit dem Befehl CLx). Die meisten Statusbits werden von Bit-Test, Vergleichs- und Rechenoperationen gesetzt oder rückgesetzt und anschliessend für Entscheidungen und Verzweigungen im Programm verwendet.

Die folgende Tabelle enthält eine Liste der Assembler-Instruktionen, die die jeweiligen Status-Bits beeinflussen.
BitRechnenLogikVergleichBitsSchieben Sonstige
ZADD, ADC, ADIW, DEC, INC, SUB, SUBI, SBC, SBCI, SBIW AND, ANDI, OR, ORI, EOR, COM, NEG, SBR, CBR CP, CPC, CPI BCLR Z, BSET Z, CLZ, SEZ, TST ASR, LSL, LSR, ROL, ROR CLR
CADD, ADC, ADIW, SUB, SUBI, SBC, SBCI, SBIW COM, NEG CP, CPC, CPI BCLR C, BSET C, CLC, SEC ASR, LSL, LSR, ROL, ROR -
NADD, ADC, ADIW, DEC, INC, SUB, SUBI, SBC, SBCI, SBIW AND, ANDI, OR, ORI, EOR, COM, NEG, SBR, CBR CP, CPC, CPI BCLR N, BSET N, CLN, SEN, TST ASR, LSL, LSR, ROL, ROR CLR
VADD, ADC, ADIW, DEC, INC, SUB, SUBI, SBC, SBCI, SBIW AND, ANDI, OR, ORI, EOR, COM, NEG, SBR, CBR CP, CPC, CPI BCLR V, BSET V, CLV, SEV, TST ASR, LSL, LSR, ROL, ROR CLR
SSBIW - - BCLR S, BSET S, CLS, SES - -
HADD, ADC, SUB, SUBI, SBC, SBCI NEG CP, CPC, CPI BCLR H, BSET H, CLH, SEH - -
T- - - BCLR T, BSET T, BST, CLT, SET - -
I- - - BCLR I, BSET I, CLI, SEI - RETI


Zum Seitenanfang

©2002-2009 by http://www.avr-asm-tutorial.net