Bauen und Programmieren von AVR-Mikrocontrollern am Beispiel von ATMEL ATTiny 2313 und ATMega 8
Die Lade-, Vergleichs- und Arithmetikbefehle für Konstanten funktionieren nur für die Universalregister R16 bis R31 (0x10 bis 0x1F). Für die unteren Register R0 bis R15 muss man zum Laden den Umweg über ein oberes Register gehen. Vergleichs- und Arithmetikbefehle für die unteren Register arbeiten auch nur mit einem anderen Register, niemals aber mit Konstanten. Es gibt übrigens keinen Additionsbefehl für Konstanten (für eine Bytebreite). Konstrukte dieser Art müssen durch die Subtraktion der konstanten Gegenzahl umschrieben werden. Die Additions- und Subtraktionsbefehle für Konstanten in Wortbreite (= 2 Byte = 16 Bit) arbeiten nur für die obersten 4 Registerpaare (R24:R25; R26:R27; R28:R29 und R30:R31) und nur für vorzeichenlose Konstanten von 0 bis 63. Dabei ist immer die durch 2 teilbare Registernummer im Befehl als Parameter zu nennen.
Der CLR-Befehl funktioniert für alle allgemeinen Register R0 bis R31.
geht | geht nicht | Ersatz |
LDI R16,0x4A | LDI R5, 0x4A |
LDI R16, 0x4A |
CPI R16, 0xF8 | CPI R5, 0xF8 | LDI R16, 0xF8 CP R5, R16 |
ANDI R16, 0b10011101 | ANDI R5, 0b10011101 | LDI R16, 0b10011101 AND R5, R16 |
... | ... | ... |
SUBI R16, 135 | SUBI R5, 135 | LDI R16, 135 SUB R5, R16 |
ADD R16, R17 | ADDI R16, 209 | SUBI R16, -209 oder LDI R17, 209 ADD R16, R17 |
CLR R10 | - | - |
ADIW R24, 42 | ADIW R12, 42 ADIW R28, 86 ADIW R27, 0x08 |
- |
SBIW R30, 1 | SBIW R0, 75 | - |
Bitte ein Bit - oder ein Byte?
Bei manchen Befehlen ist als Parameter die Nummer eines Bits gefragt, bei anderen
der Wert eines Bytes. Leider ist die Syntax zum Teil verwirrend. Besonders bei
den rot hinterlegten Befehlen ist Vorsicht geboten. Während bei SBI und
CBI der Assembler wenigstens registriert, wenn der Parameter mit der Bitnummer
größer 7 ist, gibt es bei SBR und CBR keinerlei Fehlermeldung. Der
Befehl
SBR R16, 5
wird klaglos geschluckt, jedoch an Stelle von:
SBR R16, 0b00100000
interpretiert als:
SBR R16, 0b00000101
und das ist total daneben!
Zu Fehlermeldungen (unzulässiger Bereich) führen auch Bit-Befehle, die I/O-Register im oberen Bereich (ab 0x20) ansprechen. Die Register von 0x20 bis 0x3F müssen daher mit Byteoperationen bedient werden.
Bitorientierte Befehle
SBI: Set Bit in IO-Register CBI: Clear Bit in IO-Register |
SBI PORTD,5 | Setze das Bit Nummer 5 in Port D auf 1 Es kann nur ein Bit pro Befehl beeinflusst werden |
SBRC: Skip next command if Bit in Register clear SBRS: Skip next command if Bit in Register set |
SBRC R16, 3 |
Wenn im Register R16 das Bit Nummer 3 gelöscht ist, wird der RJMP-Befehl übersprungen und mit dem Ausgabebefehl weiter gemacht. |
SBIC Skip next command if Bit in IO-Register clear SBIS Skip next command if Bit in IO-Register set |
SBIS DDRD, 3 RJMP weiter OUT PORTD, R16 |
Wenn in DDRD das Bit Nummer 3 gesetzt ist, wird der RJMP-Befehl übersprungen und mit dem Ausgabebefehl weiter gemacht. |
Die Skip-Befehle können logischerweise nur bitorientiert arbeiten, weil keinen Sinn macht, mehrere gesetzte Bits auf einmal abzufragen. Allerdings geht das nur bei den unteren IO-Registern 0x00 bis 0x1F und bei allen allgemeinen Registern.
Byteorientierte Befehle
SBR: Set Bits in Register CBR: Clear Bits in Register |
SBR R16, 0b00100000 CBR R17, 0b01100100 |
Setze das Bit Nummer 5 in Register R16 auf 1 Setze das Bit Nummer 5 in Register R17 auf 0 Mit einem Befehl können mehrere Bits gesetzt/rückgesetzt werden. |
IN R16, TCCR1B ANDI R16, 0b11111000 ORI R16, 1<<CS11 OUT TCCR1B, R16 |
Mit einem Befehl können mehrere Bits gesetzt/rückgesetzt werden. |
Byteorientierte Befehle müssen zum Setzen und Rücksetzen von Bits in allen allgemeinen und den höheren IO-Registern (0x20 bis 0x3F) verwendet werden.
Falsche Logik
Die Befehle SBRC, SBRS, SBIC, SBIS werden meistens in Verbindung mit einem Sprungbefehl
RJMP eingesetzt. Mit der ganzen Springerei und Überspringerei kann sich
recht schnell ein Logikfehler einschleichen. Solche Fehler kann man nur vermeiden,
wenn man die mit diesem Konstrukt verbundene Wenn-Dann-Sonst-Struktur gut durchdenkt
und jeden Fall konkret durchspielt.
Die folgenden Ablaufdiagramme zeigen die Verwendung der Skip-Befehle im Zusammenhang mit der einseitigen und zweiseitigen Alternative.
Auf den SBRS-Befehl folgt immer die RJMP-Anweisung, die im linken Fall den Dannteil überspringt. Im rechten Fall überspringt die RJMP-Anweisung den Dann-Teil zum Sont-Teil, der in die Marke "weiter" mündet. Der Dannteil wird abgearbeitet, wenn der SBRS-Befehl die erste RJMP-Anweisung skipt (=überspringt). Am Ende des Dann-Teils muss ein unmittelbarer Sprung hinter den Sonst-Teil zur Marke "weiter" erfolgen.
Register sichern
Dass die in einer Prozedur verwendeten Register auf dem Stack gesichert werden
müssen, sieht jeder ein. Dass dieses besonders bei Interruptserviceroutinen
(ISR) nötig ist leuchtet auch ein. Was man gerne vergisst, ist das Statusregister
SREG, in dem die Bits untergebracht sind, welche bei arithmetischen, logischen
und Vergleichsoperationen gesetzt werden um den weiteren Programmablauf zu steuern.
Wird dieses Register in einer ISR benutzt, die z. B. einen Vergleichsbefehl
(CP, CPI, TST, etc)enthält, dann werden im SREG Bits gesetzt oder rückgesetzt
und in diesem veränderten Zustand an das aufrufende Programm übergeben,
wenn die ISR zu ende ist. Folgt jetzt im aufrufenden Programm ein Branch-Befehl
(BREQ, BRNE, BRSH ...) dann kann durch ein falsch gesetztes Statusbit das Programm
in eine nicht gewünschte Richtung gelenkt werden. Deshalb ist es nötig,
dass auch das SREG auf den Stack gerettet wird, falls die Procedur Statusbits
verändert. Das ist der Fall, wenn in der Procedur logische, arithmetische
oder Vergleichsbefehle explizit (AND, OR, ADD, SBC, CP) oder verdeckt (TST)
ausgeführt werden. Natürlich verändern auch Befehle wie CLC,
CLR, SEN, SEZ, INC, DEC das Statusregister. Keine Beeinflussung erfolgt durch
Lade- und Speicherbefehle (LD, ST, MOV..)
In dieser ISR wird zunächst der Inhalt von Register temp1 auf dem Stack gesichert um dann den Inhalt von SREG aufzunehmen. Dann wird temp1 mit dem Inhalt des Statusregisters ein weiteres Mal auf den Stack gesichert, danach temp2. Am Ende der ISR bei ana_comp_exit werden die Register in genau der umgekehrten Reihenfolge restauriert.
Ein netter Fehler, der gleich einen ganzen Rattenschwanz an Fehlermeldungen der folgenden Art hervorruft ist in diesem Programmtext versteckt.:
Nach dem Assemblieren tauchen folgende Meldungen auf:
Die Ursache ist der CLI-Befehl. Dieser Befehl existiert zwar, er löscht aber das globale Interruptflag und erwartet keinen Parameter. An seiner Stelle sollte eigentlich der ähnlich geschriebene Befehl CBI stehen, der das Löschen eines Bits in einem IO-Register zur Folge hat und daher die zwei Parameter testport und testpin erwartet.
Der entscheidende Hinweis auf die Fehlerursache steht ganz am Anfang der 29 Einträge enthaltenden Fehlerliste: "Wrong number of operands".
Sensibilisiert durch den Hinweis ruft man die zugehörige Programmzeile auf und erkennt hoffentlich sehr schnell den Schreibfehler.
Den CLR-Befehl habe ich schon weiter oben betrachtet. Er tut das, was der Name sagt für alle allgemeinen Register, er löscht alle Bits, setzt sie also auf 0.
Ganz daneben liegt der, welcher meint, der SET-Befehl würde nun alle Bits setzen. Tatsächlich wird genau ein Bit gesetzt und zwar ist das das T-Bit im Statusregister (SREG). Dieses T-Bit dient zusammen mit den Befehlen BLD und BST zum Transfer eines Bit von einem Register in ein anderes.
Das Gegenstück zum CLR-Befehl ist der SER-Befehl (Set Bits in Register), der aber bereits wieder eine Extrabehandlung braucht, weil er nur für die oberen allgemeinen Register (R16 bis R31) funktioniert.
Keine Reaktion beim Einlesen von einem Port
An einen Port angeschlossene Leitungen werdn mit einem IN-Befehl abgefragt, das ist klar. Essentiell ist aber auch, dass das Input-Register des Ports PINX (X steht für den Kennbuchstaben des Ports: B oder D beim Tiny 2313) eingelesen wird. Liest man z. B. von der Adresse PORTD statt von PIND, dann erhält man als Eingabewert das Byte, das im Ausgangsregister des Ports steht.
Wieso schafft die Leitung eines Ports es , meine LED, die ich über ein Darlingtonarray ansteuere, zum Leuchten zu bringen. Ich teste den Port ohne Beschaltung - aus: 0V - ein: 5V alles OK ~?:-(( Ich schließe den Darlingtoneingang an und messe - aus: 0V - ein: 2V - Mist, kapute Portleitung? :-((
Nein, ganz gemeiner Fehler! Das scheinbar korrekte Verhalten ohne Belastung des Ausgangs kommt daher, dass der Port als Eingang programmiert war und mit dem Einschalten des Portbits lediglich der interne Pullupwiderstand eingeschaltet wurde. Bei externer Belastung sinkt die Leerlaufspannung von 5V daher auf den niedrigeren Wert 2V, der zum Durchschalten des Darlingtontransistors nicht mehr reicht. Die LED bleibt dunkel. Nach dem Setzen des korrespondierenden Bits im DDR war die Leitung nun wirklich als Ausgang programmiert und die LED reagierte wie vorgesehen. ;-))
Was laufend vorkommt, wenn man im entscheidenden Moment abgelenkt ist oder wird: