Forum: Mikrocontroller und Digitale Elektronik Inline Assembler in C


von Thorsten K. (Gast)


Lesenswert?

Hallöschen,

wie bekomme ich den
1
void setDdrLed( uint8_t x )
2
{
3
  asm (
4
      "ldi r16, %0\n\t"
5
      "out 0x17 , r16"  
6
      : "+r" ( x ) 
7
    );
8
}
Wie bekomme ich es hin das ich in "0x17" den Wert von "x" rein schreibe?

von pointer (Gast)


Lesenswert?

uint8_t *ptr = 0x17;

... ptr, r123..

von Thorsten K. (Gast)


Lesenswert?

pointer schrieb:
> uint8_t *ptr = 0x17;
>
> ... ptr, r123..
Sollte möglichst alles in Inline geschehen..

von Benjamin S. (recycler)


Lesenswert?

Ich denke der Compiler optimiert das am besten:
1
uint8_t *ptr = 0x17;

wenn du die Funktion inline verwenden willst, dann definiere sie inline.
1
inline void setDdrLed( uint8_t x )
2
{
3
  asm (
4
      "ldi r16, %0\n\t"
5
      "out 0x17 , r16"  
6
      : "+r" ( x ) 
7
    );
8
}

von Thorsten K. (Gast)


Lesenswert?

Benjamin S. schrieb:
> Ich denke der Compiler optimiert das am besten:uint8_t *ptr =
> 0x17;
> wenn du die Funktion inline verwenden willst, dann definiere sie inline.
> inline void setDdrLed( uint8_t x )
> {
>   asm (
>       "ldi r16, %0\n\t"
>       "out 0x17 , r16"
>       : "+r" ( x )
>     );
> }
Okay, habe ich übersehen.
Geht aber nach wie vor nicht.

von Benjamin S. (recycler)


Lesenswert?

Du kannst x mit 17 belegen beim Aufruf
1
setDdrLed(0x17)

oder willst du beim "out 0x17 , r16" das 17 dynmisch haben?
1
inline void setDdrLed( uint8_t x, uint8_t reg )
2
{
3
  asm (
4
      "ldi r16, %0\n\t"
5
      "out %1 , r16"  
6
      : "+r" ( x ) 
7
      : "+r" ( reg ) 
8
    );
9
}

weitere Info gibts u.a. hier:
https://rn-wissen.de/wiki/index.php?title=Inline-Assembler_in_avr-gcc

von Thorsten K. (Gast)


Lesenswert?

Benjamin S. schrieb:
> Du kannst x mit 17 belegen beim AufrufsetDdrLed(0x17)
> oder willst du beim "out 0x17 , r16" das 17 dynmisch haben?
> inline void setDdrLed( uint8_t x, uint8_t reg )
> {
>   asm (
>       "ldi r16, %0\n\t"
>       "out %1 , r16"
>       : "+r" ( x )
>       : "+r" ( reg )
>     );
> }
>
> weitere Info gibts u.a. hier:
> https://rn-wissen.de/wiki/index.php?title=Inline-A...

Ich würde gerne in das Register bzw. an die Adresse 0x17 den Wert den 
ich übergebe, rein schreiben.

von S. R. (svenska)


Lesenswert?

Thorsten K. schrieb:
> Ich würde gerne in das Register bzw. an die Adresse 0x17 den Wert den
> ich übergebe, rein schreiben.

Das kannst du auch ohne Assembler machen:
1
static func(uint8_t x) {
2
  *(uint8_t*)0x17 = x;
3
}

Oder besser lesbar, wie bereits geschrieben wurde:
1
static func(uint8_t x) {
2
  uint8_t *ptr = (uint8_t*)0x17;
3
  *ptr = x;
4
}

Wenn es dir darum geht, den Inline-Assembler von gcc besser zu 
verstehen, dann sage das bitte deutlich an.

von Thorsten K. (Gast)


Lesenswert?

S. R. schrieb:
> Thorsten K. schrieb
schrieben wurde:static func(uint8_t x)
> {
>   uint8_t *ptr = (uint8_t*)0x17;
>   *ptr = x;
> }
>
> Wenn es dir darum geht, den Inline-Assembler von gcc besser zu
> verstehen, dann sage das bitte deutlich an.

Ja ich würde es gerne in Inline Assembler machen, damit ich es auch 
besser verstehe..

von Carl D. (jcw2)


Lesenswert?

Benjamin S. schrieb:
> Du kannst x mit 17 belegen beim Aufruf
>
1
setDdrLed(0x17)
>
> oder willst du beim "out 0x17 , r16" das 17 dynmisch haben?
>
>
1
> inline void setDdrLed( uint8_t x, uint8_t reg )
2
> {
3
>   asm (
4
>       "ldi r16, %0\n\t"
5
>       "out %1 , r16"
6
>       : "+r" ( x )
7
>       : "+r" ( reg )
8
>     );
9
> }
10
>

Welcher μC soll das denn sein? Aus dem ersten Post zu schließen ein 
AVR8.
Wo hat der ein OUT mit Port-Adresse in einem Register? Das geht nur über 
die MEM-Adresse (idR IO-Adresse+0x20 also 0x37) in einem 
Pointer-Register.

von Mario M. (thelonging)


Lesenswert?

Wozu Inline-Assembler, wenn man auch "DDRB = x" schreiben kann?

von Carl D. (jcw2)


Lesenswert?

Mario M. schrieb:
> Wozu Inline-Assembler, wenn man auch "DDRB = x" schreiben kann?

Weil "DDRB" hier "uint8_t reg" heißt?!

von Thorsten K. (Gast)


Lesenswert?

Ich würde es halt mal gerne wissen wie man das damit macht. Es ist ein 
Tiny2313.

Eigentlich ist der Port mir egal.. Also welcher..

von foobar (Gast)


Lesenswert?

> Ich würde es halt mal gerne wissen wie man das damit macht.

Wie wär's mit Google? Ne Suche nach "avr-gcc inline assembler" gibt als 
ersten deutschen[1] Treffer:

  https://rn-wissen.de/wiki/index.php?title=Inline-Assembler_in_avr-gcc



[1] Wobei ich mir nicht sicher bin, ob Deutsch die passende Sprache ist 
;-)

von Thorsten K. (Gast)


Lesenswert?

foobar schrieb:
>> Ich würde es halt mal gerne wissen wie man das damit macht.
>
> Wie wär's mit Google? Ne Suche nach "avr-gcc inline assembler" gibt als
> ersten deutschen[1] Treffer:
>
>   https://rn-wissen.de/wiki/index.php?title=Inline-A...
>
> [1] Wobei ich mir nicht sicher bin, ob Deutsch die passende Sprache ist
> ;-)

Ja habe ich schon gegoogelt.. Diese Seite habe ich auch schon 
aufgerufen.. Bin nicht schlauer geworden..

von S. R. (svenska)


Lesenswert?

Thorsten K. schrieb:
> Bin nicht schlauer geworden..

Das tut mir aber Leid.
Woran mangelt's denn?

von foobar (Gast)


Lesenswert?

> Diese Seite habe ich auch schon aufgerufen.. Bin nicht schlauer
> geworden..

Die Seite erklärt aber gut und ausführlich. Besser wirst du's hier auch 
nicht bekommen. Evtl solltest du dir nen anderes Thema suchen ...

von Thorsten K. (Gast)


Lesenswert?

foobar schrieb:
>> Diese Seite habe ich auch schon aufgerufen.. Bin nicht schlauer
>> geworden..
>
> Die Seite erklärt aber gut und ausführlich. Besser wirst du's hier auch
> nicht bekommen. Evtl solltest du dir nen anderes Thema suchen ...

Man könnte es jedoch wenigstens mal versuchen.
Ich kann auf den Port zugreifen. Das heißt wenn ich jetzt ne LED dran 
hänge fängt diese auch zu leuchten, je nach dem welches Bit ich setze.

Bei den Ausgängen jedoch funktioniert das eben nicht. Muss doch einen 
einfachen Grund haben..

von Rolf M. (rmagnus)


Lesenswert?

Thorsten K. schrieb:
> Geht aber nach wie vor nicht.

Was heißt "geht nicht"? Nimmt er Compiler es nicht an? Kommt der Wert 
nicht im Register an? Sollte es so sein: Woran hast du das erkannt?

von Thorsten K. (Gast)


Lesenswert?

Rolf M. schrieb:
> Thorsten K. schrieb:
>> Geht aber nach wie vor nicht.
>
> Was heißt "geht nicht"? Nimmt er Compiler es nicht an? Kommt der Wert
> nicht im Register an? Sollte es so sein: Woran hast du das erkannt?

Der Compiler zeigt mir keine warnings oder Fehlermeldungen an.
Ich vermute mal das der Wert nicht ins Register rein geschrieben wird.

von Axel S. (a-za-z0-9)


Lesenswert?

Thorsten K. schrieb:
> foobar schrieb:

>> Die Seite erklärt aber gut und ausführlich. Besser wirst du's hier auch
>> nicht bekommen. Evtl solltest du dir nen anderes Thema suchen ...
>
> Man könnte es jedoch wenigstens mal versuchen.
> Ich kann auf den Port zugreifen. Das heißt wenn ich jetzt ne LED dran
> hänge fängt diese auch zu leuchten, je nach dem welches Bit ich setze.
>
> Bei den Ausgängen jedoch funktioniert das eben nicht.

Du sprichst in Rätseln. Was meinst du denn bitte mit "den Ausgängen"? 
Inwiefern unterscheiden sich irgendwelche Ausgänge von Portpins, an 
denen LED hängen? Und was genau soll "funktioniert nicht" heißen?

Klingt gerade überhaupt nicht nach einem Softwareproblem, am wenigsten 
nach einem Problem mit Inline-Assembler.

von Peter D. (peda)


Lesenswert?

Thorsten K. schrieb:
>
1
> void setDdrLed( uint8_t x )
2
> {
3
>   asm (
4
>       "ldi r16, %0\n\t"
5
>       "out 0x17 , r16"
6
>       : "+r" ( x )
7
>     );
8
> }
9
>

Nun, x ist ja bereits im Register r24 (das macht der Aufrufer).
Das ldi ist somit falsch und gibt eine Fehlermeldung:
1
C:\Users\xxx\AppData\Local\Temp/cciQ1sp0.o: In function `setDdrLed1':
2
D:\AVR-C\Test2/asm.c:5: undefined reference to `r24'

Außerdem wird alles wegoptimiert, da nicht volatile.
So geht es:
1
void setDdrLed( uint8_t x )
2
{
3
  asm volatile (
4
      "mov r16, %0\n\t"
5
      "out %1 , r16\n\t"  
6
      : "+r" ( x ) 
7
      : "M" (_SFR_IO_ADDR (DDRB))
8
    );
9
}

Steht aber alles schön erklärt im:
https://rn-wissen.de/wiki/index.php?title=Inline-Assembler_in_avr-gcc

von Rolf M. (rmagnus)


Lesenswert?

Peter D. schrieb:
> Nun, x ist ja bereits im Register r24 (das macht der Aufrufer).

In welchem Register das an die Funktion übergeben wird, muss deinem 
asm-Block egal sein. Wesentlich ist nur, dass das "+r" sagt, dass dein 
Code den Wert in einem (beliebigen) Register erwartet. ldi will aber 
einen Immediate-Wert und kein Register als zweiten Operanden.

Peter D. schrieb:
> void setDdrLed( uint8_t x )
> {
>   asm volatile (
>       "mov r16, %0\n\t"
>       "out %1 , r16\n\t"
>       : "+r" ( x )
>       : "M" (_SFR_IO_ADDR (DDRB))
>     );
> }

Warum ist x hier als Output-Operand aufgeführt? Es wird doch 
ausschließlich gelesen.
Man könnte sich den Umweg über r16 übrigens noch sparen. Und man kann 
den Operanden zur Übersicht auch Namen geben. Dann würde das ganze so 
aussehen:
1
  asm volatile (
2
      "out %[Register], %[Value]\n\t"
3
      : /* Leere Liste */
4
      : [Value]"r" ( x ), [Register]"M" (_SFR_IO_ADDR (DDRB))
5
    );

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Carl D. schrieb:
> Mario M. schrieb:
>> Wozu Inline-Assembler, wenn man auch "DDRB = x" schreiben kann?
>
> Weil "DDRB" hier "uint8_t reg" heißt?!

Genau darum kann es auch gar nicht gehen, C hin, Inline-Assembler her. 
Der Operand für OUT muss eine Compilezeit-Konstante sein, denn die 
Adresse des Ports wird in den Befehl hinein codiert.  Deshalb ist ja 
auch OUT auf einen vergleichsweise kleinen Portadressraum eingeschränkt.

Wenn es unbedingt zur Laufzeit einstellbar sein soll, kann man nur üben 
den (langsameren) Alias der Portadresse im Speicheradressraum gehen. Das 
ist in der avr-libc-FAQ beschrieben.

von Thorsten K. (Gast)


Lesenswert?

Rolf M. schrieb:
> asm volatile (
>       "out %[Register], %[Value]\n\t"
>       : /* Leere Liste */
>       : [Value]"r" ( x ), [Register]"M" (_SFR_IO_ADDR (DDRB))
>     );

Das sieht viel versprechend aus ;) werde ich später mal testen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Thorsten K. schrieb:
> Das sieht viel versprechend aus

Ist allerdings nicht das, was du oben gewollt hast: PORTB muss hier als 
Makro übergeben werden. Den kann man nicht durch einen Variablennamen 
ersetzen.

Ansonsten ist das aber wirklich nichts anderes, als gleich in C zu 
schreiben:
1
PORTB = x;

von Rolf M. (rmagnus)


Lesenswert?

Jörg W. schrieb:
> Ist allerdings nicht das, was du oben gewollt hast: PORTB muss hier als
> Makro übergeben werden. Den kann man nicht durch einen Variablennamen
> ersetzen.

Wo hat er das denn gewollt?

Thorsten K. schrieb:
> Ich würde gerne in das Register bzw. an die Adresse 0x17 den Wert den
> ich übergebe, rein schreiben.

Ich interpretiere das so, dass der Wert als dynamischer Parameter 
übergeben werden soll, aber nicht die Adresse des Registers.

> Ansonsten ist das aber wirklich nichts anderes, als gleich in C zu
> schreiben:
> PORTB = x;

Es geht ihm ja nur darum, inline assembler zu verstehen. Da ist es nicht 
von Nachteil, mit was einfachem anzufangen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Rolf M. schrieb:
> Es geht ihm ja nur darum, inline assembler zu verstehen. Da ist es nicht
> von Nachteil, mit was einfachem anzufangen.

Dafür gibt's das Inline Assembler Cookbook.

https://www.nongnu.org/avr-libc/user-manual/inline_asm.html

von Thorsten K. (Gast)


Lesenswert?

Jörg W. schrieb:
> Ist allerdings nicht das, was du oben gewollt hast: PORTB muss hier als
> Makro übergeben werden. Den kann man nicht durch einen Variablennamen
> ersetzen.
>
> Ansonsten ist das aber wirklich nichts anderes, als gleich in C zu
> schreiben:
> PORTB = x;

Das ist mir soweit auch klar -.-*
Ich möchte doch einfach nur mein Wissen in Inline ASM ein wenig 
verstärken.. Und durch das belesen, bin ich halt auch nicht weiter 
gekommen..

von Thorsten K. (Gast)


Lesenswert?

Jörg W. schrieb:
> Ist allerdings nicht das, was du oben gewollt hast: PORTB muss hier als
> Makro übergeben werden. Den kann man nicht durch einen Variablennamen
> ersetzen.

Wie meinst du das? Die Adresse von PORTx? Welches Makro?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Thorsten K. schrieb:
> Und durch das belesen, bin ich halt auch nicht weiter gekommen.

Dann schreib lieber mal, wo du nicht weiterkommst.

Das relativ simple Beispiel mit den IO-Port-Ausgaben steht im genannten 
Inline Asm Cookbook ja bereits ziemlich weit oben.  Nach unten wird's 
dann immer tiefgehender.

Diese Constraints mögen auf den ersten Blick ziemlich verwirrend 
aussehen. Letztlich bilden sie das ab, was der Compiler intern in seinen 
Zwischensprachen ebenfalls benutzt, um die einzelnen Operanden zu 
markieren. Dadurch kann man die Aufteilung, welche Daten beispielsweise 
in Registern gehalten werden und welche im Speicher bleiben (und dann 
temporär in Register kopiert werden müssen), bis zu einem Zeitpunkt nach 
hinten schieben, bei dem der Optimierer dann entscheiden kann, was wie 
zugegriffen wird, sodass der gesamte Code möglichst effizient wird. 
„Harte“ Registerzuteilungen sollte man folglich auch im Inline-Assembler 
vermeiden und stattdessen Variablen mit passendem Constraining benutzen.

Schau dir dein einleitendes Beispiel an: du hast Register r16 hart 
vorgegeben (und müsstest es in die "clobber"-Liste eintragen).  Wenn du 
stattdessen deinen OUT-Befehl mit einem Operanden versiehst, der 
passendes Constraining bekommt (OUT braucht ein beliebiges Register, 
also "r"), dann kann der Compiler sich darum kümmern, dass der 
auszugebende Wert gleich mal in einem solchen Register berechnet wird, 
sodass man nicht nochmal umkopieren muss.
1
void setled(uint8_t value)
2
{
3
   asm("out %[port], %[val]"
4
      :  /* no output operands 1) */
5
      : [port] "I" (_SFR_IO_ADDR(PORTB)),
6
        [val] "r" (value));
7
}

1) Natürlich wird aus dem Controller etwas ausgegeben, aber das 
Inline-Asm-Statement gibt in diesem Kontext nichts in seine C-Umgebung 
zurück. Daher gibt es hier nur input operands.

Obige Funktion compiliert zu:
1
.global setled
2
        .type   setled, @function
3
setled:
4
/* prologue: function */
5
/* frame size = 0 */
6
/* stack size = 0 */
7
.L__stack_usage = 0
8
/* #APP */
9
 ;  6 "setled.c" 1
10
        out 24, r24
11
 ;  0 "" 2
12
/* #NOAPP */
13
        ret

r24 ist hierbei das Register, in dem die Funktion den ersten Parameter 
übergeben bekommt. Der Compiler muss nichts extra umkopieren, er kann 
dieses Register gleich für den OUT-Befehl benutzen. Bei deiner Variante 
hätte er es erst (sinnlos) nach r16 umkopieren müssen.

: Bearbeitet durch Moderator
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Thorsten K. schrieb:
> Die Adresse von PORTx? Welches Makro?

PORTx ist ein Makro.

von Thorsten K. (Gast)


Lesenswert?

Jörg W. schrieb:
> : [port] "I" (_SFR_IO_ADDR(PORTB)),

Okay. Und kann ich meiner Funktion auch "PORTB" mitgeben?
Also quasie..
1
void setLed( uint8_t port , uint8_t bit )
2
{
3
  asm("out %[port], %[bit]"
4
  :  /* no output operands 1) */
5
  : [port]  "I" (_SFR_IO_ADDR( port ) ) , [bit] "r" ( bit) );
6
}

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Thorsten K. schrieb:
> Und kann ich meiner Funktion auch "PORTB" mitgeben?

Nein.  Schrieb ich doch weiter oben, sowas gibt die AVR-Architektur 
schlicht nicht her.

von Thorsten K. (Gast)


Lesenswert?

Jörg W. schrieb:
> Thorsten K. schrieb:
> Das sieht viel versprechend aus
>
> Ist allerdings nicht das, was du oben gewollt hast: PORTB muss hier als
> Makro übergeben werden. Den kann man nicht durch einen Variablennamen
> ersetzen.
>
> Ansonsten ist das aber wirklich nichts anderes, als gleich in C zu
> schreiben:PORTB = x;

Also Makro übergeben. Kannst du mir bitte ein konkretes Beispiel 
posten..?
Wenn PORTB oder DDRB ein Makro ist, müsste ich das doch übergeben 
können?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Thorsten K. schrieb:
> Wenn PORTB oder DDRB ein Makro ist, müsste ich das doch übergeben
> können?

Nein. Mach dich mal mit dem Konzept von Makros vertraut: das ist doch 
nur eine Textersetzung. Er würde ja bei der Übergabe aufgelöst werden 
müssen.

Unabhängig von Makro oder nicht, scheinst du immer noch nicht verstanden 
zu haben, dass beide Operanden eines OUT-Befehls zu dem Zeitpunkt, da 
es der Assembler vorgesetzt bekommt, feststehende Konstanten sein 
müssen. Das beißt sich mit deinem Wunsch, sie als Parameter einer 
Funktion zu übergeben, denn dann würde man ja den konkreten Port erst 
zur Laufzeit festlegen. Wenn du sowas willst, musst du über die 
Port-Aliase im Speicheradressbereich gehen. Wie das geht, ist hier 
beschrieben:

https://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_port_pass

von Thorsten K. (Gast)


Lesenswert?

Jörg W. schrieb:
> Unabhängig von Makro oder nicht, scheinst du immer noch nicht verstanden
> zu haben, dass beide Operanden eines OUT-Befehls zu dem Zeitpunkt, da es
> der Assembler vorgesetzt bekommt, feststehende Konstanten sein müssen.
> Das beißt sich mit deinem Wunsch, sie als Parameter einer Funktion zu
> übergeben, denn dann würde man ja den konkreten Port erst zur Laufzeit
> festlegen. Wenn du sowas willst, musst du über die Port-Aliase im
> Speicheradressbereich gehen. Wie das geht, ist hier beschrieben:
>
> https://www.nongnu.org/avr-libc/user-manual/FAQ.ht...

Das geht dann aber nur in C Code?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Thorsten K. schrieb:
> Das geht dann aber nur in C Code?

Was geht nur in C-Code?  Zugriff auf IO-Ports über Speicherbereiche? 
Natürlich geht das auch direkt in Assemblercode, der C-Compiler erzeugt 
ja letztlich nur Assemblercode. Die Speicheradresse eines IO-Ports 
bekommt man mit _SFR_MEM_ADDR() – steht natürlich alles in der Doku. ;-)

Mir stellt sich trotzdem noch die Frage, warum du für simple Dinge, die 
in C völlig unproblematisch und einfach gehen, erst die Verrenkungen 
über inline asm machen willst. Wirklich lernen kann man an der Stelle 
nicht so viel …

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.