(→Quellen: linkfix) |
|||
(124 dazwischenliegende Versionen von 7 Benutzern werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
− | + | In C versteht man unter '''Inline Assembler''' die Möglichkeit, direkt Assembler-Befehle in den Code einzufügen bzw. die eingefügten Assembler-Befehle selbst. | |
− | < | + | Neben den einzufügenden Befehlen muss beschrieben werden, welche Nebeneffekte die Befehle auf die Maschine haben und wo/wie Parameter übergeben werden, bzw. wie die Zuordnung von Variablen zu den Registern ist. Diese Beschreibung ist notwendig weil der [[Compiler]] keine Vorstellung davon hat, welche Effekte und Nebenwirkungen die Assembler-Kommandos auf Register, Variablen und Speicher haben, denn der Assembler-Schnippsel ist für den Compiler lediglich eine "Black Box". |
− | {| {{Blauetabelle}} | + | |
− | |+ '''Tabelle: | + | Obgleich das dazu verwendete Schlüsselwort <tt>__asm</tt> zum ANSI-C-Standard gehört, ist dies in jedem C-Compiler anders implementiert. Das gilt insbesondere für die Schnittstellenbeschreibung Variablen/Register. Dieser Artikel bezieht sich auf den Inline Assembler von [[avr-gcc]]. |
+ | Viele Erklärungen aus diesem Artikel sind auch gültig für Inline-Assembler anderer C-Compiler der [[gcc]]-Familie. Instruktionen und Registerklassen werden sich aber unterscheiden, weil diese natürlich abhängig sind vom verwendeten Controller bzw. der Controller-Familie. | ||
+ | |||
+ | =Assembler oder Inline-Assembler?= | ||
+ | ;Assembler: Längere und komplexere Code-Stücke sind komfortabler direkt in Assembler auszudrücken. Dazu schreibt man Assembler-Funktionen und ruft diese von C aus auf. Natürlich können auch C-Funktionen von Assembler aus aufgerufen werden. Zudem kann man im Assembler den C-Präprozessor nutzen, was bei Inline-Assembler nur auf C-Ebene geht. Der Build-Prozess wird allerdings komplizierter, da extra asm-Dateien übersetzt werden müssen. Ein typischer Fall ist es, eine komplette ISR in Assembler zu schreiben. | ||
+ | ;Inline-Assembler: Mit Inline-Assembler kann man kleine Assembler-Stückchen direkt in den C-Code einbetten. Es muss dann keine Assembler-Funktion aufgerufen werden. Dies kann von der Registerverwendung her deutlich günstiger sein, da [[gcc]] genau weiß, welche Register gebraucht werden und welche nicht. | ||
+ | :Eine (Assembler-)Funktion ist hingegen eine Black Box, bei der von der Standard-[[avr-gcc/Interna#Registerverwendung|Registerverwendung]] ausgegangen werden muss, auch wenn weniger Register in der Funktion verwendet werden. Ein Funktionsaufruf bedeutet also meistens einen Laufzeit-Overhead im Vergleich zu einem Inline-Code. | ||
+ | :Bei mehrfacher Verwendung einer längeren Codesequenz ist eine Funktion jedoch sparsamer im [[Flash]]-Verbrauch. Legt man eine Funktion als C-Funktion an und ihren Body als Inline-Assembler (eine s.g. Stub-Funktion, von engl. ''Stub'' = Stumpf), dann übernimmt gcc das Verwalten von Funktions-Argumenten, return-Wert, etc. und man brauch sich nicht selber um die Aufruf-Konvention zu kümmern. Auch innerhalb einer Funktion kann C mit Assembler gemischt werden. | ||
+ | :Ein Vorteil von Inline-Assembler ist, daß eine C-Funktion, die Inline-Assembler enthält, vom C-Compiler [[C-Tutorial#Inlining|geinlinet]] werden kann. Dies ist mit einer reinen Assembler-Funktion nicht möglich. | ||
+ | |||
+ | =Begriffe= | ||
+ | ;Assembler-Template: Das Template (''Schablone'') ist ein statischer, konstanter String im Sinne von C. Es enthält die Assembler-Befehle sowie Platzhalter, in deren Stelle später die Operanden treten | ||
+ | ;Constraint: Die Constraints (''Nebenbedingungen'') beschreiben Einschränkungen an die zu verwendeten Register. Dies ist notwendig, da nicht alle Maschinenbefehle auf alle Register anwendbar sind | ||
+ | ;Clobber-List: Das ist eine Liste von Registern, deren Inhalt durch den Inline-Assembler zerstört wird | ||
+ | |||
+ | =Syntax und Semantik= | ||
+ | Das Schlüsselwort, um eine Inline-Assembler Sequenz einzuleiten, ist <tt>__asm</tt> (ANSI). Oft ist auch <tt>asm</tt> oder <tt>__asm__</tt> verwendbar. Um zu kennzeichnen, daß die Sequenz keinesfalls wegoptimiert werden darf – etwa dann, wenn der Assembler keine Wirkung auf C-Variablen hat – wird dem <tt>asm</tt> ein <tt>volatile</tt> bzw. <tt>__volatile</tt> nachgestellt. Danach folgen in runden Klammern die durch <tt>''':'''</tt> getrennten Abschnitte des Inline-Assemblers: | ||
+ | asm volatile ([[#Assembler-Template|asm-template]] : [[#Operanden und Constraints|output-operand-list]] : [[#Operanden und Constraints|input-operand-list]] : [[#Clobbers|clobber-list]]); | ||
+ | |||
+ | Abschnitte, die leer sind, können auch weggelassen werden, wenn dahinter kein weiterer Abschnitt folgt: | ||
+ | asm volatile (asm-template); | ||
+ | |||
+ | Oder, wenn weder Input- noch Output-Operanden gebraucht werden, aber Register oder Speicher verändert werden: | ||
+ | asm volatile (asm-template ::: clobber-list); | ||
+ | |||
+ | {{FarbigerRahmen | | ||
+ | Aus Compiler-Sicht werden die Assembler-Befehle im Template parallel, also ''gleichzeitig'' ausgeführt! Dies ist zu bedenken, wenn Register sowohl als Input als auch als Output verwendet werden. | ||
+ | }} | ||
+ | |||
+ | Ab Version 4.5 kennt GCC <tt>asm goto</tt>, mit dem ausgedrückt werden kann, dass der Codefluß des Assembler-Teils u.U. zu einem Stück C-Code springt: | ||
+ | asm goto ([[#Assembler-Template|asm-template]] : {{comment|Leer}} : [[#Operanden und Constraints|input-operand-list]] : [[#Clobbers|clobber-list]] : [[#asm goto mit C-Labels|C-labels]]); | ||
+ | |||
+ | == Assembler-Template == | ||
+ | |||
+ | Im Template stehen die durch Zeilenumbrüche getrennten Assembler-Befehle. Das Template kann zudem <tt>%</tt>-Ausdrücke als Platzhalter enthalten, welche durch die Operanden ersetzt werden. Dabei bezieht sich <tt>%0</tt> auf den ersten Operanden, <tt>%1</tt> auf den zweiten Operanden, etc. | ||
+ | Die Operanden selbst werden im zweiten und dritten Abschnitt des Templates als Komma-getrennte Liste angegeben. | ||
+ | Diese Ersetzung findet jedoch nur dann statt, wenn das asm nicht nur aus einem String besteht: | ||
+ | asm ("10% mehr"); {{comment|"10% mehr"}} | ||
+ | asm ("10%% mehr" :); {{comment|"10% mehr"}} | ||
+ | |||
+ | Ein Platzhalter kann zusätzlich einen einbuchstabigen Modifier enthalten, um den Operanden in printf-ähnlicher Manier in einem speziellen Format darzustellen. Wird z.B. ein Wert ab Register <tt>r28</tt> (dem Y-Register) gehalten, dann wären folgende Ersetzungen denkbar (als erstes Argument): | ||
+ | |||
+ | :<tt>%0 → r28</tt> | ||
+ | |||
+ | :<tt>%A0 → r28</tt><br/> | ||
+ | :<tt>%B0 → r29</tt><br/> | ||
+ | :<tt>%C0 → r30</tt><br/> | ||
+ | :<tt>%D0 → r31</tt> | ||
+ | |||
+ | :<tt>%a0 → y </tt> | ||
+ | |||
+ | Im einfachsten Falle enthält das Templater nur einen Befehl: | ||
+ | "nop" | ||
+ | oder sogar garkeinen Befehl oder lediglich einen Kommentar: | ||
+ | "; ein Kommentar" | ||
+ | |||
+ | :{| {{Blauetabelle}} | ||
+ | |+ '''Tabelle: asm-Platzhalter und ihre Modifier, Sonderzeichen''' | ||
|- {{Hintergrund1}} | |- {{Hintergrund1}} | ||
− | ! | + | ! Platzhalter || wird ersetzt durch || AVR-spezifisch |
+ | |- valign="top" | ||
+ | |<tt>%''n''</tt> || Wird ersezt durch Argument <tt>''n''</tt> mit <tt>''n''</tt> = 0...9 | ||
+ | | | ||
+ | |- valign="top" | ||
+ | |<tt>%A''n''</tt> || das erste (untere) Register des Arguments <tt>''n''</tt> (Bits 0...7) | ||
+ | |align="center" rowspan="4"| '''X''' | ||
+ | |- valign="top" | ||
+ | |<tt>%B''n''</tt> || das zweite Register des Arguments <tt>''n''</tt> (Bits 8...15) | ||
+ | |- valign="top" | ||
+ | |<tt>%C''n''</tt> || das dritte Register des Arguments <tt>''n''</tt> (Bits 16...23) | ||
+ | |- valign="top" | ||
+ | |<tt>%D''n''</tt> || das vierte Register des Arguments <tt>''n''</tt> (Bits 24...31) | ||
+ | |- valign="top" | ||
+ | |<tt>%a''n''</tt> || Ausgabe des Arguments als Adress-Register,<br/>also als <tt>x</tt>, <tt>y</tt> bzw. <tt>z</tt>. Erlaubt zusammen mit Constraint <tt>b</tt>, <tt>e</tt>, <tt>x</tt>, <tt>y</tt>, <tt>z</tt> | ||
+ | |align="center"| '''X''' | ||
+ | |- valign="top" | ||
+ | |<tt>%i''n''</tt> || Ab 4.7. Ausgabe einer RAM-Adresse als I/0-Adresse. Beispiel: <tt>0x30</tt> wird auf Xmega-Controllern als <tt>0x30</tt> ausgegeben und auf nicht-Xmega als <tt>0x10</tt>. | ||
+ | |align="center"| '''X''' | ||
+ | |- valign="top" | ||
+ | |<tt>%r''n''</tt> || Ab 4.8. Ausgabe eines Registers ohne die Register-Präfix "r". Damit kann per Inline-Assembler auf 64-Bit Variablen zugegriffen werden, z.B. kann mit <tt>"clr %r0+7"</tt> das High-Byte gelöscht werden. | ||
+ | |align="center"| '''X''' | ||
+ | |- valign="top" | ||
+ | |<tt>%T''n''%T''m''</tt> || Ab 4.7. Gibt einen Doppel-Operanden aus wie er zum Beispiel in BLD oder BST benötigt wird, d.h. durch ein Komma getrennte Register- und Bitnummer. Das erste <tt>%T</tt> erhält ein Register und speichert es zwischen bis zum nächsten <tt>%T</tt>, das die konstante Bitnummer ''m'' enthält. Die Ausgabe erfolgt erst mit dem zweiten <tt>%T</tt>: Beispielsweite kann durch folgende Zeile das High-Bit von ''var'' gelöscht werden: | ||
+ | asm ("clt $ bld %T[v]%T[b]" : [v] "+r" (var) : [b] "n" (8*(sizeof var)-1)); | ||
+ | |align="center"| '''X''' | ||
+ | |- valign="top" | ||
+ | |<tt>%T''n''%t''m''</tt> || Ab 4.7. Funktioniert wie <tt>%T''n''%T''m''</tt>, es wird aber nur das zum Bit ''m'' gehörende Register ausgegeben, d.h. weder Komma noch Bitnummer. | ||
+ | |align="center"| '''X''' | ||
+ | |- valign="top" | ||
+ | |<tt>%x''n''</tt> || Ab 4.5. Gibt ein Label ohne Operand-Modifier <tt>gs()</tt> aus. | ||
+ | |align="center"| '''X''' | ||
+ | |- valign="top" | ||
+ | |<tt>%~</tt> || wird auf AVR mit Flash bis max. 8kiByte durch ein <tt>r</tt> ersetzt, ansonsten bleibt es leer.<br/>Zum Aufbau von Sprungbefehlen, etwa "<tt>%~call foo</tt>" | ||
+ | |align="center"| '''X''' | ||
+ | |- valign="top" | ||
+ | |<tt>%!</tt> || Ab 4.4. Wird auf AVR mit Flash ab 128kiByte durch ein <tt>e</tt> ersetzt, ansonsten bleibt es leer.<br/>Zum Aufbau von indirekten Sprungbefehlen, etwa "<tt>%!icall</tt>" | ||
+ | |align="center"| '''X''' | ||
+ | |- valign="top" | ||
+ | |<tt>%=</tt> || eine für dieses asm-Template und die Übersetzungseinheit eindeutige Zahl.<br/>Zum Aufbau lokaler Sprungmarken. | ||
+ | | | ||
+ | |- {{Hintergrund1}} | ||
+ | ! Sequenz || wird ersetzt durch Sonderzeichen || AVR-spezifisch | ||
+ | |- valign="top" | ||
+ | |<tt>%%</tt> || das <tt>%</tt>-Zeichen selbst | ||
+ | | | ||
+ | |- valign="top" | ||
+ | |<tt>\n</tt> || ein Zeilenumbruch zum Trennen mehrerer asm-Befehle/Zeilen | ||
+ | | | ||
+ | |- valign="top" | ||
+ | |<tt>$</tt> || trennt mehrere Befehle in der gleichen Zeile | ||
+ | |align="center"| '''X''' | ||
+ | |- valign="top" | ||
+ | |<tt>\t</tt> || ein TAB, zur Übersichtlichkeit im erzeugten asm || | ||
+ | |- valign="top" | ||
+ | |<tt>\"</tt> || ein <tt>"</tt> wird eingefügt || | ||
+ | |- valign="top" | ||
+ | |<tt>\\</tt> || das <tt>\</tt>-Zeichen selbst || | ||
+ | |- {{Hintergrund1}} | ||
+ | ! Kommentar || Beschreibung || AVR-spezifisch | ||
+ | |- valign="top" | ||
+ | |<tt>; ''Text''</tt> || einzeiliger Kommentar bis zum Ende des Templates bzw. nächsten Zeilenumbruch | ||
+ | |align="center"| '''X''' | ||
+ | |- valign="top" | ||
+ | |<tt>/* ''Text'' */</tt> || mehrzeiliger Kommentar wie in C || | ||
+ | |} | ||
+ | |||
+ | == Operanden und Constraints == | ||
+ | |||
+ | Ein Operand besteht aus der Angabe des Constraint-Strings (also der Registerklasse und Kennzeichnung, ob es sich um einen Output-Operanden handelt) und dahinter in runden Klammern der C-Ausdruck, der in Register der angegebenen Klasse geladen werden soll. Die Operanden werden mit 0 beginnend von links nach rechts durchnumeriert und über diese Nummer angesprochen, um sie ins Assembler-Schnippsel einzufügen. | ||
+ | |||
+ | Mehrere Input- bzw. Output-Operanden werden durch Komma getrennt. | ||
+ | |||
+ | :{| {{Blauetabelle}} | ||
+ | |+ '''Tabelle: Constraints und ihre Bedeutung''' | ||
+ | |- {{Hintergrund1}} | ||
+ | ! Constraint || Register || Wertebereich | ||
+ | |rowspan="14"| | ||
+ | ! Constraint || Konstante || Wertebereich | ||
|- | |- | ||
− | |<tt> | + | |<tt>a</tt>||einfache obere Register||<tt>r16</tt>...<tt>r23</tt> |
+ | |<tt>G</tt>||Floatingpoint-Konstante ||<tt>0.0</tt> | ||
|- | |- | ||
− | |<tt> | + | |<tt>b</tt>||Pointer-Register ||<tt>y</tt>, <tt>z</tt> |
+ | |<tt>i</tt>||Konstante, entspricht <tt>"sn"</tt> || | ||
|- | |- | ||
− | |<tt> | + | |<tt>d</tt>||obere Register ||<tt>r16</tt>...<tt>r31</tt> |
+ | |<tt>s</tt>||symbolischer Wert || | ||
|- | |- | ||
− | |<tt> | + | |<tt>e</tt>||Pointer-Register ||<tt>x</tt>, <tt>y</tt>, <tt>z</tt> |
+ | |<tt>n</tt>||Wert bekannt zur Compilezeit || | ||
|- | |- | ||
− | |<tt> | + | |<tt>l</tt>||untere Register ||<tt>r0</tt>...<tt>r15</tt> |
+ | |<tt>I</tt>||positive 6-Bit-Konstante ||0...63 | ||
|- | |- | ||
− | |<tt> | + | |<tt>q</tt>||Stack-Pointer ||<tt>SPH:SPL</tt> |
+ | |<tt>J</tt>||negative 6-Bit Konstante ||<tt>-</tt>63...0 | ||
|- | |- | ||
− | |<tt> | + | |<tt>r</tt>||ein Register ||<tt>r0</tt>...<tt>r31</tt> |
+ | |<tt>M</tt>||8-Bit Konstante ||0...255 | ||
|- | |- | ||
− | |<tt> | + | |<tt>t</tt>||Scratch-Register ||<tt>r0</tt> |
+ | |colspan="3" | | ||
|- | |- | ||
− | |<tt> | + | |<tt>w</tt>||obere Register-Paare ||<tt>r24</tt>, <tt>r26</tt>, <tt>r28</tt>, <tt>r30</tt> |
+ | !{{Hintergrund1}}| Constraint ||{{Hintergrund1}}| Memory ||{{Hintergrund1}}| Wertebereich | ||
+ | |- | ||
+ | |<tt>x</tt>||Pointer-Register X ||<tt>x</tt> (<tt>r27:r26</tt>) | ||
+ | |<tt>m</tt>|| Memory || | ||
+ | |- | ||
+ | |<tt>y</tt>||Pointer-Register Y ||<tt>y</tt> (<tt>r29:r28</tt>) | ||
+ | |colspan="3" rowspan="3"| | ||
+ | |- | ||
+ | |<tt>z</tt>||Pointer-Register Z ||<tt>z</tt> (<tt>r31:r30</tt>) | ||
+ | |- | ||
+ | |<tt>0</tt>...<tt>9</tt>||colspan="2"|Identisch mit dem angegebenen Operanden<br/>Wird verwendet, wenn ein Operand sowohl als Input<br/>als auch als Output dient, um sich auf diesen<br/>Operanden zu beziehen | ||
|} | |} | ||
− | + | ||
− | + | ||
− | {| {{Blauetabelle}} | + | :{| {{Blauetabelle}} |
− | |+ '''Tabelle: | + | |+ '''Tabelle: Constraint Modifier''' |
|- {{Hintergrund1}} | |- {{Hintergrund1}} | ||
− | ! | + | !Modifier || Bedeutung |
|- | |- | ||
− | |<tt> | + | |<tt>=</tt> || der Operand ist Output-Operand |
|- | |- | ||
− | |<tt> | + | |<tt>&</tt> || diesen Operanden ''nicht'' als Input-Operanden verwenden,<br/>sondern nur als Output-Operand |
|- | |- | ||
− | |<tt> | + | |<tt>+</tt> || dieser Operanden ist Input- und Output-Operand |
+ | |} | ||
+ | |||
+ | |||
+ | Ein Input-Operand könnte also so aussehen, wobei <tt>foo</tt> eine C-Variable ist. Als Register dient ein (je nach Typ von <tt>foo</tt> auch mehrere) obere Register, irgendwo von <tt>r16</tt> bis <tt>r31</tt> (Constraint <tt>"d"</tt>): | ||
+ | "d" (foo) | ||
+ | |||
+ | In den Klammern kann ein beliebiger, gültiger C-Ausdruck stehen, der beim folgenden Beispiel in irgendeinem Register landet (Constraint <tt>"r"</tt>), ohne weitere Einschränkung an das Register: | ||
+ | "r" ((foo >= 0) ? foo : -foo) | ||
+ | |||
+ | Um einen Operanden als Output-Operanden zu kennzeichnen, wird dem Constraint ein <tt>"="</tt> vorangestellt. | ||
+ | Soll <tt>foo</tt> ein Output-Operand sein, der in den Registern <tt>r0</tt>...<tt>r15</tt> landen soll (Constraint <tt>"l"</tt>, sieht es so aus. Dabei muss <tt>foo</tt> ein sogenannter Lvalue sein, also ein Wert, dem etwas zugewiesen werden kann: | ||
+ | "=l" (foo) | ||
+ | |||
+ | Ist <tt>foo</tt> sowohl Input als auch Output, schreibt man <tt>foo</tt> als Output- und als Input-Operand hin. In der Input-Constraint bezieht man sich dann auf die Operanden-Nummer von <tt>foo</tt>. Hier ein komplettes Beispiel, das die Nibbles von <tt>foo</tt> tauscht. Weil <tt>swap</tt> auf alle Register anwendbar ist, kann als Registerklasse <tt>"r"</tt> genommen werden: | ||
+ | <pre> | ||
+ | unsigned char foo; | ||
+ | ... | ||
+ | asm ("swap %0" : "=r" (foo) : "0" (foo)); | ||
+ | </pre> | ||
+ | <tt>foo</tt> als Input-Operand soll im gleichen Register liegen wie <tt>foo</tt> als Output-Operand. Daher wird als Constraint <tt>"0"</tt> angegeben, d.h. es wird ins gleiche Register geladen wie der Operand Numero 0 <tt>"r" (foo)</tt>. | ||
+ | |||
+ | === Benannte Constraints === | ||
+ | |||
+ | Um das Assembler-Schnippsel besser lesbar zu halten, kann man Constraints einen Namen geben: | ||
+ | asm ("swap %[bar]" : [bar] "+r" (foo)); | ||
+ | Damit ist der Assembler auch unabhängig von der Reihenfolge der Operanden. Das macht die Anpassung, wenn ein neuer Operand hinzukommt, wesentlich einfacher und den Schnippsel zudem besser lesbar. | ||
+ | |||
+ | Hier wird <tt>bar</tt> als Alias für die C-Variable <tt>foo</tt> benutzt; es könnte aber auch jeder andere gültige C-Bezeichner sein, also insbesondere auch <tt>foo</tt>. | ||
+ | |||
+ | === Instruktionen und Constraints === | ||
+ | |||
+ | Die folgende Tabelle enhält eine Auflistung von AVR-Instruktionen und dazu passende Argumente bzw. Constraints. Nicht alle Shorthands sind in der Tabelle enthalten, so ist "<tt>clr Rn</tt>" nur eine Abkürzung für "<tt>eor Rn, Rn</tt>", ähnliches gilt für den Zoo von Instruktionen rund um das SREG wie branch, bit set, bit clear, etc., die im Endeffekt auf nur vier Instruktionen abbilden. Instruktionen wie <tt>nop</tt>, die keine Argumente brauchen, sind ebenfalls nicht in der Tabelle enthalten. Gleiches gilt für den Krypto-Befehl <tt>des</tt>, für die keine Constraint verfügbar ist. | ||
+ | |||
+ | <tt>"load"</tt> ist ein <tt>"load from SRAM"</tt><br/> | ||
+ | <tt>"store"</tt> ist <tt>"store to SRAM"</tt><br/> | ||
+ | |||
+ | :{| {{Blauetabelle}} | ||
+ | |+'''Tabelle: Übersicht AVR-Instruktionen und passende Constraints''' | ||
+ | |- {{Hintergrund1}} | ||
+ | !Mnemonic || Constraint ||Bedeutung ||rowspan="35"| | ||
+ | !Mnemonic || Constraint ||Bedeutung | ||
|- | |- | ||
− | |<tt> | + | | <tt>adc</tt>||<tt>r,r</tt> || add with carry |
+ | | <tt>add</tt>||<tt>r,r</tt> || add | ||
|- | |- | ||
− | |<tt> | + | | <tt>adiw</tt>||<tt>w,I</tt> || add immediate to word |
+ | | <tt>and</tt>||<tt>r,r</tt> || and | ||
|- | |- | ||
− | |<tt> | + | | <tt>andi</tt>||<tt>d,M</tt> || and with immediate |
+ | | <tt>asr</tt>||<tt>r</tt> || arithmetic shift right | ||
|- | |- | ||
− | |<tt> | + | | <tt>bclr</tt>||<tt>I</tt> || bit clear in SREG |
+ | | <tt>bld</tt>||<tt>r,I</tt> || bit load from T | ||
|- | |- | ||
− | |<tt> | + | | <tt>brbc</tt>||<tt>I,label</tt> || branch if bit in SREG clear |
− | | | + | | <tt>brbs</tt>||<tt>I,label</tt> || branch if bit in SREG set |
− | + | ||
|- | |- | ||
− | |<tt> | + | | <tt>bset</tt>||<tt>I</tt> || bit set in SREG |
+ | | <tt>bst</tt>||<tt>r,I</tt> || bit store from T | ||
|- | |- | ||
− | |<tt> | + | | <tt>cbi</tt>||<tt>I,I</tt> || clear bit in I/O |
+ | | <tt>com</tt>||<tt>r</tt> || complement | ||
|- | |- | ||
− | |<tt> | + | | <tt>cp</tt>||<tt>r,r</tt> || compare |
+ | | <tt>cpc</tt>||<tt>r,r</tt> || compare with carry | ||
|- | |- | ||
− | |<tt> | + | | <tt>cpi</tt>||<tt>d,M</tt> || compare against immediate |
+ | | <tt>cpse</tt>||<tt>r,r</tt>|| compare, skip if equal | ||
|- | |- | ||
− | |<tt> | + | | <tt>dec</tt>||<tt>r</tt> || decrement |
− | | | + | | <tt>[e]lpm</tt>||<tt>r,z</tt>|| load from program memory<sup>1</sup> |
− | + | ||
|- | |- | ||
− | |<tt> | + | | <tt>eor</tt>||<tt>r,r</tt> || exclusive-or |
+ | | <tt>fmul*</tt>||<tt>a,a</tt> || fractional multiply | ||
|- | |- | ||
− | |<tt> | + | | <tt>in</tt>||<tt>r,I</tt> || input from I/O |
− | + | | <tt>inc</tt>||<tt>r</tt> || increment | |
− | < | + | |
− | + | ||
− | < | + | |
− | + | ||
− | | | + | |
− | | | + | |
− | + | ||
|- | |- | ||
− | |<tt> | + | | <tt>lac</tt>||<tt>z,r</tt> || load and clear |
+ | | <tt>lat</tt>||<tt>z,r</tt> || load and toggle | ||
|- | |- | ||
− | |<tt> | + | | <tt>las</tt>||<tt>z,r</tt> || load and set |
+ | |colspan="3"| | ||
|- | |- | ||
− | |<tt> | + | | <tt>ld</tt>||<tt>r,e</tt> || load indirect |
+ | | <tt>ld</tt>||<tt>r,e+</tt> || load indirect, post-increment | ||
|- | |- | ||
− | |<tt> | + | | <tt>ld</tt>||<tt>r,-e</tt> || load indirect, pre-decrement |
+ | | <tt>ldd</tt>||<tt>r,b+I</tt> || load indirect with displacement | ||
|- | |- | ||
− | |<tt> | + | | <tt>ldi</tt>||<tt>d,M</tt> || load immediate |
+ | | <tt>lds</tt>||<tt>r,label</tt> || load direct from SRAM | ||
|- | |- | ||
− | |<tt> | + | | <tt>lpm</tt>||<tt>t,z</tt> || load from program memory<sup>1</sup> |
+ | | <tt>lsr</tt>||<tt>r</tt> || logical shift right | ||
|- | |- | ||
− | |<tt> | + | | <tt>mov</tt>||<tt>r,r</tt> || move |
+ | | <tt>movw</tt>||<tt>r,r</tt> || move word | ||
|- | |- | ||
− | |<tt> | + | | <tt>mul</tt>||<tt>r,r</tt> || multiply unsigned |
+ | | <tt>muls</tt>||<tt>d,d</tt> || multiply signed | ||
|- | |- | ||
− | |<tt> | + | | <tt>mulsu</tt>||<tt>a,a</tt> || multiply signed/unsigned |
+ | |colspan="3"| | ||
|- | |- | ||
− | |<tt> | + | | <tt>neg</tt>||<tt>r</tt> || negate |
+ | | <tt>or</tt>||<tt>r,r</tt> || or | ||
|- | |- | ||
− | |<tt> | + | | <tt>ori</tt>||<tt>d,M</tt> || or with immediate |
+ | | <tt>out</tt>||<tt>I,r</tt> || output to I/O | ||
|- | |- | ||
− | |<tt> | + | | <tt>pop</tt>||<tt>r</tt> || pop from Stack |
+ | | <tt>push</tt>||<tt>r</tt> || push to Stack | ||
|- | |- | ||
− | |<tt> | + | | <tt>ror</tt>||<tt>r</tt> || rotate right |
+ | |colspan="3"| | ||
|- | |- | ||
− | |<tt> | + | | <tt>sbc</tt>||<tt>r,r</tt> || subtract with carry |
+ | | <tt>sbci</tt>||<tt>d,M</tt> || subtract with carry immediate | ||
|- | |- | ||
− | |<tt> | + | | <tt>sbic</tt>||<tt>I,I</tt> || skip if bit in I/O clear |
+ | | <tt>sbis</tt>||<tt>I,I</tt> || skip if bit in I/O set | ||
|- | |- | ||
− | |<tt> | + | | <tt>sbi</tt>||<tt>I,I</tt> || set Bit in I/O |
+ | | <tt>sbiw</tt>||<tt>w,I</tt> || subtract immediate from word | ||
|- | |- | ||
− | |<tt> | + | | <tt>sbrc</tt>||<tt>r,I</tt> || skip if bit in register clear |
+ | | <tt>sbrs</tt>||<tt>r,I</tt> || skip if bit in register set | ||
|- | |- | ||
− | |<tt> | + | | <tt>st</tt>||<tt>e,r</tt> || store indirect |
+ | | <tt>st</tt>||<tt>e+,r</tt> || store indirect, post-increment | ||
|- | |- | ||
− | |<tt> | + | | <tt>st</tt>||<tt>-e,r</tt> || store indirect, pre-decrement |
+ | | <tt>std</tt>||<tt>b,r</tt> || store indirect with displacement | ||
|- | |- | ||
− | |<tt> | + | | <tt>sts</tt>||<tt>label,r</tt> || store direct |
+ | | <tt>sub</tt>||<tt>r,r</tt> || subtract | ||
|- | |- | ||
− | |<tt> | + | | <tt>subi</tt>||<tt>d,M</tt> || subtract immediate |
+ | | <tt>swap</tt>||<tt>r</tt> || swap nibbles | ||
|- | |- | ||
− | |<tt> | + | | <tt>xch</tt>||<tt>z,r</tt> || exchange |
+ | |colspan="3"| | ||
|} | |} | ||
− | |||
− | < | + | <sup>1</sup> Je nach Derivat werden die folgenden <tt>lpm</tt>-Ausprägungen unterstützt oder nicht: |
− | {| {{Blauetabelle}} | + | * <tt>lpm</tt> |
− | |+'''Tabelle: | + | * <tt>lpm r,z</tt> bzw. <tt>lpm r,z+</tt> |
+ | * <tt>elpm</tt> | ||
+ | * <tt>elpm r,z</tt> bzw. <tt>elpm r,z+</tt> | ||
+ | |||
+ | Hier noch ein paar der gebräuchlichsten Shorthands und deren Abbildung auf "Basis"-Instruktionen: | ||
+ | |||
+ | :{| {{Blauetabelle}} | ||
+ | |+'''Tabelle: Shorthands''' | ||
|- {{Hintergrund1}} | |- {{Hintergrund1}} | ||
− | ! | + | !Shorthand || gleichbedeutend mit || Bedeutung ||rowspan="5"| |
− | + | !Shorthand || gleichbedeutend mit || Bedeutung | |
− | ! | + | |
− | + | ||
|- | |- | ||
− | | <tt> | + | | <tt>rol r</tt> || <tt>adc r,r</tt> || rotate left |
− | | | + | | <tt>cbr r,M</tt> || <tt>andi r, ~M</tt> || clear bits in reg |
− | + | ||
− | | | + | |
|- | |- | ||
− | | <tt> | + | | <tt>clr r</tt> || <tt>eor r,r</tt> || clear reg to zero |
− | + | | <tt>lsl r</tt> || <tt>add r,r</tt> || logical shift left | |
− | | | + | |
− | | <tt> | + | |
|- | |- | ||
− | | <tt> | + | | <tt>sbr d,M</tt> || <tt>ori d, M</tt> || set bits in reg |
− | | | + | | <tt>ser d</tt> || <tt>ldi d, 0xff</tt> || set all bits in reg |
− | | <tt> | + | |
− | | | + | |
|- | |- | ||
− | | <tt> | + | | <tt>tst r</tt> || <tt>and r,r</tt> || test against zero |
− | | <tt> | + | |colspan="3"| |
− | + | |} | |
− | | <tt> | + | |
+ | ==Clobbers== | ||
+ | |||
+ | In der Komma-getrennten Clobber-Liste kann man angeben, welche Register durch den Inline-Assembler ihren Wert ändern. Ändern z.B. <tt>r2</tt> und <tt>r3</tt> ihre Werte, dann ist die Clobber-Liste | ||
+ | "r2", "r3" | ||
+ | |||
+ | Wird schreibend auf das RAM zugegriffen, dann muss man das auch mitteilen, damit RAM-Inhalte, die sich evtl. in Registern befinden, nach dem Inline-Assembler neu gelesen werden. Der Clobber dafür ist: | ||
+ | "memory" | ||
+ | |||
+ | '''Beispiel:''' | ||
+ | |||
+ | Es soll ein Inline-Assembler geschrieben werden, das den Inhalt zweier aufeinanderfolgender Speicherstellen austauscht. Die Adresse soll in <tt>addr</tt> stehen. Sie ist Input-Operand und muss in Register X, Y oder Z stehen, um den <tt>ld</tt> bzw. <tt>st</tt>-Befehl anwenden zu können. Die passende Constraint ist also <tt>"e"</tt>. Nach der Sequenz liegt <tt>addr</tt> unverändert vor. | ||
+ | <pre> | ||
+ | asm volatile ( | ||
+ | "ld r2, %a0+" "\n\t" | ||
+ | "ld r3, %a0" "\n\t" | ||
+ | "st %a0, r2" "\n\t" | ||
+ | "st -%a0, r3" | ||
+ | : /* keine Output-Operanden */ | ||
+ | : "e" (addr) | ||
+ | : "r2", "r3", "memory" | ||
+ | ); | ||
+ | </pre> | ||
+ | avr-gcc entscheidet sich dazu, das Z-Register für <tt>addr</tt> zu verwenden: | ||
+ | <pre> | ||
+ | ld r2, Z+ | ||
+ | ld r3, Z | ||
+ | st Z, r2 | ||
+ | st -Z, r3 | ||
+ | </pre> | ||
+ | |||
+ | Günstiger ist es jedoch, dem Compiler auch die Entscheidung zu überlassen, welche(s) Register als Hilfsregister verwendet werden sollen. Ein Register kann <tt>__tmp_reg__</tt> sein, für das zweite legen wir eine lokale 8-Bit-Variable <tt>hilf</tt> an: | ||
+ | <pre> | ||
+ | { | ||
+ | char hilf; | ||
+ | |||
+ | asm volatile ( | ||
+ | "ld __tmp_reg__, %a1+" "\n\t" | ||
+ | "ld %0, %a1" "\n\t" | ||
+ | "st %a1, __tmp_reg__" "\n\t" | ||
+ | "st -%a1, %0" | ||
+ | : "=&r" (hilf) | ||
+ | : "e" (addr) | ||
+ | : "memory" | ||
+ | ); | ||
+ | } | ||
+ | </pre> | ||
+ | <tt>__tmp_reg__</tt> (also <tt>r0</tt>) brauch nicht in die Clobber-Liste aufgenommen zu werden. Um das zweite benötigte Register (hier <tt>r24</tt>, in dem <tt>hilf</tt> lebt) kümmert sich avr-gcc und sichert es, falls nötig | ||
+ | <pre> | ||
+ | ld r0, Z+ | ||
+ | ld r24, Z | ||
+ | st Z, r0 | ||
+ | st -Z, r24 | ||
+ | </pre> | ||
+ | |||
+ | {{FarbigerRahmen | | ||
+ | Falls das Register <tt>r1</tt> verändert wird – was z.B. geschieht, wenn man Multiplikationsbefehle verwendet – dann muss am Ende des Templates das Register wieder auf 0 gesetzt werden, denn bei avr-gcc enthält dieses Register immer den Wert 0. | ||
+ | Das Register in die Clobber-Liste aufzunehen bleibt wirkungslos. Hat man es zerstört, | ||
+ | dann schreibt man ans Ende des Templates ein | ||
+ | clr __zero_reg__ | ||
+ | und stellt es dadurch wieder her. | ||
+ | }} | ||
+ | |||
+ | == asm goto mit C-Labels == | ||
+ | |||
+ | Ab Version 4.5 kennt GCC <tt>asm goto</tt>, mit dem ausgedrückt werden kann, dass der Codefluß des Assembler-Teils u.U. zu einem Stück C-Code springt: | ||
+ | |||
+ | asm goto ([[#Assembler-Template|asm-template]] : {{comment|Leer}} : [[#Operanden und Constraints|input-operand-list]] : [[#Clobbers|clobber-list]] : [[#asm goto mit C-Labels|C-labels]]); | ||
+ | |||
+ | Die Codefluß-Analyse des Compilers muss wissen, daß die angegebenen C-Code Sequenzen verwendet werden, damit sie nicht wegoptimiert werden. Solch ein Inline-Assembler mit <tt>goto</tt> darf keine Output-Operanden haben, denn der Compiler hat keine Möglichkeit, Code für die entsprechenden Output-Reloads zu erzeugen. | ||
+ | |||
+ | ;Beispiel: | ||
+ | |||
+ | extern void __attribute__((noreturn)) | ||
+ | panic (void); | ||
+ | |||
+ | char panic_if_x (char x) | ||
+ | { | ||
+ | asm goto ("cpse %0, __zero_reg__" "\n\t" | ||
+ | "%~jmp %x1" "\n\t" | ||
+ | "%~jmp %x2" | ||
+ | :: "r" (x) :: proceed, do_panic); | ||
+ | __builtin_unreachable(); | ||
+ | |||
+ | if (0) | ||
+ | { | ||
+ | do_panic: | ||
+ | panic(); | ||
+ | } | ||
+ | |||
+ | if (0) | ||
+ | { | ||
+ | proceed: | ||
+ | x++; | ||
+ | } | ||
+ | |||
+ | return x; | ||
+ | } | ||
+ | |||
+ | Im Beispiel wird der Inline-Assembler nur über die Marken <tt>do_panic</tt> oder <tt>proceed</tt> verlassen, weshalb der Code direkt danach unerreichbar ist. Dies wird der Codefluß-Analyse per <tt>__builtin_unreachable()</tt> mitgeteilt. Es wird folgender Code erzeugt: | ||
+ | |||
+ | panic_if_x: | ||
+ | {{comment|#APP}} | ||
+ | cpse r24,__zero_reg__ | ||
+ | rjmp .L2 | ||
+ | rjmp .L3 | ||
+ | {{comment|#NOAPP}} | ||
+ | .L3: | ||
+ | rcall panic | ||
+ | .L2: | ||
+ | subi r24,lo8(-(1)) | ||
+ | ret | ||
+ | |||
+ | =Vordefinierte Bezeichner und Makros= | ||
+ | Je nach Assembler, für den avr-gcc Code erzeugt, gibt es unterschiedliche vordefinierte Funktionen/Makros, die von Inline-Assembler aus verwendbar sind. | ||
+ | |||
+ | == GNU-Assembler == | ||
+ | |||
+ | :{| {{Blauetabelle}} | ||
+ | |+ '''Vordefinierte Makros und<br/>Relocatable Expression Modifiers'''<br/> | ||
+ | |- {{Hintergrund1}} | ||
+ | ! Bezeichner || Bedeutung | ||
|- | |- | ||
− | | <tt> | + | |<tt>__SP_L__</tt> || unteres Byte des Stack-Pointers, für <tt>in</tt> bzw. <tt>out</tt> |
− | + | ||
− | + | ||
− | + | ||
|- | |- | ||
− | | <tt> | + | |<tt>__SP_H__</tt> || oberes Byte des Stack-Pointers, für <tt>in</tt> bzw. <tt>out</tt> |
− | + | ||
− | + | ||
− | + | ||
|- | |- | ||
− | | <tt> | + | |<tt>__SREG__</tt> || Status-Register, für <tt>in</tt> bzw. <tt>out</tt> |
− | + | ||
− | + | ||
− | + | ||
|- | |- | ||
− | | <tt> | + | |<tt>__tmp_reg__</tt> || ein Register zur temporären Verwendung (<tt>r0</tt>) |
− | + | ||
− | + | ||
− | + | ||
|- | |- | ||
− | | <tt> | + | |<tt>__zero_reg__</tt> || ein Register, das 0 enthält (<tt>r1</tt>) |
− | + | ||
− | + | ||
− | + | ||
|- | |- | ||
− | | <tt> | + | |<tt>lo8(''const'')</tt> || Bits 0...7 der Konstanten <tt>''const''</tt> |
− | + | ||
− | + | ||
− | + | ||
|- | |- | ||
− | | <tt> | + | |<tt>hi8(''const'')</tt> || Bits 8...15 der Konstanten <tt>''const''</tt> |
− | | | + | |- valign="top" |
− | | <tt> | + | |<tt>hlo8(''const'')</tt><br/><tt>hh8(''const'')</tt> || Bits 16...23 der Konstanten <tt>''const''</tt> |
− | + | ||
|- | |- | ||
− | | <tt> | + | |<tt>hhi8(''const'')</tt> || Bits 24...31 der Konstanten <tt>''const''</tt> |
− | | <tt> | + | |- valign="top" |
− | + | |<tt>pm(''const'')</tt> || Konstante <tt>''const''</tt> durch 2 geteilt,<br/>zur Berechnung von Word-Adressen für <tt>icall</tt> und <tt>ijmp</tt>. | |
− | + | |- valign="top" | |
− | |- | + | |<tt>pm_lo8(''const'')</tt><br/><tt>pm_hi8(''const'')</tt><br/><tt>pm_hh8(''const'')</tt> |
− | | <tt> | + | | Abkürzung für <tt>lo8(pm(''const''))</tt><br/>Abkürzung für <tt>hi8(pm(''const''))</tt><br/>Abkürzung für <tt>hh8(pm(''const''))</tt> |
− | + | |- valign="top" | |
− | + | |<tt>gs(''label'')</tt> || Word-Adresse von <tt>''label''</tt>. Für Devices mit mehr als 128k Flash wird die Adresse eines Jump-Pads eingetragen. Der Linker erzeugt ein Jump-Pad, das einen direkten Sprung zu <tt>label</tt> erhält. <tt>gs(label)</tt> wird durch diese Adresse ersetzt. | |
− | | <tt> | + | |
− | + | ||
− | + | ||
− | + | ||
− | | | + | |
− | | <tt> | + | |
− | + | ||
− | + | ||
− | + | ||
|} | |} | ||
− | </ | + | |
+ | =Beispiele= | ||
+ | |||
+ | ==nop== | ||
+ | Mit den bisherigen Vorkenntnissen ist zu <tt>nop</tt> nicht viel zu sagen: | ||
+ | asm volatile ("nop"); | ||
+ | Oder als C-Makro: | ||
+ | #define nop() \ | ||
+ | asm volatile ("nop") | ||
+ | |||
+ | <tt>nop</tt> hat weder Input- noch Output-Operanden, und Register/RAM ändern sich natürlich nicht. Bei der Makro-Definition ist lediglich darauf zu achten, daß das Makro nicht mit einem <tt>;</tt> endet, damit man den C-Code wie gewohnt mit <tt>;</tt> schreiben kann: | ||
+ | <pre> | ||
+ | if (x) | ||
+ | nop(); | ||
+ | else | ||
+ | ... | ||
+ | </pre> | ||
+ | |||
+ | ==swap Nibbles== | ||
+ | Ein einfaches Beispiel für <tt>swap</tt> haben wir bereits oben kennen gelernt. Der Inline-Assembler dreht die Nibbles von <tt>foo</tt> um: | ||
+ | <pre> | ||
+ | unsigned char foo; | ||
+ | ... | ||
+ | asm ("swap %0" : "+r" (foo)); | ||
+ | </pre> | ||
+ | |||
+ | Wünschenswert wäre eher, <tt>swap</tt> wie eine normale C-Funktion verwenden zu können und hinzuschreiben: | ||
+ | a = swap(b); | ||
+ | ohne daß <tt>b</tt> seinen Wert ändert. Soll <tt>b</tt> geswappt werden, dann via | ||
+ | b = swap(b); | ||
+ | zudem soll das Argument ein Ausdruck sein können: | ||
+ | if (b == swap (b+1)) | ||
+ | ... | ||
+ | |||
+ | Dazu verwenden wir eine lokale Variable <tt>_x_</tt>, in die der ursprüngliche Wert <tt>x</tt> gesichert wird: | ||
+ | <pre> | ||
+ | #define swap(x) \ | ||
+ | ({ \ | ||
+ | unsigned char _x_ = (unsigned char) x; \ | ||
+ | asm ("swap %0" : "+r" (_x_)); \ | ||
+ | _x_; \ | ||
+ | }) | ||
+ | </pre> | ||
+ | alternativ könnten wir eine inline-Funktion definieren: | ||
+ | <pre> | ||
+ | static inline unsigned char swap (unsigned char x) | ||
+ | { | ||
+ | asm ("swap %0" : "+r" (x)); | ||
+ | return x; | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | ==swap Bytes== | ||
+ | Werden Zahlen zwischen verschiedenen Plattformen übertragen, kann es sein, daß diese unterschiedlich dargestellt werden: Das low-Byte kann in sich im unteren Byte befinden (AVR), es kann aber auch im oberen Byte sein. Ist das so, dann müssen die Werte beim Senden/Empfang umgewandelt werden, indem die Bytes getauscht werden. Liegt der 16-Bit-Wert in <tt>x_in</tt> und soll der konvertierte Wert nach <tt>x_out</tt> gespeichert werden, dann könnte man auf die Idee kommen, so etwas zu schreiben: | ||
+ | <pre> | ||
+ | asm ( | ||
+ | "mov %A0, %B1" "\n\t" | ||
+ | "mov %B0, %A1" | ||
+ | : "=r" (x_out) | ||
+ | : "r" (x_in) | ||
+ | ); | ||
+ | </pre> | ||
+ | Daraus könnte folgender Code entstehen: | ||
+ | <pre> | ||
+ | mov r24, r25 | ||
+ | mov r25, r24 | ||
+ | </pre> | ||
+ | Das ist offenbar Käse! Was ist passiert? | ||
+ | |||
+ | avr-gcc hat sich dazu entschieden, <tt>x_in</tt> in <tt>r25:r24</tt> anzulegen. Auch <tt>x_out</tt> wird in diesen Registern angelegt. Das ist erst mal in Ordnung, wenn <tt>x_in</tt> nach dem Inline nicht mehr gebraucht wird. | ||
+ | |||
+ | Allerdings wird das Inline nicht en bloc — also nicht zeitparallel — ausgeführt, sondern sequenziell. Bei ''gleichzeitiger'' Ausführung der beiden <tt>mov</tt>-Instruktionen wäre auch nichts dagegen zu sagen! Ein <tt>swap</tt>-Kommando z.B. tauscht die Nibbles ''gleichzeitig'', und der Input-Operand kann im gleichen Register leben wie der Output-Operand, wenn der Input nicht weiter verwendet wird. | ||
+ | |||
+ | Mit den beiden Bytes geht es aber nicht. Wir müssen daher kennzeichnen, daß sich <tt>x_out</tt> in einem Register befindet, das ''nur'' als Output dient, was durch das <tt>&</tt> in der Output-Constraint erreicht wird: | ||
+ | <pre> | ||
+ | asm ( | ||
+ | "mov %A0, %B1" "\n\t" | ||
+ | "mov %B0, %A1" | ||
+ | : "=&r" (x_out) | ||
+ | : "r" (x_in) | ||
+ | ); | ||
+ | </pre> | ||
+ | Damit erfolgt eine korrekte Registerzuordnung. <tt>x_out</tt> steht jetzt in <tt>r19:r18</tt> und überschneidet nich nicht mehr mit <tt>x_in</tt>: | ||
+ | <pre> | ||
+ | mov r18, r25 | ||
+ | mov r19, r24 | ||
+ | </pre> | ||
+ | |||
+ | Alternativ können wir wie bei [[#swap Nibbles|swap Nibbles]] eine lokale Variable verwenden, und alles als (inline-)Funktion machen. | ||
+ | |||
+ | |||
+ | |||
+ | == Zugriff auf SFRs == | ||
+ | Um auf SFRs zuzugreifen, können im asm-Template keine Defines aus <tt>avr/io.h</tt> verwendet werden, weil der Präprozessor nicht mehr über den erzeugten Assembler-Code läuft (er läuft vor dem Compilieren). Um nicht die hex-Codes des SFRs angeben zu müssen, kann man dem Inline die Adressen als Konstanten übergeben. Weil die Adressen RAM-Adressen sind, müssen sie in das <tt>_SFR_IO_ADDR</tt> Makro verpackt werden, um den Offset für den I/O-Bereich abzuziehen. <tt>tcnt1</tt> wird auf den Inhalt von <tt>TCNT1</tt> gesetzt: | ||
+ | <pre> | ||
+ | #include <avr/io.h> | ||
+ | ... | ||
+ | uint16_t tcnt1; | ||
+ | |||
+ | asm volatile ( | ||
+ | "in %A0, %1" "\n\t" | ||
+ | "in %B0, %1+1" | ||
+ | : "=r" (tcnt1) | ||
+ | : "M" (_SFR_IO_ADDR (TCNT1)) | ||
+ | ); | ||
+ | </pre> | ||
+ | wird umgesetzt zu | ||
+ | <pre> | ||
+ | in r24, 44 | ||
+ | in r25, 44+1 | ||
+ | </pre> | ||
+ | |||
+ | Ab avr-gcc 4.7 kann auch der Operand-Modofier <tt>%i</tt> verwendet werden, um eine RAM-Adresse, wie sie von <tt>avr/io.h</tt> definiert werden, als I/O-Adresse auszugeben: | ||
+ | <pre> | ||
+ | uint16_t tcnt1; | ||
+ | |||
+ | asm volatile ( | ||
+ | "in %A0, %i1" "\n\t" | ||
+ | "in %B0, %i1+1" | ||
+ | : "=r" (tcnt1) | ||
+ | : "n" (& TCNT1) | ||
+ | ); | ||
+ | </pre> | ||
+ | |||
+ | Das nur als Beispiel. Von C aus geht das natürlich auch mit | ||
+ | uint16_t tcnt1 = TCNT1; | ||
+ | |||
+ | == Zugriff aufs SRAM == | ||
+ | Auf bekannte globale Symbole kann man direkt von Assembler aus zugreifen: | ||
+ | <pre> | ||
+ | extern int einInt; | ||
+ | |||
+ | asm volatile ( | ||
+ | "lds %A0, einInt" "\n\t" | ||
+ | "lds %B0, einInt+1" | ||
+ | : "=r" (...) | ||
+ | ); | ||
+ | </pre> | ||
+ | ergibt | ||
+ | <pre> | ||
+ | lds r24, einInt | ||
+ | lds r25, einInt+1 | ||
+ | </pre> | ||
+ | alternativ ginge | ||
+ | <pre> | ||
+ | asm volatile ( | ||
+ | "lds %A0, %A1" "\n\t" | ||
+ | "lds %B0, %B1" | ||
+ | : "=r" (...) | ||
+ | : "m" (einInt) | ||
+ | ); | ||
+ | </pre> | ||
+ | |||
+ | Falls die Adresse eines Objekts zur Compilezeit bekannt ist, kann man auch die Adresse von C aus übergeben: | ||
+ | <pre> | ||
+ | struct foo_t { | ||
+ | int a[2], b[2]; | ||
+ | } foo; | ||
+ | |||
+ | ... | ||
+ | { | ||
+ | asm volatile ( | ||
+ | "lds %A0, %1" "\n\t" | ||
+ | "lds %B0, %1+1" | ||
+ | : "=r" (...) | ||
+ | : "i" (& foo.b[1]) | ||
+ | ); | ||
+ | } | ||
+ | </pre> | ||
+ | ergibt | ||
+ | <pre> | ||
+ | lds r24, einStruct+6 | ||
+ | lds r25, einStruct+6+1 | ||
+ | </pre> | ||
+ | |||
+ | Falls die Adresse zur Compilezeit nicht bekannt ist, muss sie natürlich in einem Adress- oder Basisregister übergeben werden: | ||
+ | <pre> | ||
+ | void blah (struct foo_t * pfoo) | ||
+ | { | ||
+ | asm volatile ( | ||
+ | "ld %A0, %a1" "\n\t" | ||
+ | "ldd %B0, %a1+1" | ||
+ | : "=&r" (...) | ||
+ | : "b" (& pfoo->b[1]) | ||
+ | ); | ||
+ | } | ||
+ | </pre> | ||
+ | ergibt | ||
+ | <pre> | ||
+ | ld r24, Z | ||
+ | ldd r25, Z+1 | ||
+ | </pre> | ||
+ | Auch hier muss der Output-Operand mit einem <tt>&</tt> gekennzeichnet werden, damit er nicht ins gleiche Register geladen wird wie die Adresse. | ||
+ | |||
+ | = Labels und Schleifen = | ||
+ | Für Labels in Sprüngen und Schleifen können keine festen Bezeichner verwendet werden, weil ein Label, der via Makro oder inline-Funktion in den Code eingefügt wird, nicht mehrfach vorkommen darf. Dazu kann man sich durch Einfügen von <tt>"%="</tt> Labels zusammenbauen, etwa <tt>L_a%=</tt>, <tt>L_b%=</tt>, etc. Das <tt>"%="</tt> wird durch eine für die Übersetzungseinheit und den Code-Schnippsel eindeutige Zahl ersetzt. Die obigen Sequenzen könnten also z.B. umgesetzt werden als <tt>L_a14</tt> und <tt>L_b14</tt>, wenn sie im gleichen Schnippsel stehen. | ||
+ | |||
+ | Etwas bequemer ist die Verwendung einer Ziffer als Label. Beim Sprung gibt man direkt hinter der Ziffer an, in welche Richtung das Label gesucht wird. Ist das Label <tt>''n''</tt>, dann sucht und springt | ||
+ | *<tt>''n''b</tt> zurück (backward) | ||
+ | *<tt>''n''f</tt> nach vorne (forward) | ||
+ | |||
+ | Es wird zum nächsten auffindbaren Label in der angegebenen Richtung gesprungen. | ||
+ | |||
+ | ==Bits zählen== | ||
+ | |||
+ | Dieses Assembler-Schnippsel zählt die Anzahl der gesetzten Bits in einem Byte <tt>eingabe</tt>. Die Eingabe wird nach rechts ins Carry geschoben, und das Carry zum Ergebnis dazu addiert. | ||
+ | <pre> | ||
+ | static inline unsigned char count_bits (unsigned char eingabe) | ||
+ | { | ||
+ | unsigned char count; | ||
+ | |||
+ | asm ( | ||
+ | "clr %0" "\n" | ||
+ | "0:" "\n\t" | ||
+ | "lsr %1" "\n\t" | ||
+ | "adc %0, __zero_reg__" "\n\t" | ||
+ | "tst %1" "\n\t" | ||
+ | "brne 0b" | ||
+ | : "=&r" (count), "+r" (eingabe) | ||
+ | ); | ||
+ | |||
+ | return count; | ||
+ | } | ||
+ | </pre> | ||
+ | Damit kann man sich ein Parity bauen: | ||
+ | #define parity(x) (count_bits(x) & 1) | ||
+ | und hätte eine schlankere (aber etwas langsamere) Parity-Implementierung als in <tt>avr/parity.h</tt>. Ein | ||
+ | if (parity(foo)) | ||
+ | ... | ||
+ | wird in Assembler dann zu (<tt>r24 = eingabe</tt>, <tt>r25 = count</tt>): | ||
+ | <pre> | ||
+ | lds r24,foo | ||
+ | /* #APP */ | ||
+ | clr r25 | ||
+ | 0: | ||
+ | lsr r24 | ||
+ | adc r25, __zero_reg__ | ||
+ | tst r24 | ||
+ | brne 0b | ||
+ | /* #NOAPP */ | ||
+ | sbrs r25,0 | ||
+ | rjmp .L1 | ||
+ | ... | ||
+ | </pre> | ||
+ | |||
+ | =Fallstricke= | ||
+ | * Um die Sonderzeichen <tt>"%~"</tt> und <tt>"%="</tt> benutzen zu können, muss bei parameterlosen Inline Assembler dem Template ein Doppelpunkt folgen: | ||
+ | :<pre>asm volatile ("%~call some_function" :);</pre> | ||
+ | * Steht das <tt>asm</tt> an oberster Ebene — also außerhalb einer Funktion — ist weiter zu beachten: | ||
+ | ** Jedes <tt>%</tt> wird als solches ausgegeben, und es sind keine Operanden erlaubt. | ||
+ | ** Falls die Reihenfolge von <tt>asm</tt> und Funktionen beibehalten werden soll, muß mit <tt>-fno-toplevel-reorder</tt> compiliert werden. | ||
+ | |||
+ | =Quellen= | ||
+ | |||
+ | ; Dokumentation | ||
+ | |||
+ | * AVR-LibC: [http://nongnu.org/avr-libc/user-manual/inline_asm.html ''Inline Assembler Cookbook''] | ||
+ | * GCC Wiki: [http://gcc.gnu.org/wiki/avr-gcc ''avr-gcc''] | ||
+ | * GCC: [http://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html ''Assembler Instructions with C Expression Operands''] | ||
+ | * GCC: [http://gcc.gnu.org/onlinedocs/gcc/Constraints.html ''Constraints for <tt>asm</tt> Operands''] | ||
+ | * GCC: [http://gcc.gnu.org/onlinedocs/gcc/Explicit-Reg-Vars.html ''Variables in Specified Registers''] | ||
+ | * GCC Internals: [http://gcc.gnu.org/onlinedocs/gccint/Constraints.html ''Operand Constraints''] | ||
+ | |||
+ | |||
+ | ;GCC-Quellen: | ||
+ | |||
+ | * [http://gcc.gnu.org/viewcvs/gcc/trunk/gcc/config/avr/avr.md?content-type=text%2Fplain&view=co ./gcc/config/avr/avr.md] | ||
+ | * [http://gcc.gnu.org/viewcvs/gcc/trunk/gcc/config/avr/constraints.md?content-type=text%2Fplain&view=co ./gcc/config/avr/constraints.md] | ||
+ | |||
+ | =Siehe auch= | ||
+ | * [[AVR Assembler Einführung]] | ||
+ | * [[:Kategorie:Quellcode Assembler_AVR|Quellcode Assembler]] | ||
+ | * [[Sourcevergleich#GCC (Assembler einfügen)]] | ||
+ | * [[avr-gcc]] | ||
+ | |||
+ | [[Kategorie:Microcontroller]] | ||
+ | [[Kategorie:Software]] | ||
+ | [[Kategorie:Grundlagen]] | ||
+ | [[Kategorie:Quellcode_Assembler_AVR]] | ||
+ | [[Kategorie:Quellcode C]] |
Aktuelle Version vom 25. Oktober 2014, 12:38 Uhr
In C versteht man unter Inline Assembler die Möglichkeit, direkt Assembler-Befehle in den Code einzufügen bzw. die eingefügten Assembler-Befehle selbst.
Neben den einzufügenden Befehlen muss beschrieben werden, welche Nebeneffekte die Befehle auf die Maschine haben und wo/wie Parameter übergeben werden, bzw. wie die Zuordnung von Variablen zu den Registern ist. Diese Beschreibung ist notwendig weil der Compiler keine Vorstellung davon hat, welche Effekte und Nebenwirkungen die Assembler-Kommandos auf Register, Variablen und Speicher haben, denn der Assembler-Schnippsel ist für den Compiler lediglich eine "Black Box".
Obgleich das dazu verwendete Schlüsselwort __asm zum ANSI-C-Standard gehört, ist dies in jedem C-Compiler anders implementiert. Das gilt insbesondere für die Schnittstellenbeschreibung Variablen/Register. Dieser Artikel bezieht sich auf den Inline Assembler von avr-gcc. Viele Erklärungen aus diesem Artikel sind auch gültig für Inline-Assembler anderer C-Compiler der gcc-Familie. Instruktionen und Registerklassen werden sich aber unterscheiden, weil diese natürlich abhängig sind vom verwendeten Controller bzw. der Controller-Familie.
Inhaltsverzeichnis
Assembler oder Inline-Assembler?
- Assembler
- Längere und komplexere Code-Stücke sind komfortabler direkt in Assembler auszudrücken. Dazu schreibt man Assembler-Funktionen und ruft diese von C aus auf. Natürlich können auch C-Funktionen von Assembler aus aufgerufen werden. Zudem kann man im Assembler den C-Präprozessor nutzen, was bei Inline-Assembler nur auf C-Ebene geht. Der Build-Prozess wird allerdings komplizierter, da extra asm-Dateien übersetzt werden müssen. Ein typischer Fall ist es, eine komplette ISR in Assembler zu schreiben.
- Inline-Assembler
- Mit Inline-Assembler kann man kleine Assembler-Stückchen direkt in den C-Code einbetten. Es muss dann keine Assembler-Funktion aufgerufen werden. Dies kann von der Registerverwendung her deutlich günstiger sein, da gcc genau weiß, welche Register gebraucht werden und welche nicht.
- Eine (Assembler-)Funktion ist hingegen eine Black Box, bei der von der Standard-Registerverwendung ausgegangen werden muss, auch wenn weniger Register in der Funktion verwendet werden. Ein Funktionsaufruf bedeutet also meistens einen Laufzeit-Overhead im Vergleich zu einem Inline-Code.
- Bei mehrfacher Verwendung einer längeren Codesequenz ist eine Funktion jedoch sparsamer im Flash-Verbrauch. Legt man eine Funktion als C-Funktion an und ihren Body als Inline-Assembler (eine s.g. Stub-Funktion, von engl. Stub = Stumpf), dann übernimmt gcc das Verwalten von Funktions-Argumenten, return-Wert, etc. und man brauch sich nicht selber um die Aufruf-Konvention zu kümmern. Auch innerhalb einer Funktion kann C mit Assembler gemischt werden.
- Ein Vorteil von Inline-Assembler ist, daß eine C-Funktion, die Inline-Assembler enthält, vom C-Compiler geinlinet werden kann. Dies ist mit einer reinen Assembler-Funktion nicht möglich.
Begriffe
- Assembler-Template
- Das Template (Schablone) ist ein statischer, konstanter String im Sinne von C. Es enthält die Assembler-Befehle sowie Platzhalter, in deren Stelle später die Operanden treten
- Constraint
- Die Constraints (Nebenbedingungen) beschreiben Einschränkungen an die zu verwendeten Register. Dies ist notwendig, da nicht alle Maschinenbefehle auf alle Register anwendbar sind
- Clobber-List
- Das ist eine Liste von Registern, deren Inhalt durch den Inline-Assembler zerstört wird
Syntax und Semantik
Das Schlüsselwort, um eine Inline-Assembler Sequenz einzuleiten, ist __asm (ANSI). Oft ist auch asm oder __asm__ verwendbar. Um zu kennzeichnen, daß die Sequenz keinesfalls wegoptimiert werden darf – etwa dann, wenn der Assembler keine Wirkung auf C-Variablen hat – wird dem asm ein volatile bzw. __volatile nachgestellt. Danach folgen in runden Klammern die durch : getrennten Abschnitte des Inline-Assemblers:
asm volatile (asm-template : output-operand-list : input-operand-list : clobber-list);
Abschnitte, die leer sind, können auch weggelassen werden, wenn dahinter kein weiterer Abschnitt folgt:
asm volatile (asm-template);
Oder, wenn weder Input- noch Output-Operanden gebraucht werden, aber Register oder Speicher verändert werden:
asm volatile (asm-template ::: clobber-list);
Aus Compiler-Sicht werden die Assembler-Befehle im Template parallel, also gleichzeitig ausgeführt! Dies ist zu bedenken, wenn Register sowohl als Input als auch als Output verwendet werden.
Ab Version 4.5 kennt GCC asm goto, mit dem ausgedrückt werden kann, dass der Codefluß des Assembler-Teils u.U. zu einem Stück C-Code springt:
asm goto (asm-template : /* Leer */ : input-operand-list : clobber-list : C-labels);
Assembler-Template
Im Template stehen die durch Zeilenumbrüche getrennten Assembler-Befehle. Das Template kann zudem %-Ausdrücke als Platzhalter enthalten, welche durch die Operanden ersetzt werden. Dabei bezieht sich %0 auf den ersten Operanden, %1 auf den zweiten Operanden, etc. Die Operanden selbst werden im zweiten und dritten Abschnitt des Templates als Komma-getrennte Liste angegeben. Diese Ersetzung findet jedoch nur dann statt, wenn das asm nicht nur aus einem String besteht:
asm ("10% mehr"); /* "10% mehr" */ asm ("10%% mehr" :); /* "10% mehr" */
Ein Platzhalter kann zusätzlich einen einbuchstabigen Modifier enthalten, um den Operanden in printf-ähnlicher Manier in einem speziellen Format darzustellen. Wird z.B. ein Wert ab Register r28 (dem Y-Register) gehalten, dann wären folgende Ersetzungen denkbar (als erstes Argument):
- %0 → r28
- %A0 → r28
- %B0 → r29
- %C0 → r30
- %D0 → r31
- %a0 → y
Im einfachsten Falle enthält das Templater nur einen Befehl:
"nop"
oder sogar garkeinen Befehl oder lediglich einen Kommentar:
"; ein Kommentar"
Tabelle: asm-Platzhalter und ihre Modifier, Sonderzeichen Platzhalter wird ersetzt durch AVR-spezifisch %n Wird ersezt durch Argument n mit n = 0...9 %An das erste (untere) Register des Arguments n (Bits 0...7) X %Bn das zweite Register des Arguments n (Bits 8...15) %Cn das dritte Register des Arguments n (Bits 16...23) %Dn das vierte Register des Arguments n (Bits 24...31) %an Ausgabe des Arguments als Adress-Register,
also als x, y bzw. z. Erlaubt zusammen mit Constraint b, e, x, y, zX %in Ab 4.7. Ausgabe einer RAM-Adresse als I/0-Adresse. Beispiel: 0x30 wird auf Xmega-Controllern als 0x30 ausgegeben und auf nicht-Xmega als 0x10. X %rn Ab 4.8. Ausgabe eines Registers ohne die Register-Präfix "r". Damit kann per Inline-Assembler auf 64-Bit Variablen zugegriffen werden, z.B. kann mit "clr %r0+7" das High-Byte gelöscht werden. X %Tn%Tm Ab 4.7. Gibt einen Doppel-Operanden aus wie er zum Beispiel in BLD oder BST benötigt wird, d.h. durch ein Komma getrennte Register- und Bitnummer. Das erste %T erhält ein Register und speichert es zwischen bis zum nächsten %T, das die konstante Bitnummer m enthält. Die Ausgabe erfolgt erst mit dem zweiten %T: Beispielsweite kann durch folgende Zeile das High-Bit von var gelöscht werden: asm ("clt $ bld %T[v]%T[b]" : [v] "+r" (var) : [b] "n" (8*(sizeof var)-1));
X %Tn%tm Ab 4.7. Funktioniert wie %Tn%Tm, es wird aber nur das zum Bit m gehörende Register ausgegeben, d.h. weder Komma noch Bitnummer. X %xn Ab 4.5. Gibt ein Label ohne Operand-Modifier gs() aus. X %~ wird auf AVR mit Flash bis max. 8kiByte durch ein r ersetzt, ansonsten bleibt es leer.
Zum Aufbau von Sprungbefehlen, etwa "%~call foo"X %! Ab 4.4. Wird auf AVR mit Flash ab 128kiByte durch ein e ersetzt, ansonsten bleibt es leer.
Zum Aufbau von indirekten Sprungbefehlen, etwa "%!icall"X %= eine für dieses asm-Template und die Übersetzungseinheit eindeutige Zahl.
Zum Aufbau lokaler Sprungmarken.Sequenz wird ersetzt durch Sonderzeichen AVR-spezifisch %% das %-Zeichen selbst \n ein Zeilenumbruch zum Trennen mehrerer asm-Befehle/Zeilen $ trennt mehrere Befehle in der gleichen Zeile X \t ein TAB, zur Übersichtlichkeit im erzeugten asm \" ein " wird eingefügt \\ das \-Zeichen selbst Kommentar Beschreibung AVR-spezifisch ; Text einzeiliger Kommentar bis zum Ende des Templates bzw. nächsten Zeilenumbruch X /* Text */ mehrzeiliger Kommentar wie in C
Operanden und Constraints
Ein Operand besteht aus der Angabe des Constraint-Strings (also der Registerklasse und Kennzeichnung, ob es sich um einen Output-Operanden handelt) und dahinter in runden Klammern der C-Ausdruck, der in Register der angegebenen Klasse geladen werden soll. Die Operanden werden mit 0 beginnend von links nach rechts durchnumeriert und über diese Nummer angesprochen, um sie ins Assembler-Schnippsel einzufügen.
Mehrere Input- bzw. Output-Operanden werden durch Komma getrennt.
Tabelle: Constraints und ihre Bedeutung Constraint Register Wertebereich Constraint Konstante Wertebereich a einfache obere Register r16...r23 G Floatingpoint-Konstante 0.0 b Pointer-Register y, z i Konstante, entspricht "sn" d obere Register r16...r31 s symbolischer Wert e Pointer-Register x, y, z n Wert bekannt zur Compilezeit l untere Register r0...r15 I positive 6-Bit-Konstante 0...63 q Stack-Pointer SPH:SPL J negative 6-Bit Konstante -63...0 r ein Register r0...r31 M 8-Bit Konstante 0...255 t Scratch-Register r0 w obere Register-Paare r24, r26, r28, r30 Constraint Memory Wertebereich x Pointer-Register X x (r27:r26) m Memory y Pointer-Register Y y (r29:r28) z Pointer-Register Z z (r31:r30) 0...9 Identisch mit dem angegebenen Operanden
Wird verwendet, wenn ein Operand sowohl als Input
als auch als Output dient, um sich auf diesen
Operanden zu beziehen
Tabelle: Constraint Modifier Modifier Bedeutung = der Operand ist Output-Operand & diesen Operanden nicht als Input-Operanden verwenden,
sondern nur als Output-Operand+ dieser Operanden ist Input- und Output-Operand
Ein Input-Operand könnte also so aussehen, wobei foo eine C-Variable ist. Als Register dient ein (je nach Typ von foo auch mehrere) obere Register, irgendwo von r16 bis r31 (Constraint "d"):
"d" (foo)
In den Klammern kann ein beliebiger, gültiger C-Ausdruck stehen, der beim folgenden Beispiel in irgendeinem Register landet (Constraint "r"), ohne weitere Einschränkung an das Register:
"r" ((foo >= 0) ? foo : -foo)
Um einen Operanden als Output-Operanden zu kennzeichnen, wird dem Constraint ein "=" vorangestellt. Soll foo ein Output-Operand sein, der in den Registern r0...r15 landen soll (Constraint "l", sieht es so aus. Dabei muss foo ein sogenannter Lvalue sein, also ein Wert, dem etwas zugewiesen werden kann:
"=l" (foo)
Ist foo sowohl Input als auch Output, schreibt man foo als Output- und als Input-Operand hin. In der Input-Constraint bezieht man sich dann auf die Operanden-Nummer von foo. Hier ein komplettes Beispiel, das die Nibbles von foo tauscht. Weil swap auf alle Register anwendbar ist, kann als Registerklasse "r" genommen werden:
unsigned char foo; ... asm ("swap %0" : "=r" (foo) : "0" (foo));
foo als Input-Operand soll im gleichen Register liegen wie foo als Output-Operand. Daher wird als Constraint "0" angegeben, d.h. es wird ins gleiche Register geladen wie der Operand Numero 0 "r" (foo).
Benannte Constraints
Um das Assembler-Schnippsel besser lesbar zu halten, kann man Constraints einen Namen geben:
asm ("swap %[bar]" : [bar] "+r" (foo));
Damit ist der Assembler auch unabhängig von der Reihenfolge der Operanden. Das macht die Anpassung, wenn ein neuer Operand hinzukommt, wesentlich einfacher und den Schnippsel zudem besser lesbar.
Hier wird bar als Alias für die C-Variable foo benutzt; es könnte aber auch jeder andere gültige C-Bezeichner sein, also insbesondere auch foo.
Instruktionen und Constraints
Die folgende Tabelle enhält eine Auflistung von AVR-Instruktionen und dazu passende Argumente bzw. Constraints. Nicht alle Shorthands sind in der Tabelle enthalten, so ist "clr Rn" nur eine Abkürzung für "eor Rn, Rn", ähnliches gilt für den Zoo von Instruktionen rund um das SREG wie branch, bit set, bit clear, etc., die im Endeffekt auf nur vier Instruktionen abbilden. Instruktionen wie nop, die keine Argumente brauchen, sind ebenfalls nicht in der Tabelle enthalten. Gleiches gilt für den Krypto-Befehl des, für die keine Constraint verfügbar ist.
"load" ist ein "load from SRAM"
"store" ist "store to SRAM"
Tabelle: Übersicht AVR-Instruktionen und passende Constraints Mnemonic Constraint Bedeutung Mnemonic Constraint Bedeutung adc r,r add with carry add r,r add adiw w,I add immediate to word and r,r and andi d,M and with immediate asr r arithmetic shift right bclr I bit clear in SREG bld r,I bit load from T brbc I,label branch if bit in SREG clear brbs I,label branch if bit in SREG set bset I bit set in SREG bst r,I bit store from T cbi I,I clear bit in I/O com r complement cp r,r compare cpc r,r compare with carry cpi d,M compare against immediate cpse r,r compare, skip if equal dec r decrement [e]lpm r,z load from program memory1 eor r,r exclusive-or fmul* a,a fractional multiply in r,I input from I/O inc r increment lac z,r load and clear lat z,r load and toggle las z,r load and set ld r,e load indirect ld r,e+ load indirect, post-increment ld r,-e load indirect, pre-decrement ldd r,b+I load indirect with displacement ldi d,M load immediate lds r,label load direct from SRAM lpm t,z load from program memory1 lsr r logical shift right mov r,r move movw r,r move word mul r,r multiply unsigned muls d,d multiply signed mulsu a,a multiply signed/unsigned neg r negate or r,r or ori d,M or with immediate out I,r output to I/O pop r pop from Stack push r push to Stack ror r rotate right sbc r,r subtract with carry sbci d,M subtract with carry immediate sbic I,I skip if bit in I/O clear sbis I,I skip if bit in I/O set sbi I,I set Bit in I/O sbiw w,I subtract immediate from word sbrc r,I skip if bit in register clear sbrs r,I skip if bit in register set st e,r store indirect st e+,r store indirect, post-increment st -e,r store indirect, pre-decrement std b,r store indirect with displacement sts label,r store direct sub r,r subtract subi d,M subtract immediate swap r swap nibbles xch z,r exchange
1 Je nach Derivat werden die folgenden lpm-Ausprägungen unterstützt oder nicht:
- lpm
- lpm r,z bzw. lpm r,z+
- elpm
- elpm r,z bzw. elpm r,z+
Hier noch ein paar der gebräuchlichsten Shorthands und deren Abbildung auf "Basis"-Instruktionen:
Tabelle: Shorthands Shorthand gleichbedeutend mit Bedeutung Shorthand gleichbedeutend mit Bedeutung rol r adc r,r rotate left cbr r,M andi r, ~M clear bits in reg clr r eor r,r clear reg to zero lsl r add r,r logical shift left sbr d,M ori d, M set bits in reg ser d ldi d, 0xff set all bits in reg tst r and r,r test against zero
Clobbers
In der Komma-getrennten Clobber-Liste kann man angeben, welche Register durch den Inline-Assembler ihren Wert ändern. Ändern z.B. r2 und r3 ihre Werte, dann ist die Clobber-Liste
"r2", "r3"
Wird schreibend auf das RAM zugegriffen, dann muss man das auch mitteilen, damit RAM-Inhalte, die sich evtl. in Registern befinden, nach dem Inline-Assembler neu gelesen werden. Der Clobber dafür ist:
"memory"
Beispiel:
Es soll ein Inline-Assembler geschrieben werden, das den Inhalt zweier aufeinanderfolgender Speicherstellen austauscht. Die Adresse soll in addr stehen. Sie ist Input-Operand und muss in Register X, Y oder Z stehen, um den ld bzw. st-Befehl anwenden zu können. Die passende Constraint ist also "e". Nach der Sequenz liegt addr unverändert vor.
asm volatile ( "ld r2, %a0+" "\n\t" "ld r3, %a0" "\n\t" "st %a0, r2" "\n\t" "st -%a0, r3" : /* keine Output-Operanden */ : "e" (addr) : "r2", "r3", "memory" );
avr-gcc entscheidet sich dazu, das Z-Register für addr zu verwenden:
ld r2, Z+ ld r3, Z st Z, r2 st -Z, r3
Günstiger ist es jedoch, dem Compiler auch die Entscheidung zu überlassen, welche(s) Register als Hilfsregister verwendet werden sollen. Ein Register kann __tmp_reg__ sein, für das zweite legen wir eine lokale 8-Bit-Variable hilf an:
{ char hilf; asm volatile ( "ld __tmp_reg__, %a1+" "\n\t" "ld %0, %a1" "\n\t" "st %a1, __tmp_reg__" "\n\t" "st -%a1, %0" : "=&r" (hilf) : "e" (addr) : "memory" ); }
__tmp_reg__ (also r0) brauch nicht in die Clobber-Liste aufgenommen zu werden. Um das zweite benötigte Register (hier r24, in dem hilf lebt) kümmert sich avr-gcc und sichert es, falls nötig
ld r0, Z+ ld r24, Z st Z, r0 st -Z, r24
Falls das Register r1 verändert wird – was z.B. geschieht, wenn man Multiplikationsbefehle verwendet – dann muss am Ende des Templates das Register wieder auf 0 gesetzt werden, denn bei avr-gcc enthält dieses Register immer den Wert 0. Das Register in die Clobber-Liste aufzunehen bleibt wirkungslos. Hat man es zerstört, dann schreibt man ans Ende des Templates ein
clr __zero_reg__
und stellt es dadurch wieder her.
asm goto mit C-Labels
Ab Version 4.5 kennt GCC asm goto, mit dem ausgedrückt werden kann, dass der Codefluß des Assembler-Teils u.U. zu einem Stück C-Code springt:
asm goto (asm-template : /* Leer */ : input-operand-list : clobber-list : C-labels);
Die Codefluß-Analyse des Compilers muss wissen, daß die angegebenen C-Code Sequenzen verwendet werden, damit sie nicht wegoptimiert werden. Solch ein Inline-Assembler mit goto darf keine Output-Operanden haben, denn der Compiler hat keine Möglichkeit, Code für die entsprechenden Output-Reloads zu erzeugen.
- Beispiel
extern void __attribute__((noreturn)) panic (void); char panic_if_x (char x) { asm goto ("cpse %0, __zero_reg__" "\n\t" "%~jmp %x1" "\n\t" "%~jmp %x2" :: "r" (x) :: proceed, do_panic); __builtin_unreachable(); if (0) { do_panic: panic(); } if (0) { proceed: x++; } return x; }
Im Beispiel wird der Inline-Assembler nur über die Marken do_panic oder proceed verlassen, weshalb der Code direkt danach unerreichbar ist. Dies wird der Codefluß-Analyse per __builtin_unreachable() mitgeteilt. Es wird folgender Code erzeugt:
panic_if_x: /* #APP */ cpse r24,__zero_reg__ rjmp .L2 rjmp .L3 /* #NOAPP */ .L3: rcall panic .L2: subi r24,lo8(-(1)) ret
Vordefinierte Bezeichner und Makros
Je nach Assembler, für den avr-gcc Code erzeugt, gibt es unterschiedliche vordefinierte Funktionen/Makros, die von Inline-Assembler aus verwendbar sind.
GNU-Assembler
Vordefinierte Makros und
Relocatable Expression Modifiers
Bezeichner Bedeutung __SP_L__ unteres Byte des Stack-Pointers, für in bzw. out __SP_H__ oberes Byte des Stack-Pointers, für in bzw. out __SREG__ Status-Register, für in bzw. out __tmp_reg__ ein Register zur temporären Verwendung (r0) __zero_reg__ ein Register, das 0 enthält (r1) lo8(const) Bits 0...7 der Konstanten const hi8(const) Bits 8...15 der Konstanten const hlo8(const)
hh8(const)Bits 16...23 der Konstanten const hhi8(const) Bits 24...31 der Konstanten const pm(const) Konstante const durch 2 geteilt,
zur Berechnung von Word-Adressen für icall und ijmp.pm_lo8(const)
pm_hi8(const)
pm_hh8(const)Abkürzung für lo8(pm(const))
Abkürzung für hi8(pm(const))
Abkürzung für hh8(pm(const))gs(label) Word-Adresse von label. Für Devices mit mehr als 128k Flash wird die Adresse eines Jump-Pads eingetragen. Der Linker erzeugt ein Jump-Pad, das einen direkten Sprung zu label erhält. gs(label) wird durch diese Adresse ersetzt.
Beispiele
nop
Mit den bisherigen Vorkenntnissen ist zu nop nicht viel zu sagen:
asm volatile ("nop");
Oder als C-Makro:
#define nop() \ asm volatile ("nop")
nop hat weder Input- noch Output-Operanden, und Register/RAM ändern sich natürlich nicht. Bei der Makro-Definition ist lediglich darauf zu achten, daß das Makro nicht mit einem ; endet, damit man den C-Code wie gewohnt mit ; schreiben kann:
if (x) nop(); else ...
swap Nibbles
Ein einfaches Beispiel für swap haben wir bereits oben kennen gelernt. Der Inline-Assembler dreht die Nibbles von foo um:
unsigned char foo; ... asm ("swap %0" : "+r" (foo));
Wünschenswert wäre eher, swap wie eine normale C-Funktion verwenden zu können und hinzuschreiben:
a = swap(b);
ohne daß b seinen Wert ändert. Soll b geswappt werden, dann via
b = swap(b);
zudem soll das Argument ein Ausdruck sein können:
if (b == swap (b+1)) ...
Dazu verwenden wir eine lokale Variable _x_, in die der ursprüngliche Wert x gesichert wird:
#define swap(x) \ ({ \ unsigned char _x_ = (unsigned char) x; \ asm ("swap %0" : "+r" (_x_)); \ _x_; \ })
alternativ könnten wir eine inline-Funktion definieren:
static inline unsigned char swap (unsigned char x) { asm ("swap %0" : "+r" (x)); return x; }
swap Bytes
Werden Zahlen zwischen verschiedenen Plattformen übertragen, kann es sein, daß diese unterschiedlich dargestellt werden: Das low-Byte kann in sich im unteren Byte befinden (AVR), es kann aber auch im oberen Byte sein. Ist das so, dann müssen die Werte beim Senden/Empfang umgewandelt werden, indem die Bytes getauscht werden. Liegt der 16-Bit-Wert in x_in und soll der konvertierte Wert nach x_out gespeichert werden, dann könnte man auf die Idee kommen, so etwas zu schreiben:
asm ( "mov %A0, %B1" "\n\t" "mov %B0, %A1" : "=r" (x_out) : "r" (x_in) );
Daraus könnte folgender Code entstehen:
mov r24, r25 mov r25, r24
Das ist offenbar Käse! Was ist passiert?
avr-gcc hat sich dazu entschieden, x_in in r25:r24 anzulegen. Auch x_out wird in diesen Registern angelegt. Das ist erst mal in Ordnung, wenn x_in nach dem Inline nicht mehr gebraucht wird.
Allerdings wird das Inline nicht en bloc — also nicht zeitparallel — ausgeführt, sondern sequenziell. Bei gleichzeitiger Ausführung der beiden mov-Instruktionen wäre auch nichts dagegen zu sagen! Ein swap-Kommando z.B. tauscht die Nibbles gleichzeitig, und der Input-Operand kann im gleichen Register leben wie der Output-Operand, wenn der Input nicht weiter verwendet wird.
Mit den beiden Bytes geht es aber nicht. Wir müssen daher kennzeichnen, daß sich x_out in einem Register befindet, das nur als Output dient, was durch das & in der Output-Constraint erreicht wird:
asm ( "mov %A0, %B1" "\n\t" "mov %B0, %A1" : "=&r" (x_out) : "r" (x_in) );
Damit erfolgt eine korrekte Registerzuordnung. x_out steht jetzt in r19:r18 und überschneidet nich nicht mehr mit x_in:
mov r18, r25 mov r19, r24
Alternativ können wir wie bei swap Nibbles eine lokale Variable verwenden, und alles als (inline-)Funktion machen.
Zugriff auf SFRs
Um auf SFRs zuzugreifen, können im asm-Template keine Defines aus avr/io.h verwendet werden, weil der Präprozessor nicht mehr über den erzeugten Assembler-Code läuft (er läuft vor dem Compilieren). Um nicht die hex-Codes des SFRs angeben zu müssen, kann man dem Inline die Adressen als Konstanten übergeben. Weil die Adressen RAM-Adressen sind, müssen sie in das _SFR_IO_ADDR Makro verpackt werden, um den Offset für den I/O-Bereich abzuziehen. tcnt1 wird auf den Inhalt von TCNT1 gesetzt:
#include <avr/io.h> ... uint16_t tcnt1; asm volatile ( "in %A0, %1" "\n\t" "in %B0, %1+1" : "=r" (tcnt1) : "M" (_SFR_IO_ADDR (TCNT1)) );
wird umgesetzt zu
in r24, 44 in r25, 44+1
Ab avr-gcc 4.7 kann auch der Operand-Modofier %i verwendet werden, um eine RAM-Adresse, wie sie von avr/io.h definiert werden, als I/O-Adresse auszugeben:
uint16_t tcnt1; asm volatile ( "in %A0, %i1" "\n\t" "in %B0, %i1+1" : "=r" (tcnt1) : "n" (& TCNT1) );
Das nur als Beispiel. Von C aus geht das natürlich auch mit
uint16_t tcnt1 = TCNT1;
Zugriff aufs SRAM
Auf bekannte globale Symbole kann man direkt von Assembler aus zugreifen:
extern int einInt; asm volatile ( "lds %A0, einInt" "\n\t" "lds %B0, einInt+1" : "=r" (...) );
ergibt
lds r24, einInt lds r25, einInt+1
alternativ ginge
asm volatile ( "lds %A0, %A1" "\n\t" "lds %B0, %B1" : "=r" (...) : "m" (einInt) );
Falls die Adresse eines Objekts zur Compilezeit bekannt ist, kann man auch die Adresse von C aus übergeben:
struct foo_t { int a[2], b[2]; } foo; ... { asm volatile ( "lds %A0, %1" "\n\t" "lds %B0, %1+1" : "=r" (...) : "i" (& foo.b[1]) ); }
ergibt
lds r24, einStruct+6 lds r25, einStruct+6+1
Falls die Adresse zur Compilezeit nicht bekannt ist, muss sie natürlich in einem Adress- oder Basisregister übergeben werden:
void blah (struct foo_t * pfoo) { asm volatile ( "ld %A0, %a1" "\n\t" "ldd %B0, %a1+1" : "=&r" (...) : "b" (& pfoo->b[1]) ); }
ergibt
ld r24, Z ldd r25, Z+1
Auch hier muss der Output-Operand mit einem & gekennzeichnet werden, damit er nicht ins gleiche Register geladen wird wie die Adresse.
Labels und Schleifen
Für Labels in Sprüngen und Schleifen können keine festen Bezeichner verwendet werden, weil ein Label, der via Makro oder inline-Funktion in den Code eingefügt wird, nicht mehrfach vorkommen darf. Dazu kann man sich durch Einfügen von "%=" Labels zusammenbauen, etwa L_a%=, L_b%=, etc. Das "%=" wird durch eine für die Übersetzungseinheit und den Code-Schnippsel eindeutige Zahl ersetzt. Die obigen Sequenzen könnten also z.B. umgesetzt werden als L_a14 und L_b14, wenn sie im gleichen Schnippsel stehen.
Etwas bequemer ist die Verwendung einer Ziffer als Label. Beim Sprung gibt man direkt hinter der Ziffer an, in welche Richtung das Label gesucht wird. Ist das Label n, dann sucht und springt
- nb zurück (backward)
- nf nach vorne (forward)
Es wird zum nächsten auffindbaren Label in der angegebenen Richtung gesprungen.
Bits zählen
Dieses Assembler-Schnippsel zählt die Anzahl der gesetzten Bits in einem Byte eingabe. Die Eingabe wird nach rechts ins Carry geschoben, und das Carry zum Ergebnis dazu addiert.
static inline unsigned char count_bits (unsigned char eingabe) { unsigned char count; asm ( "clr %0" "\n" "0:" "\n\t" "lsr %1" "\n\t" "adc %0, __zero_reg__" "\n\t" "tst %1" "\n\t" "brne 0b" : "=&r" (count), "+r" (eingabe) ); return count; }
Damit kann man sich ein Parity bauen:
#define parity(x) (count_bits(x) & 1)
und hätte eine schlankere (aber etwas langsamere) Parity-Implementierung als in avr/parity.h. Ein
if (parity(foo)) ...
wird in Assembler dann zu (r24 = eingabe, r25 = count):
lds r24,foo /* #APP */ clr r25 0: lsr r24 adc r25, __zero_reg__ tst r24 brne 0b /* #NOAPP */ sbrs r25,0 rjmp .L1 ...
Fallstricke
- Um die Sonderzeichen "%~" und "%=" benutzen zu können, muss bei parameterlosen Inline Assembler dem Template ein Doppelpunkt folgen:
asm volatile ("%~call some_function" :);
- Steht das asm an oberster Ebene — also außerhalb einer Funktion — ist weiter zu beachten:
- Jedes % wird als solches ausgegeben, und es sind keine Operanden erlaubt.
- Falls die Reihenfolge von asm und Funktionen beibehalten werden soll, muß mit -fno-toplevel-reorder compiliert werden.
Quellen
- Dokumentation
- AVR-LibC: Inline Assembler Cookbook
- GCC Wiki: avr-gcc
- GCC: Assembler Instructions with C Expression Operands
- GCC: Constraints for asm Operands
- GCC: Variables in Specified Registers
- GCC Internals: Operand Constraints
- GCC-Quellen