(→Source-Code Muster) |
(→Source-Code Muster) |
||
Zeile 38: | Zeile 38: | ||
Das folgende Codebeispiel ist eine reine Basis-Initialisierung ohne irgendeine erkennbare Funktion. Für den Einstieg ist es am besten, das einfach abzuschreiben, wie es ist, und dann an den bezeichnenten Stellen mit eigenen Befehlen nach und nach zu erweitern. | Das folgende Codebeispiel ist eine reine Basis-Initialisierung ohne irgendeine erkennbare Funktion. Für den Einstieg ist es am besten, das einfach abzuschreiben, wie es ist, und dann an den bezeichnenten Stellen mit eigenen Befehlen nach und nach zu erweitern. | ||
− | |||
<pre> | <pre> | ||
.NOLIST ; List-Output unterdrücken | .NOLIST ; List-Output unterdrücken | ||
Zeile 50: | Zeile 49: | ||
;------------------------------------------------------ | ;------------------------------------------------------ | ||
RESET: | RESET: | ||
− | + | jmp INIT ; springen nach "INIT" | |
;------------------------------------------------------ | ;------------------------------------------------------ | ||
Zeile 91: | Zeile 90: | ||
rjmp Ende | rjmp Ende | ||
</pre> | </pre> | ||
+ | ===Der Ablauf der Übersetzung=== | ||
+ | Der Assembler ist ja selbst auch nur ein Programm und zu Beginn recht spartanisch ausgestattet: Er hat einen Daten-Buffer zur Verfügung, in den er den Maschinencode hineinschreiben soll, einen Pointer (Zeiger) dazu, damit er immer weiß, wo er grad ist, und eine allgemeine Tabelle, wo er Texte mit Zahlen verknüpfen kann. Alles ist zu Beginn leer bzw. auf NULL. | ||
+ | Mit diesen Voraussetzungen fängt er an, unseren Source-Text zeilenweise zu lesen. Und in jeder Zeile (die nicht leer ist) erwartet er eine Anweisung, was zu tun ist | ||
+ | ====PreCompiler Anweisungen==== | ||
+ | Das sind Anweisungen an den Assembler selbst und erzeugen keine Maschinenbefehle. Das ob. Source-Fragment hat auch gleich mit sowas begonnen | ||
+ | .NOLIST | ||
+ | Normalerweise schreibt der Assembler ja eine Übersetzungsliste. Nach dieser Anweisung läßt er das bleiben | ||
+ | .INCLUDE <m8def.inc> ; das gibt es für jeden Controllertyp | ||
+ | sagt ihm, er soll jetzt erstmal die (text) datei "m8def.inc" lesen und genauso behandeln, als wäre diese Datei mit dem Editor hier reinkopiert worden. | ||
+ | .LIST | ||
+ | Ab nun wird wieder alles in der Übersetzungsliste protokolliert. | ||
+ | .CSEG | ||
+ | was danach folgt, bezieht sich auf den FLASH-Programm-Speicher | ||
+ | |||
+ | Gleich ein Hinweis: Der "AVR Assembler 2" hat als Kennzeichen für den Precompiler ein "#" | ||
+ | und andere Schlüsselwörter. Zum allgemeinen Verständnis ist das aber vorerst egal. | ||
+ | |||
+ | ====Bemerkungen / Kommentare==== | ||
+ | ;------------------------------------------------------ | ||
+ | Alles, was NACH einem Strichpunkt steht, schreibt er einfach nur in die Übersetzungsliste, sonst macht er nix. | ||
+ | ====Label (Sprungmarke)==== | ||
+ | RESET: | ||
+ | Was gleich ganz links in der Zeile beginnt (und mit einem Doppelpunkt abgeschlossen ist) schreibt er einfach in die genannte Tabelle und den momentanen Wert des Pointers dazu. Sonst hat das im Moment keine weitere Bedeutung. Natürlich, wenn er den gleichen Text schon mal hatte, gibt es eine Fehlermeldung, denn '''ein Label muss eindeutig sein''' | ||
+ | ====Commands==== | ||
+ | jmp INIT ; springen nach "INIT" | ||
+ | Alles, was nicht ganz links steht und auch nix anderes ist, betrachtet der Assembler als "Command". Normalerweise ist das eben eine AVR-Instruktion aus dem "Instruction Set". Hier ist das "JMP", also im Programm einfach woanders weitermachen. Gut, aber wo ? Der Rechner braucht nun unbedingt eine Zahl, denn "INIT" sagt ihm ja nichts. Also sieht er nun in der Tabelle nach, ob er den Text "INIT" schon mal hatte. Hatte er nicht, den zu der Stelle weiter hinten, wo "INIT:" als Sprungmarke steht (s.o) ist er ja noch garnicht gekommen. | ||
+ | |||
+ | Ich kann leider nicht genau sagen, welche Strategie der AVR-Assembler nun anwendet, dazu gibt es keine Doku. Manche Assembler lesen erstmal überhaupt nur den Sourcetext von vorn bis hinten durch, um alle "Sprungmarken" zu sammeln (Pass I) und fangen dann nochmal von vorne an (Pass II), diesmal mit schon gefüllter Tabelle. Andere machen das etwas gefinkelter. Ist uns aber eigentlich egal. | ||
+ | |||
+ | Jedenfalls findet er letztlich den Text "INIT" und daneben steht die Nummer der Speicherstelle, wo wir eben "INIT:" hingeschrieben haben. Und nun schreibt der Assembler also den Maschinencode für "JMP" in den Buffer und dazu die Zahl aus der Tabelle. Das war mal ein Befehl, also addiert er auf seinen Zeiger eins drauf und ist mit diesem Befehl fertig. | ||
+ | |||
+ | |||
+ | ====ORG / JMP==== | ||
+ | Das, was für den Controller beim Ablauf dann ein "JMP" ist, ist das ".ORG" für den Compiler. Wenn also bislang der Assembler den FLASH-Speicher von 0 - nn mit Befehlen befüllt hat, macht er durch | ||
+ | .ORG INT_VECTORS_SIZE | ||
+ | mit der Stelle weiter, die er über die gleiche Tabelle unter "INT_VECTORS_SIZE" gefunden hat. Dieser Tabelleneintrag, genauso wie "RAMEND" an anderer Stelle, entsteht aus der Datei "m8def.inc". Wie im vorliegenen Fall macht man diese "Assembler-Jumps", um Speicherstellen freizuhalten. | ||
==Befehle== | ==Befehle== |
Version vom 6. Dezember 2005, 16:05 Uhr
Inhaltsverzeichnis
AVR Assembler Einführung
Es gibt mehrere Gründe, sich mit dem AVR-Assembler zu beschäftigen:
- reines Interesse
- Man hat eben keinen anderen Compiler, Bascom kostet ja was und GCC-C kann man möglicherweise genausowenig, also warum nicht gleich.
- Endlich Programmieren ohne Sprach-Restriktionen
- (theoretisch) sagenhaft schneller und kurzer Code
Dadurch ergeben sich aber auch verschiedene Haupt-Zielgruppen für eine Einführung. Die eine ist in einer oder mehreren anderen Sprachen durchaus versiert, und möchte endlich auch mal richtig in die Tiefen der Hardware steigen. Die andere ist totaler Neueinsteiger und hat eben gehört, daß man Micro-Controller eben am besten mit Assembler programmiert.
Unterschiede zu anderen Sprachen
Bildhaft ist der Unterschied der: Beim Assembler hat man ein weißes Stück Papier (ohne Linien) und ein Alphabet von A-Z. Aus diesen Buchstaben soll man nun erstmal Worte suchen und dann damit einen sinnvollen Text verfassen.
Bei "höheren" Sprachen ist das Papier zumindest mal liniert, und dazu kriegt man auch ein Wörtebuch und die Grammatik. Teile des Textes sind schon vorgeschrieben und ich muß nurmehr bei den ..... Punkten was einsetzen.
- Daten
Höherer Sprachen kennen alle möglichen Datentypen in allen möglichen Längen, integer, floating point, Strings. Dadurch sind aber auch gleich die möglichen und zulässigen Operationen damit schon eingeschränkt.
Beim (AVR) Assembler gibt es nur zwei Typen: Bytes mit Vorzeichen und Bytes ohne Vorzeichen. Aus. Und es gibt wenige Einschränkungen, was man damit machen kann.
- Vor- und Nachteile
Wenn man den Assembler mit höheren Sprachen vergleicht, um vielleicht Vor- und Nachteile herausarbeiten zu können, kommt man schnell drauf, daß in jedem Vorteil für eine der Seiten auch schon der Nachteil drinnen steckt. Was ich beim Assembler an Performance heraushole, weil ich z.B. "Goodies" eines Controllers ausnutze, bezahle ich, wenn ich mir einen anderen kaufe und dann das ganze Programm umbauen muß.
Struktur eines AVR-Maschinenprogrammes
Bei höheren Sprachen wird diese Struktur vom Compiler/Linker erzeugt, beim Assembler muß man sich selbst darum kümmern.
Der Controller beginnt mit der Abarbeitung links oben bei der Programmspeicheradresse 0000. Wenn man Interrupts verwenden will, muß man gleich das nächste Bereich (ISR-Vectoren) überspringen (mit "JMP"). Wenn nicht, kann man sich diese Vektoren aber auch wegdenken.
Man landet so oder so bei den Befehlen, die der Initialisierung dienen (Setzen der Startbedingung). Das ist zumindest die Festlegung des Stack-Pointers, sonst kann man keine "CALLS" oder Interrupts durchführen.
Danach kommt man von oben in die Haupt-Programm-Schleife, die (üblicherweise) immer wieder ohne Ende durchlaufen wird.
Das "End" ist bei Microcontrollern daher auch kaum wirklich notwendig. Wenn es da ist, ist das eine ewige Wiederholung eines einzigen Befehls.
Source-Code Muster
Das folgende Codebeispiel ist eine reine Basis-Initialisierung ohne irgendeine erkennbare Funktion. Für den Einstieg ist es am besten, das einfach abzuschreiben, wie es ist, und dann an den bezeichnenten Stellen mit eigenen Befehlen nach und nach zu erweitern.
.NOLIST ; List-Output unterdrücken .INCLUDE <m8def.inc> ; das gibt es für jeden Controllertyp .LIST ; List-Output wieder aufdrehen .CSEG ; was nun folgt, gehört in den FLASH-Speicher ;------------------------------------------------------ ; Start Adresse 0000 ;------------------------------------------------------ RESET: jmp INIT ; springen nach "INIT" ;------------------------------------------------------ ; ISR VECTORS ;------------------------------------------------------ ; ..... hier kommen dann die Sprungadressen für die Interrupts rein ; dazu kommen wir noch .ORG INT_VECTORS_SIZE ; dadurch haben wir für die Vektoren Platz gelassen INIT: ;------------------------------------------------------ ; INITIALIZE ;------------------------------------------------------ ldi r24,high(RAMEND) ;Stack Pointer setzen out SPH,r24 ; "RAMEND" ist in m8def.inc (s.o.) festgelegt ldi r24,low(RAMEND) ; out SPL,r24 ; ;------------------------------------------------------ ; eigene Initialisierungen ;------------------------------------------------------ ;.... ;.... ;.... ;------------------------------------------------------ ; HAUPTSCHLEIFE ;------------------------------------------------------ Hauptschleife: ;.... eigene befehle ;.... eigene befehle ;.... eigene befehle rjmp Hauptschleife ; immer wiederholen ;------------------------------------------------------ ; ENDE ;------------------------------------------------------ Ende: rjmp Ende
Der Ablauf der Übersetzung
Der Assembler ist ja selbst auch nur ein Programm und zu Beginn recht spartanisch ausgestattet: Er hat einen Daten-Buffer zur Verfügung, in den er den Maschinencode hineinschreiben soll, einen Pointer (Zeiger) dazu, damit er immer weiß, wo er grad ist, und eine allgemeine Tabelle, wo er Texte mit Zahlen verknüpfen kann. Alles ist zu Beginn leer bzw. auf NULL. Mit diesen Voraussetzungen fängt er an, unseren Source-Text zeilenweise zu lesen. Und in jeder Zeile (die nicht leer ist) erwartet er eine Anweisung, was zu tun ist
PreCompiler Anweisungen
Das sind Anweisungen an den Assembler selbst und erzeugen keine Maschinenbefehle. Das ob. Source-Fragment hat auch gleich mit sowas begonnen
.NOLIST
Normalerweise schreibt der Assembler ja eine Übersetzungsliste. Nach dieser Anweisung läßt er das bleiben
.INCLUDE <m8def.inc> ; das gibt es für jeden Controllertyp
sagt ihm, er soll jetzt erstmal die (text) datei "m8def.inc" lesen und genauso behandeln, als wäre diese Datei mit dem Editor hier reinkopiert worden.
.LIST
Ab nun wird wieder alles in der Übersetzungsliste protokolliert.
.CSEG
was danach folgt, bezieht sich auf den FLASH-Programm-Speicher
Gleich ein Hinweis: Der "AVR Assembler 2" hat als Kennzeichen für den Precompiler ein "#" und andere Schlüsselwörter. Zum allgemeinen Verständnis ist das aber vorerst egal.
Bemerkungen / Kommentare
;------------------------------------------------------
Alles, was NACH einem Strichpunkt steht, schreibt er einfach nur in die Übersetzungsliste, sonst macht er nix.
Label (Sprungmarke)
RESET:
Was gleich ganz links in der Zeile beginnt (und mit einem Doppelpunkt abgeschlossen ist) schreibt er einfach in die genannte Tabelle und den momentanen Wert des Pointers dazu. Sonst hat das im Moment keine weitere Bedeutung. Natürlich, wenn er den gleichen Text schon mal hatte, gibt es eine Fehlermeldung, denn ein Label muss eindeutig sein
Commands
jmp INIT ; springen nach "INIT"
Alles, was nicht ganz links steht und auch nix anderes ist, betrachtet der Assembler als "Command". Normalerweise ist das eben eine AVR-Instruktion aus dem "Instruction Set". Hier ist das "JMP", also im Programm einfach woanders weitermachen. Gut, aber wo ? Der Rechner braucht nun unbedingt eine Zahl, denn "INIT" sagt ihm ja nichts. Also sieht er nun in der Tabelle nach, ob er den Text "INIT" schon mal hatte. Hatte er nicht, den zu der Stelle weiter hinten, wo "INIT:" als Sprungmarke steht (s.o) ist er ja noch garnicht gekommen.
Ich kann leider nicht genau sagen, welche Strategie der AVR-Assembler nun anwendet, dazu gibt es keine Doku. Manche Assembler lesen erstmal überhaupt nur den Sourcetext von vorn bis hinten durch, um alle "Sprungmarken" zu sammeln (Pass I) und fangen dann nochmal von vorne an (Pass II), diesmal mit schon gefüllter Tabelle. Andere machen das etwas gefinkelter. Ist uns aber eigentlich egal.
Jedenfalls findet er letztlich den Text "INIT" und daneben steht die Nummer der Speicherstelle, wo wir eben "INIT:" hingeschrieben haben. Und nun schreibt der Assembler also den Maschinencode für "JMP" in den Buffer und dazu die Zahl aus der Tabelle. Das war mal ein Befehl, also addiert er auf seinen Zeiger eins drauf und ist mit diesem Befehl fertig.
ORG / JMP
Das, was für den Controller beim Ablauf dann ein "JMP" ist, ist das ".ORG" für den Compiler. Wenn also bislang der Assembler den FLASH-Speicher von 0 - nn mit Befehlen befüllt hat, macht er durch
.ORG INT_VECTORS_SIZE
mit der Stelle weiter, die er über die gleiche Tabelle unter "INT_VECTORS_SIZE" gefunden hat. Dieser Tabelleneintrag, genauso wie "RAMEND" an anderer Stelle, entsteht aus der Datei "m8def.inc". Wie im vorliegenen Fall macht man diese "Assembler-Jumps", um Speicherstellen freizuhalten.
Befehle
Autor: PicNick