Hallo,
ich arbeite mit Atmel Studio 6.2 und bin in meinem Programm auf eine
unerwartetes Zeitverhalten gestossen. Schnell hab ich den Fehler
entdeckt.
Es liegt an dem Handling des Interrupt. Es werden viel zu viele push und
pop gemacht. Egal welche optimize Option ich verwende.
Lediglich R24/R25/R28/R29 wird im Code verwendet.
Gibt es eine Möglichkeit hier Einfluss auf die push und pop zu nehmen?
Vielen Dank und schönen Vatertag
Martin Mörtl
Disassembler:
1
00000360 PUSH R1 Push register on stack
2
00000361 PUSH R0 Push register on stack
3
00000362 IN R0,0x3F In from I/O location
4
00000363 PUSH R0 Push register on stack
5
00000364 CLR R1 Clear Register
6
00000365 IN R0,0x3B In from I/O location
7
00000366 PUSH R0 Push register on stack
8
00000367 PUSH R18 Push register on stack
9
00000368 PUSH R19 Push register on stack
10
00000369 PUSH R20 Push register on stack
11
0000036A PUSH R21 Push register on stack
12
0000036B PUSH R22 Push register on stack
13
0000036C PUSH R23 Push register on stack
14
0000036D PUSH R24 Push register on stack * Used
15
0000036E PUSH R25 Push register on stack * Used
16
0000036F PUSH R26 Push register on stack
17
00000370 PUSH R27 Push register on stack
18
00000371 PUSH R28 Push register on stack * Used
19
00000372 PUSH R29 Push register on stack * Used
20
00000373 PUSH R30 Push register on stack
21
00000374 PUSH R31 Push register on stack
22
flashData |=0b01000000;
23
00000375 LDS R24,0x015E Load direct from data space
24
00000377 ORI R24,0x40 Logical OR with immediate
25
00000378 STS 0x015E,R24 Store direct to data space
26
PORTA = flashData;
27
0000037A OUT 0x02,R24 Out to I/O location
28
OCR0B = flashCompare;
29
0000037B LDS R24,0x015D Load direct from data space
30
0000037D OUT 0x28,R24 Out to I/O location
31
flashData = eeprom_read_byte(flashDataAdress++);
32
0000037E LDS R28,0x0100 Load direct from data space
33
00000380 LDI R29,0x01 Load immediate
34
00000381 ADD R29,R28 Add without carry
35
00000382 STS 0x0100,R29 Store direct to data space
36
00000384 MOV R24,R28 Copy register
37
00000385 LDI R25,0x00 Load immediate
38
00000386 RCALL PC+0x0492 Relative call subroutine
39
00000387 STS 0x015E,R24 Store direct to data space
Martin Mörtl schrieb:> Gibt es eine Möglichkeit hier Einfluss auf die push und pop zu nehmen?
ja. du darfst keine Funktionen in der ISR aufrufen.
eeprom_read_byte
wird wohl das Problem sein.
Da du im Interrupthandler eine weitere Funktion aufrufst, müssen auch
alle diejenigen Register gepusht werden, die von dieser Funktion
überschrieben werden könnten. Wenn diese Funktion tatsächlich nur ganz
wenige Register überschreibt, schießt der Compiler mit der Pusherei
natürlich über das Ziel hinaus.
Du kannst ihm bei der Optimierung dadurch helfen, dass du die
aufgerufene Funktion in die gleiche Quelldatei (oder in ein Headerfile,
dass von der Quelldatei eingebunden wird) schreibst. Dann sieht der
Compiler, welche Register tatsächlich überschrieben werden, und pusht
nur diese.
PS: Ich sehe gerade, dass die aufgerufene Funktion in einer Bibliothek
liegt, wodurch der obige Tipp schwieriger umzusetzen ist. Aber wozu
musst du das EEPROM in jedem Interruptaufruf lesen? Das kostet doch nur
unnötig Zeit. Lies das EEPROM einmal während der Intilialisierungsphase
aus und speichere die Werte in einer globalen Variable, auf die der
Interrupthandler Zugriff hat.
Sobald in einer ISR eine Funktion aufgerufen wird muss der Compiler alle
"callee saved" Register sichern, da er nicht weiss, welche Register
diese Funktion verwendet. Und das sind eben recht viele.
Es sollte aber nicht schwer fallen, diese EEPROM-Funktionen durch
direkten Zugriff zu ersetzen.
Yalu X. schrieb:> musst du das EEPROM in jedem Interruptaufruf lesen? Das kostet doch nur> unnötig Zeit.
Lesen ist harmlos - zumindest wenn man es direkt macht.
Hallo,
Danke für die Tips:
1. Im EEPROM liegt ein Bitmuster das ausgegeben werden soll. Es werde 8
PWM's erzeugt mit einem Timer. Timer zählt bis 100 und der COMPA kommt
immer, wenn sich einer der Kanäle ändert. Das ist der Grund warum ich
immer aus dem EEPROM lese und um eine Adresse weiter springe. Muster und
Comparewert liegen hintereinander im EEPROM
2. eeprom_read_byte: Habe keine JUMP gesehen der da raus springt.
3. RCALL PC+0x????: Auch hier wird nur R24 und R25 verwendet.
1
00000818 SBIC 0x1F,1 Skip if bit in I/O register cleared
2
00000819 RJMP PC-0x0001 Relative jump
3
0000081A OUT 0x22,R25 Out to I/O location
4
0000081B OUT 0x21,R24 Out to I/O location
5
0000081C SBI 0x1F,0 Set bit in I/O register
6
0000081D CLR R25 Clear Register
7
0000081E IN R24,0x20 In from I/O location
8
0000081F RET Subroutine return
4. Ich probiere es wie vorgeschlagen, die EEPROM Daten in ein ARRAY zu
laden und das ganze nochmal zu versuchen. Dauert ein wenig bis ich das
gemacht hab.
Danke schon mal. Feedback gibts später
Yalu X. schrieb:> Du kannst ihm bei der Optimierung dadurch helfen, dass du die> aufgerufene Funktion in die gleiche Quelldatei (oder in ein Headerfile,> dass von der Quelldatei eingebunden wird) schreibst. Dann sieht der> Compiler, welche Register tatsächlich überschrieben werden, und pusht> nur diese.
Das bring beim GCC aber nur dann was, wenn Inlining stattfindet d.h.
kein Funktionsaufruf mehr stattfindet. Wenn die Funktion nicht
geinlinet wird, dann nutzt GCC den bekannten, kleineren Fußabdruck der
Funktion nicht im Aufrufer aus.
Johann L. schrieb:> Das bring beim GCC aber nur dann was, wenn Inlining stattfindet
Davon, dass die Funktion geinlinet wird, bin ich jetzt mal
stillschweigend ausgegangen ;-)
Aber du hast schon recht, eine entsprechende Anmerkung wäre nützlich
gewesen.
Geschafft. Umstellt auf RAM data[] und ich fahre mit einem Pointer
drüber.
push/pop klappt, wobei ein reserviertes Register auch noch gesichert
wird. ???
Vielleicht würde noch ein Zugriff mit data[i] was bringen. wenn ich noch
lust hab hole ich das später nach.
Unten ist der Vergleich von vorher zu nacher. Warum allerdings die push
und pop vorher nicht gepasst haben kann ich mir nicht erklären. Es
wurden keine Funktionen aufgerufen. Der Optimierer hat da was
zusammengefaßt was wo anders auch noch zum Einsatz kam.
Jetzt schaut es besser aus mit meinen 36kHz PWM's.
1 Frage noch:
Wie kann man explizit für diese Interruptroutine das push und pop
komplett abschalten? Der Link funktioniert von oben nicht.
Danke für die Hilfe
Ausgangscode
2 0000038F STS 0x015D,R24 Store direct to data space
62
if (flashCompare == 255)
63
1 00000391 CPI R24,0xFF Compare with immediate
64
2 00000392 BRNE PC+0x04 Branch if not equal
65
flashDataAdress = EEPROM_SEQUENCE_START;
66
1 00000393 LDI R24,0x20 Load immediate
67
2 00000394 STS 0x0100,R24 Store direct to data space
68
}
69
2 00000396 POP R31 Pop register from stack
70
2 00000397 POP R30 Pop register from stack
71
2 00000398 POP R29 Pop register from stack
72
2 00000399 POP R28 Pop register from stack
73
2 0000039A POP R27 Pop register from stack
74
2 0000039B POP R26 Pop register from stack
75
2 0000039C POP R25 Pop register from stack
76
2 0000039D POP R24 Pop register from stack
77
2 0000039E POP R23 Pop register from stack
78
2 0000039F POP R22 Pop register from stack
79
2 000003A0 POP R21 Pop register from stack
80
2 000003A1 POP R20 Pop register from stack
81
2 000003A2 POP R19 Pop register from stack
82
2 000003A3 POP R18 Pop register from stack
83
2 000003A4 POP R0 Pop register from stack
84
1 000003A5 OUT 0x3B,R0 Out to I/O location
85
2 000003A6 POP R0 Pop register from stack
86
1 000003A7 OUT 0x3F,R0 Out to I/O location
87
2 000003A8 POP R0 Pop register from stack
88
2 000003A9 POP R1 Pop register from stack
89
2 000003AA RETI Interrupt return
90
91
134 CLK INNEN 56 CLK
Optimiert bzw über RAM *ptr rauscht nun über meinen Speicher
1
PORTA = *ptr++ |0b01000000; // Änderung für Frame auf dauer high
2
OCR0B = *ptr++; // Comparewert setzen
3
if (OCR0B == 255) // Überprüfen ob Ende der Sequenz erreicht, dann Pointer zurücksetzen auf Anfang
4
{
5
ptr = data;
6
}
7
TAKTE BEFEHLE
8
2 00000342 PUSH R1 Push register on stack
9
2 00000343 PUSH R0 Push register on stack
10
1 00000344 IN R0,0x3F In from I/O location
11
2 00000345 PUSH R0 Push register on stack
12
1 00000346 CLR R1 Clear Register
13
1 00000347 IN R0,0x3B In from I/O location
14
2 00000348 PUSH R0 Push register on stack
15
2 00000349 PUSH R24 Push register on stack
16
2 0000034A PUSH R25 Push register on stack
17
2 0000034B PUSH R30 Push register on stack
18
2 0000034C PUSH R31 Push register on stack 19
19
PORTA = *ptr++ |0b01000000; // �nderung f�r Frame auf dauer high
20
2 00000379 LDS R30,0x015B Load direct from data space
21
2 0000037B LDS R31,0x015C Load direct from data space
22
1 0000037D MOVW R24,R30 Copy register pair
23
2 0000037E ADIW R24,0x01 Add immediate to word
24
2 0000037F STS 0x015C,R25 Store direct to data space
25
2 00000381 STS 0x015B,R24 Store direct to data space
26
2 00000383 LDD R24,Z+0 Load indirect with displacement
27
1 00000384 ORI R24,0x40 Logical OR with immediate
28
1 00000385 OUT 0x02,R24 Out to I/O location
29
OCR0B = *ptr++; // Comparewert setzen
30
2 00000386 LDS R30,0x015B Load direct from data space
31
2 00000388 LDS R31,0x015C Load direct from data space
32
1 0000038A MOVW R24,R30 Copy register pair
33
2 0000038B ADIW R24,0x01 Add immediate to word
34
2 0000038C STS 0x015C,R25 Store direct to data space
35
2 0000038E STS 0x015B,R24 Store direct to data space
36
2 00000390 LDD R24,Z+0 Load indirect with displacement
37
1 00000391 OUT 0x28,R24 Out to I/O location
38
if (OCR0B == 255) // �berpr�fen ob Ende der Sequenz erreicht, dann Pointer zur�cksetzen auf Anfang
39
1 00000392 IN R24,0x28 In from I/O location
40
1 00000393 CPI R24,0xFF Compare with immediate
41
2 00000394 BRNE PC+0x07 Branch if not equal
42
ptr = data;
43
1 00000395 LDI R24,0x5D Load immediate
44
1 00000396 LDI R25,0x01 Load immediate
45
2 00000397 STS 0x015C,R25 Store direct to data space
46
2 00000399 STS 0x015B,R24 Store direct to data space
47
}
48
2 00000373 POP R31 Pop register from stack
49
2 00000374 POP R30 Pop register from stack
50
2 00000375 POP R25 Pop register from stack
51
2 00000376 POP R24 Pop register from stack
52
2 00000377 POP R0 Pop register from stack
53
1 00000378 OUT 0x3B,R0 Out to I/O location
54
2 00000379 POP R0 Pop register from stack
55
1 0000037A OUT 0x3F,R0 Out to I/O location
56
2 0000037B POP R0 Pop register from stack
57
2 0000037C POP R1 Pop register from stack
58
2 0000037D RETI Interrupt return
59
60
77 CLK innen 39 CLK
Orginal Optimiert
Overhead 2 x 39 2 x 19 51% gespart
Funktion 56 39 30% gespart
Summe 134 77 Faktor 1,74 schneller / 43% gespart
Jetzt ist der Ergeiz geweckt. Es befinden sich immer noch unnötige
Befehle beim Einsprung und auch beim Verlassen.
RESERVED 0x3B und SREG 0x3F wird gerettet. Ersteres macht nicht wiklich
sinn. Wäre nochmal 2 x 3 Takte.
Eigentlich bräuchte ich gar keine push und pop, denn das ist der
niederwertigste Interrupt und mein Hauptprogramm besteht aus while(1);
Wenn es wirklich so zeitkritisch ist, daß noch nichma SREG gesichert
werden darf, dann schreib das Zeugs in Assembler. Die Funktion ist
einfach genug dafür.
avr-gcc sichert SREG in jeder ISR, egal ob es verändert wird oder nicht.
Hallo,
Danke nochmal für die Hilfe. Ich habs in Assembler gemacht. 31+2 Takte
Hier die Lösung, um vielleicht dem einen oder anderem eine Hilfe zu
geben.
1. __attribute__((naked)) --> Der Interrupt springt ohne irgendwas zu
machen hier rein. PUSH/POP und RET(i) nicht vergessen
2. Die Adresse von data wird in R26,R27 X vom Compiler gesetzt (ldi)
3. R2 beinhaltet Variable die nur im Register gehalten wird.
4. PORTA = data(p++) |0b01000000; // Ausgabe
OCR0B = data(p++); // Comparewert setzen
if (OCR0B == 255) // Am Ende von vorne beginnen
{
p = 0;
}
1
__attribute__((naked))
2
ISR(TIMER0_COMPB_vect) // Nach entsprechendem Comparewert der Sequenze
Stellt sich nur die Frage, warum du Input-Operanden angibst, sie aber
nicht verwendest...
Solcher Code wird wesentlich besser wartbar, wenn du ihm ein eigenes
Assembler-Modul spendierst, etwa so:
1
#define __SFR_OFFSET 0
2
#include <avr/io.h>
3
4
.text
5
6
.type TIMER0_COMPB_vect, @function
7
.global TIMER0_COMPB_vect
8
9
TIMER0_COMPB_vect:
10
11
push r0
12
in r0, SREG
13
push r0
14
15
clr r1
16
add r26, r2
17
adc r27, r1
18
ld r25, X+
19
ori r25, 1 << 6
20
out PORTA, r25
21
ld r25, X+
22
out OCR0B, r25
23
ldi r16, 2
24
add r2, r16
25
cpi r25, 0xff
26
brne 1f
27
clr r2
28
1:
29
sbi PORTA, 6
30
31
pop r0
32
out SREG, r0
33
pop r0
34
reti
35
.size TIMER0_COMPB_vect, .-TIMER0_COMPB_vect
Zudem ist gemäß GCC-Doku in einer naked-Funktion ausschließlich
Inline-Assembler ohne Operanden erlaubt.
Dann verstehe ich nicht, warum du R0 und SREG sicherst? Das
Hauptprogramm besteht doch nur aus einer leeren Endlosschleife?
In dem Fall brauchst du überhaupt keine Register zu sichern, und anstatt
R2 bieten sich bessere Register an, um Werte zu addieren.
Die Abstände zwischen den Adressen, an denen die Daten aus dem Array
gelesen werden, werden ständig größer (der Adresse wächst quadratisch).
Ist das wirklich so gewollt? Und wozu Pin 6 des Ports setzen, wenn es
oben schon durch das ORI gesetzt wurde?
Johann L. schrieb:> Solcher Code wird wesentlich besser wartbar, wenn du ihm ein eigenes> Assembler-Modul spendierst, etwa so:
Ich will ja nicht der Spielverderber sein, aber im Interrupt
irgendwelche ungesicherten Register zu verwenden, ist grob daneben.
Das funktioniert nur dann, wenn man das komplette Programm in Assembler
schreibt und genau festlegt, welches Register welche Funktion hat.