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?
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
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.
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
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).
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.
--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--
> 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.
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
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.
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.
Falsch.
Der TWCR wird ueber einen pointer(Z) eingelesen,
r24 ist danach keineswegs 0xbc.
Es ist ein hardwareregister, kann sich also auch asynchron aendern.
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
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.
>> 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!).
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 :)
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.
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.
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.