Forum: Mikrocontroller und Digitale Elektronik Was macht der Compiler hier?


von John T. (abg1984)


Lesenswert?

Liebe Gemeinde,

ich habe vor Kurzem angefangen Assembler mit einem ATMega328P zu lernen. 
Konnte bis jetzt erfolgreich diverse Peripheriebausteine wie z.B. Timer, 
SPI, I2C, UART in Betrieb nehmen um Zeug damit zu steuern.

Neulich kam ich auf die Idee zu gucken was denn der Compiler macht? Habe 
dann mit verschiedenen Optimierungsstufen experimentiert und mir die 
.lss Datei angeschaut. Auf jeden Fall passieren da ziemlich ausgefuchste 
Sachen vor allem was das branching angeht :). Wobei ich aber folgende 
Zeilen nicht ganz verstanden habe:
1
  while(!(TWCR & (1<<TWINT))); 
2
 2a2:  ec eb         ldi  r30, 0xBC  ; 188
3
 2a4:  f0 e0         ldi  r31, 0x00  ; 0
4
 2a6:  80 81         ld  r24, Z
5
 2a8:  88 23         and  r24, r24
6
 2aa:  ec f7         brge  .-6        ; 0x2a6 <_Z9TWI_startv+0xa>


Hier verstehe ich den Sinn vom "and" einfach nicht. Erst wird TWCR 
(0x00BC) gelesen. Und dann why "and" mit sich selbst?

von O.M.G. (Gast)


Lesenswert?

Flags setzen.

Bist du neu?
Scheint so.

von Oliver S. (oliverso)


Lesenswert?

Irgendwie müssen die Vergleichsflags für den conditional branch ja 
gesetzt werden.

AND Rd, Rd hat den selben Opcode wie TST Rd. Da hat sich der Disassemler 
halt die unüblichere Übersetzung ausgesucht.

Oliver

: Bearbeitet durch User
von Ingo W. (uebrig) Benutzerseite


Lesenswert?

Ich rate mal: wenn TWINT=7 sein sollte, ist das Testergebnis im höchsten 
Bit (Vorzeichen). Daher die Prüfung auf "größer/gleich".

von John T. (abg1984)


Lesenswert?

Alles klar, vielen Dank.

von Aus Interesse (Gast)


Lesenswert?

Warum generiert der Compiler diese beiden Zeilen?

>ldi  r31, 0x00

>ld  r24, Z

Könnte er nicht einfach:
>and r30, r30

machen? Dass TWCR in 1 Byte passt ist dem Compiler ja vorab bekannt.

von (prx) A. K. (prx)


Lesenswert?

Aus Interesse schrieb:
> Könnte er nicht einfach:
>>and r30, r30

Damit würde er lediglich feststellen, dass das oberste Bit der Konstante 
0xBC = 0b10111100 gesetzt ist. Nicht aber das vom TWCR.

Was oben passiert:
  Z = Konstante 0x00BC
  R24 = Byte im Datenadressraum, adressiert durch Z
  teste R24

: Bearbeitet durch User
von Aus Interesse (Gast)


Lesenswert?

Ergibt Sinn, Danke, so früh am Morgen.

von Ben B. (Firma: Funkenflug Industries) (stromkraft)


Lesenswert?

Bei Assembler findet man viele solche Optimierungen. Man hat halt den 
Vorteil, daß man dem Prozessor haargenau sagt, was er tun soll und dabei 
nicht nur eingefahrene Wege nutzen braucht. Man kann den Code auf 
Geschwindigkeit optimieren oder auf Speicherplatzbedarf, oder auf was 
man gerade braucht.

Beispiel vom x86 Assembler wäre z.B. XOR AX,AX anstelle von MOV AX,0. 
Auf den ersten Blick sieht das unsinnig aus, da beide Befehle das 
Gleiche tun (AX=0000h). Auf den zweiten Blick braucht XOR AX,AX aber nur 
ein Byte (nur Opcode), während MOV AX,0 drei Bytes braucht (Opcode und 
2x 8Bit Daten).

Was ich aber oben an dem Compiler-Code nicht verstehe, wieso nutzt der 
Compiler kein LDS R24,0xBC bzw. LDS R24,TWCR? Warum der Umweg über Z? 
Kann der ATMega328 kein LDS? Das würde mich sehr wundern. Direkt mal ins 
Datenblatt geschaut, er kann LDS (load direct from SRAM).

von (prx) A. K. (prx)


Lesenswert?

Vielleicht wird im weiteren Programm noch einmal darauf zugegriffen, 
dann ohne Z neu zu laden.

: Bearbeitet durch User
von Ben B. (Firma: Funkenflug Industries) (stromkraft)


Lesenswert?

Dann bleibt das weiterhin Quark bzw. sinnlose Belegung von R30 und R31, 
da man LDS R24,0xBC jederzeit wiederholen könnte und der AVR für beides 
zwei Takte benötigt. Das da oben ist noch nicht mal schneller bzw. man 
verschenkt zwei Register und zwei Takte für die beiden LDI.

von Tippgeber (Gast)


Lesenswert?

--Zitat aus dem Datenblatt:--
The X-register, Y-register, and Z-register
The registers R26...R31 have some added functions to their general 
purpose usage. These registers are 16-bit
address pointers for indirect addressing of the data space. The three 
indirect address registers X, Y, and Z are
defined as described in Figure 7-3.
Figure 7-3. The X-, Y-, and Z-registers
In the different addressing modes these address registers have functions 
as fixed displacement, automatic
increment, and automatic decrement (see the instruction set reference 
for details).
R26 0x1A X-register Low Byte
R27 0x1B X-register High Byte
R28 0x1C Y-register Low Byte
R29 0x1D Y-register High Byte
R30 0x1E Z-register Low Byte
R31 0x1F Z-register High Byte
15 XH XL 0
X-register 7 0 7 0
R27 (0x1B) R26 (0x1A)
15 YH
--Zitatende--
> Ben schrieb:
> LDS R24,0xBC bzw. LDS R24,TWCR? Warum der Umweg über Z?
Weil für die Adressierung über k nur 7 Bits zur Verfügung stehen.
Daher ist BC nicht erreichbar.

--Zitat aus dem Datenblatt:--
LDS Rd, k Load Direct from SRAM Rd  (k) None 2
k ist nur 7 bits breit, damit ist die Adresse
The ATmega48A/PA/88A/PA/168A/PA/328/P is a complex microcontroller with 
more peripheral units than can
be supported within the 64 locations reserved in the Opcode for the IN 
and OUT instructions. For the Extended
I/O space from 0x60 - 0xFF in SRAM, only the ST/STS/STD and LD/LDS/LDD 
instructions can be used.
--Zitatende--

von Ben B. (Firma: Funkenflug Industries) (stromkraft)


Lesenswert?

> I/O space from 0x60 - 0xFF in SRAM, only the ST/STS/STD
> and LD/LDS/LDD instructions can be used.
Äh. Du verwechselst LDS mit IN bzw. STS mit OUT.
LDS/STS haben diese Begrenzung nicht.

Das ist in der Tat ein wenig nervig, wohl eine Folge davon, daß die AVRs 
mit der Zeit immer komplexer wurden und der Adressraum für IN/OUT 
dadurch zu klein wurde. Wenn man nur IN/OUT nicht mehr verwenden kann 
okay, kann man gut mit leben (weil LDS/STS die komplett ersetzen), aber 
richtig nervig ist, daß auch SBI/CBI nichts über 0x1F erreichen können.

Edit: Es wird auch kein Displacement oder pre/post inc/dec von Z 
benutzt. Es würde auch keinen Sinn machen, da alle I/O-Adressen fest 
sind und sich mit voller Geschwindigkeit mit einer einzigen Instruktion 
erreichen lassen.

: Bearbeitet durch User
von Tippgeber (Gast)


Lesenswert?

Hier ist das Instruction Set:
https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-Instruction-Set-Manual-DS40002198A.pdf

Du hast Recht, die von mir genannte Beschrenkung gilt nur für CPUs
mit reduced core (ARVrc), der 328 (AVRe+) ist davon nicht betroffen.
5.70.LDS – Load Direct from Data Space
5.71.LDS (AVRrc) – Load Direct from Data Space

5.117.STS – Store Direct to Data Space
5.118.STS (AVRrc) – Store Direct to Data Space

von Oliver S. (oliverso)


Lesenswert?

Ich ab das jetzt mal durch gcc und g++ geschoben (jeweils 10.1)
1
#include <avr/io.h>
2
3
int main()
4
{
5
  while(!(TWCR & (1<<TWINT)));
6
}

Die machen beide das gleiche da draus:
1
  7a:  80 91 bc 00   lds  r24, 0x00BC  ; 0x8000bc <__TEXT_REGION_LENGTH__+0x7e00bc>
2
  7e:  87 ff         sbrs  r24, 7
3
  80:  fc cf         rjmp  .-8        ; 0x7a <main>

Und das ist eigentlich auch das, was ich erwartet hätte.

Oliver

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Ben B. schrieb:
> Dann bleibt das weiterhin Quark bzw. sinnlose Belegung von R30 und R31,

In der Verwaltung von Registern haben Compiler viel Übung.

von (prx) A. K. (prx)


Lesenswert?

Ben B. schrieb:
> Dann bleibt das weiterhin Quark bzw. sinnlose Belegung von R30 und R31

Genau hinsehen hilft! ;-)

Vor der Schleife:
>  2a2:  ec eb         ldi  r30, 0xBC  ; 188
>  2a4:  f0 e0         ldi  r31, 0x00  ; 0

Die Schleife:
>  2a6:  80 81         ld  r24, Z
>  2a8:  88 23         and  r24, r24
>  2aa:  ec f7         brge  .-6        ; 0x2a6 <_Z9TWI_startv+0xa>

Die Schleife wird schneller, dafür wird der Code insgesamt grösser und 
es entsteht zusätzlicher Registerverbrauch, der hier aber vmtl 
irrelevant ist. Da dürfte die Optimierungseinstellung eine Rolle 
spielen, also -O vs -Os.

: Bearbeitet durch User
von Ben B. (Firma: Funkenflug Industries) (stromkraft)


Lesenswert?

Die Schleife wird nicht schneller. LD R24,Z und LDS R24,0xBC brauchen 
jeweils zwei Takte, kann man im Datenblatt nachlesen. Es ist und bleibt 
Quark.

von (prx) A. K. (prx)


Lesenswert?

Ben B. schrieb:
> ie Schleife wird nicht schneller. LD R24,Z und LDS R24,0xBC brauchen
> jeweils zwei Takte, kann man im Datenblatt nachlesen.

Stimmt, da verschenken die alten AVRs doch glatt einen Takt.

von Johann Klammer (Gast)


Lesenswert?

Falsch.
Der TWCR wird ueber einen pointer(Z) eingelesen,
r24 ist danach keineswegs 0xbc.
Es ist ein hardwareregister, kann sich also auch asynchron aendern.

von Oliver S. (oliverso)


Lesenswert?

Johann Klammer schrieb:
> falsch.
> Der TWCR wird ueber einen pointer(Z) eingelesen,
> r24 ist danach keineswegs 0xbc.

Hat auch niemand irgendwo behauptet.

Johann Klammer schrieb:
> Es ist ein hardwareregister, kann sich also auch asynchron aendern.

Asynchron zu was? R24 ist ein CPU-Register, welches sich von alleine gar 
nicht ändert, weder synchron noch asynchron. TWCR ist ein 
Hardware-Register, welches sich zwar ohne Zutun der CPU ändern kann, 
aber nur im Takt des Prozessors, also nicht asynchron.

Oliver

: Bearbeitet durch User
von Johann Klammer (Gast)


Lesenswert?

Hab's so gut erklaert wie ich kann.
Ist aber ok wennst's nicht verstehst.

von A. F. (artur-f) Benutzerseite


Lesenswert?

> ich habe vor Kurzem angefangen Assembler mit einem ATMega328P zu lernen.
Wozu lernt man heute noch diese Write-Only Programmiersprache?

: Bearbeitet durch User
von DerEgon (Gast)


Lesenswert?


von (prx) A. K. (prx)


Lesenswert?

A. F. schrieb:
> Wozu lernt man heute noch diese Write-Only Programmiersprache?

Es kann bei der Fehlersuche durchaus nützlich sein, wenn man ungefähr 
versteht, was der Compiler erzeugt.

von John T. (abg1984)


Lesenswert?

Oliver S. schrieb:
> Die machen beide das gleiche da draus:
>
>
1
>   7a:  80 91 bc 00   lds  r24, 0x00BC  ; 0x8000bc 
2
> <__TEXT_REGION_LENGTH__+0x7e00bc>
3
>   7e:  87 ff         sbrs  r24, 7
4
>   80:  fc cf         rjmp  .-8        ; 0x7a <main>
5
>
>
> Und das ist eigentlich auch das, was ich erwartet hätte.
>
> Oliver

Das ist auch das was ich ursprünglich implementiert hatte. Ich hatte 
erst in C mir paar Funktionen gebastelt um über I2C einen LCD 
anzusteuern. Dann wollte ich als Übung das ganze in Assembler machen. 
Aus Neugierde kam ich dann auf die Idee zu vergleichen wie der Compiler 
das übersetzt. Warum der nicht wie oben übersetzt hat, hat 
wahrscheinlich mit dem gesamten Code zu tun. Habe ehrlich gesagt auch 
nicht die Ambition den gesamten Kontext zu verstehen. Die paar Zeilen in 
meinem ursprünglichen Post sind mir halt aufgefallen und hatte darüber 
gerätselt (Natürlich, Flags!).

von John T. (abg1984)


Lesenswert?

A. F. schrieb:
>> ich habe vor Kurzem angefangen Assembler mit einem ATMega328P zu lernen.
> Wozu lernt man heute noch diese Write-Only Programmiersprache?

Weil ich mich entschieden habe mein Leben damit zu verschwenden. Ne, 
also Spaß bei Seite, das hilft mir den Prozessor besser zu verstehen und 
mit dem Wissen besseren C-Code zu schreiben. Das ist zu mindestens die 
Theorie :)

: Bearbeitet durch User
von EAF (Gast)


Lesenswert?

John T. schrieb:
> Weil ich mich entschieden habe mein Leben damit zu verschwenden.

Deswegen untersuchst du Kompilate?
Gute Lösung für das Problem.

Und dann auch noch C...?
Warum nicht C++?
Damit hättest quasi alles was C kann und noch eine "richtige" Sprache 
geschenkt dazu.

Nein!
Ich habe nichts dagegen auch selber in den generierten Code zu schauen. 
Gerade bei der Fehlersuche. Aber daraus ableiten zu können, wie man 
Dinge am Besten in C (oder auch in Assembler) erledigt, das möchte ich 
arg bezweifeln.

von Ben B. (Firma: Funkenflug Industries) (stromkraft)


Lesenswert?

Also ich schreibe auch viel und gerne in Assembler. Wenn man erstmal 
Templates für seine gebräuchlichsten Controller und eine kleine 
Code-Bibliothek (für sowas wie Rechenoperationen, 
Kommunikationsschnittstellen oder LCD-Operationen) hat, dann ist der 
Aufwand nicht mehr viel größer als in C, das Hauptprogramm besteht dann 
im Wesentlichen aus Funktionsaufrufen.

Den Output eines Compilers anschauen kann einem helfen, wenn man in 
Assembler mal "keine Ahnung hat" wie man was am besten hinbekommen soll 
oder wenn man den Verdacht hat, daß die eigene Lösung eine eher 
schlechte ist. Beispielsweise für eine komplexe Rechenaufgabe, das ist 
eine der großen Schwächen von Assembler. In C hat man sowas schnell 
verschachtelt in einer einzelnen Zeile hingekliert, in Assembler kann 
sowas abhängig von der geforderten Genauigkeit größerer Aufwand sein. 
Dann kann man sich durchaus mal anschauen, wie ein Compiler das Problem 
abfertigt und sich neue Ideen daraus holen.

von John T. (abg1984)


Lesenswert?

Ben B. schrieb:
> In C hat man sowas schnell
> verschachtelt in einer einzelnen Zeile hingekliert

Ich finde sich bißchen mit Assembler zu beschäftigen, schafft auch ein 
Resourcenbewusstsein auch wenn man dann doch seine Probleme mit C löst. 
Allgemein gesprochen würde ich eh die Auswahl der Programmiersprache 
abhängig von der Problemstellung machen. Der Linux Kernel z.B. ist ja 
auch gemischt mit Assembler. Hat bestimmt seine rationalen Gründe. Und 
C++ auf einem Microcontroller finde ich einfach uncool. Spätestens bei 
Interrupts wird es schmutzig mit compiler flags, statischen Funktionen 
bzw. Klassen, Friend-Gedöns usw. Da geht die Eleganz einer 
objekt-orientierten Sprache flöten und widerspricht den Gründen 
überhaupt C++ zu benutzen. Dann lieber plain C und die Strukturen sind 
klar.

Jedenfalls, vielen Dank nochmal für die Antworten zu der ursprünglichen 
Fragestellung. Die Party ist jetzt offiziel vorbei. Die, die länger 
bleiben wollen, können es auf jeden Fall tun.

: Bearbeitet durch User
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.