Forum: Compiler & IDEs kleine Anfänger-Fragen zu assembler


von Tobias AsmBeginner (Gast)


Lesenswert?

Servus zusammen,

ich bin gerade etwas am rotieren beim Einstieg in Assembler. Ich möchte 
ein C-Programm um zwei Assembler-Interrupts ergänzen, allerdings 
gestaltet sich das noch recht schwierig, da ich noch nicht sehr viel mit 
Assembler gearbeitet habe (eigentlich nur inline-Assembler).
Ich habe nun als ersten Schritt zu meinem C-Projekt eine 
interrupt.s-Datei hinzugefügt.

Jetzt stehe ich vor dem Problem mit dem Speicher. Im Prinzip möchte ich 
folgenden vereinfachten C-Code in die Assembler-Datei übersetzten (nur 
damit ihr grob wisst worum es geht).
1
uin16_t array[10];
2
3
ISR( TIMER1_OVF_vect )
4
{
5
  static uint16_t *ptr = &array[0];
6
  static uint16_t cntr = 0;
7
8
  if( GPIOR0 & (1<<0) )
9
    ptr = &array[0];
10
11
  if( GPIOR0 & (1<<1) )
12
  {
13
    if( *ptr == cntr )
14
    {
15
      PORTB ^= (1<<PB0);
16
      if( ptr < array[9] )
17
        ptr++;
18
    }
19
    cntr++;      
20
  }
21
}

ich frage mich nun, wie ich die variablen eben anlege.
Mein Versuch würde wie folgt aussehen:
1
#include <avr/io.h>
2
3
.extern array  ; das glaube ich war noch einfach
4
.comm ptr, 2   ; ein Pointer ist ja auch nur eine 16-bit Adresse
5
.comm cntr, 2
6
7
.global TIMER1_CAPT_vect
8
TIMER1_CAPT_vect:
9
10
;TBD
11
12
  reti
13
.end     ; Muss die .end Anweisung ans ende der Datei, oder ans Ende des Interrupts?! 
14
15
.global TIMER1_OVF_vect
16
TIMER1_OVF_vect:
17
18
; TBD
19
20
reti
21
.end

Nun stelle ich mir die Frage, warum z.B. im Tutorial über das SRAM 
http://www.mikrocontroller.net/articles/AVR-Tutorial:_SRAM
.dseg und .byte/.word verwendet wird? wo genau ist der Unterschied zu 
.comm und was ist hier richtig?

Und zur .end-Anweisung habe ich vielleicht überlesen, aber ich finde es 
auch nirgends beschrieben. Wo muss die hin? Ende einer Funktion, Ende 
des Interrupts?!

Wäre echt klasse wenn ihr mir da kurz auf die Sprünge helfen könntet.
Vielen Dank
lg
 Tobi

von Karl H. (kbuchegg)


Lesenswert?

Tobias AsmBeginner schrieb:


> Jetzt stehe ich vor dem Problem mit dem Speicher. Im Prinzip möchte ich
> folgenden vereinfachten C-Code in die Assembler-Datei übersetzten (nur
> damit ihr grob wisst worum es geht).

Die erste Frage, die ich mir stelle ist: Wozu?
Was denkst du, bringst du besser hin als der Compiler (mal abgesehen von 
dem Programmfehler, den du eingebaut hast)

>       if( ptr < array[9] )

Ich denke du willst hier

        if( ptr < &array[9] )

von Karl H. (kbuchegg)


Lesenswert?

Tobias AsmBeginner schrieb:

>
> Nun stelle ich mir die Frage, warum z.B. im Tutorial über das SRAM
> http://www.mikrocontroller.net/articles/AVR-Tutorial:_SRAM
> .dseg und .byte/.word verwendet wird? wo genau ist der Unterschied zu
> .comm und was ist hier richtig?

Das Tutorial kannst du vergessen. Das ist für den Atmel-Assembler und 
hier nicht vollinhaltlich anwendbar.

von Tobias AsmBeginner (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Die erste Frage, die ich mir stelle ist: Wozu?

Der Interrupt ist ein wenig länger als das oben gezeigte Beispiel, 
welches meine Problemgebiete beinhaltet und sonst einfach nur kurz 
gehalten ist.
Der kleine Fehler tut mir leid, deine Verbesserung ist natürlich 
korrekt!
Allerdings wird der Interrupt ca. alle 250 Takte auftreten und muss 
daher nicht nur in den 250 Takten abgearbeitet sein, sondern sogar noch 
schneller, da der µC in der main-loop auch ein paar Kleinigkeiten 
schaffen soll.
Ich glaube also, dass ich es unter einem erheblichen Mehraufwand schaffe 
einen schnelleren Programmablauf zu programmieren, und dabei eventuell 
sogar noch was lerne.


ich habe jetzt noch eine beschreibung zu .comm gefunden:
.comm declares a common symbol named symbol. When linking, a common 
symbol in one object file may be merged with a defined or common symbol 
of the same name in another object file. If ld does not see a definition 
for the symbol--just one or more common symbols--then it will allocate 
length bytes of uninitialized memory. length must be an absolute 
expression. If ld sees multiple common symbols with the same name, and 
they do not all have the same size, it will allocate space using the 
largest size.

so richtig helfen will mir das aber auch nicht. was ist denn jetzt 
richtiger? sollte ich .comm verwenden? kompilieren lässt sich der 
Spaß...

von Peter D. (peda)


Lesenswert?

Der Weg ist bei jedem Compiler und Target gleich:

Schreibe in C eine Dummyfunktion, die alle Argumente und Variablen 
enthält und lasse sie nach Assembler übersetzen.
Das ist dann Dein Gerüst was Du ergänzen kannst.


Peter

von Peter D. (peda)


Lesenswert?

Tobias AsmBeginner schrieb:
> Ich glaube also, dass ich es unter einem erheblichen Mehraufwand schaffe
> einen schnelleren Programmablauf zu programmieren

Glauben kann man viel.
Die Einsparung durch Assembler ist eher gering bis enttäuschend.
Richtig große Einsparungen erreicht man eigentlich nur durch einen 
besseren Programmablaufplan.


Peter

von Tobias AsmBeginner (Gast)


Lesenswert?

Um nach langer Suche meine Fragen selbst zu beantworten für Andere, die 
eventuell nach ähnlichen Antworten suchen:

.dseg ist hier nicht zu gebrauchen und wird auch vom Compiler bemängelt!
.comm ist offenbar die richtige Lösung um Daten ins SRAM zu legen.

und .end ist offenbar der Marker, an dem der Compiler/Assembler aufhört 
die Datei zu lesen...

von Tobias AsmBeginner (Gast)


Lesenswert?

Oh ja, und zum allgemeinen "manuelles assemblieren" bringt nichts.
Ich finde man sollte es immer mal versuchen, denn häufig macht der 
Compiler halt doch nen Haufen Unsinn oder nimmt Optimierungen, die man 
nur mit etwas höherem Verständnis durchführen kann halt nicht vor.

nur EIN Beispiel:
1
[...]
2
uint8_t i = rand(); // irgend ein Wert halt
3
uint16_t array[21];
4
uint16_t tmp;
5
6
if( i > 20 )
7
  i = 20;
8
9
tmp = array[ i ];
10
[...]

Der Compiler beachtet nicht, dass durch die Limitierung des Index auf 20 
beim berechnen der relativen Adresse (i*sizeof(uint16_t)) kein 8-bit 
Überlauf statt finden kann. Er schälgt sich dadurch mit 16-bit Werten 
rum, brauch mehr Instruktionen und mehr Register...

ich habe die worst-case Ausführungszeit meines Interrupts so von 143 auf 
93 Taktzyklen reduzieren können. Also über 30% Beschleunigung. Jetzt 
kann ich den Interrupt wesentlich bedenkenloser alle 10µs auslösen 
(F_CPU = 20MHz), auch wenn der µC noch einen weiteren Interrupt 
verarbeiten muss, der mit maximal 48000 kHz ( ~ 20µs ) triggert und 
dabei 69 Zyklen benötigt.

Mit dem ganzen Lernerfolg bei der Geschichte möchte ich jetzt gar nicht 
groß Anfangen...

von Peter D. (peda)


Lesenswert?

Tobias AsmBeginner schrieb:
> Der Compiler beachtet nicht, dass durch die Limitierung des Index auf 20
> beim berechnen der relativen Adresse (i*sizeof(uint16_t)) kein 8-bit
> Überlauf statt finden kann.

Es kann sehr wohl ein Überlauf erfolgen.
Es hängt davon ab, wo array im SRAM steht und das weiß erst der Linker.


Peter

von dadada (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Es kann sehr wohl ein Überlauf erfolgen.
>
> Es hängt davon ab, wo array im SRAM steht und das weiß erst der Linker.

Aber doch noch nicht bei der Multiplikation, sondern erst wenn der 
Basepointer addiert wird, oder?

von Peter D. (peda)


Lesenswert?

Stimmt, das eine ADC ist unnötig. Aber sonst ist der Code ziemlich 
optimal:
1
uint16_t test( uint8_t i )
2
{
3
  48:  85 31         cpi  r24, 0x15  ; 21
4
  4a:  08 f0         brcs  .+2        ; 0x4e <test+0x6>
5
  4c:  84 e1         ldi  r24, 0x14  ; 20
6
  4e:  e8 2f         mov  r30, r24
7
  50:  f0 e0         ldi  r31, 0x00  ; 0
8
  52:  ee 0f         add  r30, r30
9
  54:  ff 1f         adc  r31, r31
10
  56:  e0 5a         subi  r30, 0xA0  ; 160
11
  58:  ff 4f         sbci  r31, 0xFF  ; 255
12
if( i > 20 )
13
  i = 20;
14
15
tmp = array[ i ];
16
  return tmp;
17
}
18
  5a:  80 81         ld  r24, Z
19
  5c:  91 81         ldd  r25, Z+1  ; 0x01
20
  5e:  08 95         ret

Peter

von Tobias AsmBeginner (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Stimmt, das eine ADC ist unnötig. Aber sonst ist der Code ziemlich
> optimal:

In diesem Fall ja, in meinem Fall habe ich aber solche Konstrukte finden 
können:
1
    if( array1[ index ] == value )
2
 29c:  60 91 30 01   lds  r22, 0x0130
3
 2a0:  26 2f         mov  r18, r22
4
 2a2:  30 e0         ldi  r19, 0x00  ; 0
5
 2a4:  f9 01         movw  r30, r18
6
 2a6:  ee 0f         add  r30, r30
7
 2a8:  ff 1f         adc  r31, r31
8
 2aa:  ea 5e         subi  r30, 0xEA  ; 234
9
 2ac:  fe 4f         sbci  r31, 0xFE  ; 254
10
 2ae:  40 81         ld  r20, Z
11
 2b0:  51 81         ldd  r21, Z+1  ; 0x01
12
 2b2:  48 17         cp  r20, r24
13
 2b4:  59 07         cpc  r21, r25
14
 2b6:  a9 f4         brne  .+42       ; 0x2e2 <__vector_13+0xd0>
15
    {
16
        state = array2[ index ];
17
 2b8:  f9 01         movw  r30, r18
18
 2ba:  e6 5f         subi  r30, 0xF6  ; 246
19
 2bc:  fe 4f         sbci  r31, 0xFE  ; 254
20
 2be:  30 81         ld  r19, Z
21
        if( index < 11 )
22
 2c0:  6b 30         cpi  r22, 0x0B  ; 11
23
 2c2:  18 f4         brcc  .+6        ; 0x2ca <__vector_13+0xb8>
24
            index++;
25
 2c4:  6f 5f         subi  r22, 0xFF  ; 255
26
 2c6:  60 93 30 01   sts  0x0130, r22
nach dem gezeigten Ausschnitt werden r18, r19, r20, r21, r22, r30 und 
r31 nicht mehr weiter verwendet... Der ein oder andere erkennt 
vielleicht Optimierungspotential, vorallem wenn man bedenk, dass jedes 
verwendete Register in einem Interrupt erst gesichert und später 
wiederhergestellt werden muss, was mit push und pop 4 Prozessortakte je 
Register ausmacht...

von Peter D. (peda)


Lesenswert?

Zeig dochmal den exakten Code (Anhang) und nicht irgendwas ausgedachtes 
oder Schnipselchen.

Sonst reden wir ja ewig aneinander vorbei.


Peter

von Karl H. (kbuchegg)


Lesenswert?

Tobias AsmBeginner schrieb:

> nach dem gezeigten Ausschnitt werden r18, r19, r20, r21, r22, r30 und
> r31 nicht mehr weiter verwendet...

ich find den Code eigentlich nicht sooo schlecht
ok. r22 hätte der GCC einsparen können. Aber abgesehen davon ist das so 
mies auch wieder nicht, wie du tust.

Es gibt natürlich Fälle, in denen es tatsächlich auf jeden Takt ankommt. 
Die Erzeugung von Video-Signalen fällt mir da zb ein. Aber ich würde das 
ehrlich gesagt eher als die Ausnahme ansehen. In den meisten Fällen ist 
es ziemlich irrelevant, ob eine ISR 10 oder 15 Takte mehr verbrutzelt 
oder nicht.

von amateur (Gast)


Lesenswert?

Zumindest beim Studio 4 war in der Hilfe zu den Bibliotheken so einiges 
zu finden.
Beispiele zum Zusammenwerkeln von C und Assembler (.S) sowie in den 
FAQ's ein Artikel zur Belegung von Registern durch C. Ist interessant, 
wenn Du Parameter an eine Routine übergibst bzw. zurückgeliefert 
bekommst.
Schätze mal, da die Bibliotheksbeschreibung nicht von Atmel war, dass es 
die Artikel immer noch gibt.

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.