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_tarray[10];
2
3
ISR(TIMER1_OVF_vect)
4
{
5
staticuint16_t*ptr=&array[0];
6
staticuint16_tcntr=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
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] )
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.
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ß...
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
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
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...
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_ti=rand();// irgend ein Wert halt
3
uint16_tarray[21];
4
uint16_ttmp;
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...
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
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?
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...
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.
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.