Archiv verlassen und diese Seite im Standarddesign anzeigen : Sammel-Interrupt mit avr-gcc
SprinterSB
23.02.2006, 18:25
Hallo,
ich würde gerne mit avr-gcc einen Sammel-Interrupt machen, und zwar für zwei ISRs. Der Code ist praktisch der gleiche, so daß ich den Codevarbrauch an der Stelle gerne halbieren würde.
Ich hab zwar eine Lösung, aber das ist HACK, ich trau mich garnicht, das hier zu posten.
Probleme: AVR setzt das jeweilige Flag beim Auslösen der IRQ zurück, ich kann's also nicht testen
Funktionen stehen im asm möglicherweise in anderer Reihenfolge als in der C-Quelle
In den umzubiegenden ISRs dürfen weder Register verwendet werden, noch das SREG verändert werden (oder man braucht nen ISR-Prolog, und die Codeersparnis ist futsch).
Jemand ne zündende Idee?
Warum nicht einfach aus der ISR jeweils dieselbe Funktion aufrufen:
SIGNAL( SIG_INTERRUPT0 )
{
serveISR();
}
SIGNAL( SIG_INTERRUPT1 )
{
serveISR();
}
void serveISR()
{
}
Unterm Strich sorgt das Makro SIGNAL nur dafür, dass ein entsprechendes gcc __attribute(signal) hinter die Funktionsdefinition gesetzt wird, was den Compiler anweist, die Adresse der Funktion in der Interruptvektortabelle zu hinterlegen. Das einizige, was hier hinzu kommt, ist ein weiterer Funktionsaufruf.
SprinterSB
23.02.2006, 19:22
Es kommt noch mehr dazu. Ein ISR-Prolog/Epilog ist wesentlich länger, als ein normaler Funktions-Prolog, zumal wenn in der ISR eine Funktion aufgerufen wird.
Ursprünglich geplant war so was:
void __attribute__ ((naked))
SIG_INTERRUPT0()
{
// fallthru
}
SIGNAL (SIG_INTERRUPT1)
{
// serve ISR
}
Aber in INT1 sehe ich nicht, ob ich von 0 oder von 1 komme. Ich hatte geplant, das USR-Bit zu verwenden, aber das geht auch nicht, weil die ISRs selbst in einer ISR "scharf gemacht werden", und im Epilog natürlich wieder das SREG hergestellt wird.
Ich fürchte, dass ich's noch nicht ganz verstanden habe.
Sobald Du in C eine SIGNAL Routine schreibst, wird das SREG gerettet und am Ende der Routine wieder restauriert. Es gehen aber keine Interrupts verloren. Es sei denn, Deine ISR dauert so lange, dass mehrer Interrupts auf ein und derselben Quelle angekommen.
Wenn Du aus den ISRs jeweils diesselbe Funktion aufrufst verschenkst Du praktisch nur zwei Byte Flash für den Sprungbefehlt, die Dir der Optimizer vielleicht sogar wieder gibt.
SprinterSB
23.02.2006, 20:58
Nicht ganz, das SREG (oder was auch immer) wird nur gerettet, wenn ich der ISR einen Pro/Epilog gebe, was ich aber mit naked verhindern kann.
Zunächst mal eine minimal-ISR, die nix macht:
SIGNAL (SIG_INTERRUPT1)
{
}
00000060 <__vector_2>:
60: 1f 92 push r1
62: 0f 92 push r0
64: 0f b6 in r0, 0x3f ; 63
66: 0f 92 push r0
68: 11 24 eor r1, r1
6a: 0f 90 pop r0
6c: 0f be out 0x3f, r0 ; 63
6e: 0f 90 pop r0
70: 1f 90 pop r1
72: 18 95 reti
Dann eine ISR, die nix macht, ausser eine parameterlose Func zu rufen:
SIGNAL (SIG_INTERRUPT1)
{
foo();
}
00000060 <__vector_2>:
60: 1f 92 push r1
62: 0f 92 push r0
64: 0f b6 in r0, 0x3f ; 63
66: 0f 92 push r0
68: 11 24 eor r1, r1
6a: 2f 93 push r18
6c: 3f 93 push r19
6e: 4f 93 push r20
70: 5f 93 push r21
72: 6f 93 push r22
74: 7f 93 push r23
76: 8f 93 push r24
78: 9f 93 push r25
7a: af 93 push r26
7c: bf 93 push r27
7e: ef 93 push r30
80: ff 93 push r31
82: ec df rcall .-40 ; 0x5c
84: ff 91 pop r31
86: ef 91 pop r30
88: bf 91 pop r27
8a: af 91 pop r26
8c: 9f 91 pop r25
8e: 8f 91 pop r24
90: 7f 91 pop r23
92: 6f 91 pop r22
94: 5f 91 pop r21
96: 4f 91 pop r20
98: 3f 91 pop r19
9a: 2f 91 pop r18
9c: 0f 90 pop r0
9e: 0f be out 0x3f, r0 ; 63
a0: 0f 90 pop r0
a2: 1f 90 pop r1
a4: 18 95 reti
Der Rahmen ist deutlich fetter, und da sind noch keine Werte übergeben. Ausserdem wird das in jeder ISR wiederholt, die foo() aufruft.
Was ich gerne hätte, wäre so was:
0000005e <__vector_1>:
5e: 00 c0 rjmp .+0 ; 0x60
00000060 <__vector_2>:
60: 1f 92 push r1
62: 0f 92 push r0
64: 0f b6 in r0, 0x3f ; 63
66: 0f 92 push r0
68: 11 24 eor r1, r1
6a: 0f 90 pop r0
6c: 0f be out 0x3f, r0 ; 63
6e: 0f 90 pop r0
70: 1f 90 pop r1
72: 18 95 reti
was etwa durch die naked-Sequenz erzeugt wird
void __attribute__ ((naked))
SIG_INTERRUPT0()
{
asm volatile ("rjmp __vector_2");
}
SIGNAL (SIG_INTERRUPT1)
{
}
Was fehlt, ist die Unterscheidung, ob ich von INT0 komme oder von INT1...
Für den Eintrag in die Vektortabelle ist übrigens nicht das signal- oder interrupt-Attribut verantwortlich, sondern alleine der spezielle Name der Funktion __vector_n, die dem Linker bekannt ist (der Linker weiss eh nix von Attributen). Wenn du also ne Funktion schreibst
void __vector_10()
{} dann wird ein Eintrag in die VecTab gemacht!
Oh, wieder was gelernt!
Warum dann nicht einfach in __vector_10 und __vector11 die Funktion foo( paramVonWoIchKomme) aufrufen?
__vector_10( void )
{
foo( 10 );
}
__vector_11( void )
{
foo( 11 );
}
SprinterSB
23.02.2006, 21:17
*AUTSCH* dann schmiert's ab, weil die ISRs keinen passenden Rahmen haben und nicht mit reti verlassen werden. Nur ein Eintrag in die VecTab reicht nicht, um eine Funktion zur ISR zu machen:
00000074 <__vector_10>:
74: 8a e0 ldi r24, 0x0A ; 10
76: 90 e0 ldi r25, 0x00 ; 0
78: f1 df rcall .-30 ; 0x5c
7a: 08 95 ret
0000007c <__vector_11>:
7c: 8b e0 ldi r24, 0x0B ; 11
7e: 90 e0 ldi r25, 0x00 ; 0
80: ed df rcall .-38 ; 0x5c
82: 08 95 ret
Der Zusatzaufwand bei Aufruf von foo (natürlich verpackt in ISR-Rahmen) ist schon fast mehr, als foo selber lang ist! Und foo als inline machen bringt auch nix, dann könnte ich die ISRs auch normal machen, und einfach alles austexten (austexten/inline wiederholt ja auch den Code n mal) würde also auch nix sparen :-(
Kürzer als das folgende bekomme ich es nicht hin:
#include <avr/signal.h>
#include <avr/interrupt.h>
#include <inttypes.h>
void foo( uint8_t param );
// key press down
SIGNAL( SIG_INTERRUPT0 )
{
foo( 0 );
}
void _VECTOR(2)( void ) __attribute__ ((naked));
void _VECTOR(2)( void )
{
foo(3);
__asm__ __volatile__ ("reti" ::);
}
void foo( uint8_t param )
{
}
Aus Vector(2) wird dann
/* function __vector_1 size 43 (3) */
.LFE3:
.size __vector_1, .-__vector_1
.global __vector_2
.type __vector_2, @function
__vector_2:
.LFB4:
.LM3:
/* prologue: frame size=0 */
/* prologue: naked */
/* prologue end (size=0) */
.LM4:
ldi r24,lo8(3)
call foo
.LM5:
/* #APP */
reti
Das Retten und Restaurieren von SREG muss dann von Hand rein.
SprinterSB
24.02.2006, 09:01
Und was ist mit R24?
Da steht der Übergabeparameter an foo(3) drin. Das optimiert der Compiler so (ist so auch der kleinste Code). Ansonsten wäre das ja
ldi r24, lo(3)
push r24; das kann wegoptimiert werden
gcc nutzt aber einige Register stets zur Parameterübergabe (bei eingeschalteter Optimierung).
Laßt mich mitreden:
Geht darum, von ZWEI verschiedenen Interrupt-Vektoren aus EINE Funktion aufzurufen ?
Ich würde an beiden ISR-vectoren schreiben, (wenn GCC das zuläßt)
<__vector_10>:
RCALL func_tion
RETI
<__vector_11>:
RCALL func_tion
RETI
func_tion: wie auch immer, aber mit RET, kein RETI (logo, aber eigentlich ist das auch wurst)
durch den RCALL steht an einer definierten stelle vom Stack, welche ISR das nun eigentlich ist , die könnt man sich holen
Der Return Code steht immer auf dem Stack (auch beim call).
Hier geht es in der Hauptsache darum, das möglichst einfach in C zu realisieren. In assembler ruft man einfach stets diesselbe Unterroutine auf. Der C Compiler generiert aber jede Menge zusätzlichen Code und den möchten wir nach Möglichkeit einsparen.
SprinterSB
24.02.2006, 10:57
Das push muss doch vor dem ldi stehen, sonst zerstört die ISR R24. Ausserdem "weiß" gcc nicht, welche Register eine Funktion verwendet, bzw. es wird davon ausgegangen, daß die Werte in R18-R27 und Z zerstört werden (siehe Registerverwendung (https://www.roboternetz.de/wissen/index.php/Avr-gcc#Registerverwendung)).
Jedenfalls scheint das ganze komplizierter zu sein, als es auf den ersten Blick erscheint...
:idea: So könnte es gehen, zumindest für 2 ISRs:
// Jump to Symbol sym
#define jump(sym) \
__asm__ __volatile ("rjmp " #sym)
// Set T-Flag
#define set() \
__asm__ __volatile ("set")
// Clear T-Flag
#define clt() \
__asm__ __volatile ("clt")
// Load Bit n with T-Flag
#define bld(n) \
({ \
unsigned char _x; \
__asm__ __volatile ("bld %0, %1" : "=r" (_x) : "M" (n)); \
_x & (1 << (n)); \
})
char blah;
SIGNAL (foo)
{
if (bld(0) & 1)
blah = 1;
else
blah = -1;
}
void __attribute__ ((naked))
SIG_INTERRUPT0()
{
clt();
jump (foo);
}
void __attribute__ ((naked))
SIG_INTERRUPT1()
{
set();
jump (foo);
}
Das ergibt
0000005c <foo>:
5c: 1f 92 push r1
5e: 0f 92 push r0
60: 0f b6 in r0, 0x3f ; 63
62: 0f 92 push r0
64: 11 24 eor r1, r1
66: 8f 93 push r24
;;;;;;;;;;;;; Prolog Ende
68: 80 f9 bld r24, 0
6a: 80 ff sbrs r24, 0
6c: 02 c0 rjmp .+4 ; 0x72
6e: 81 e0 ldi r24, 0x01 ; 1
70: 01 c0 rjmp .+2 ; 0x74
72: 8f ef ldi r24, 0xFF ; 255
74: 80 93 7b 00 sts 0x007B, r24
;;;;;;;;;;;;; Epilog Anfang
78: 8f 91 pop r24
7a: 0f 90 pop r0
7c: 0f be out 0x3f, r0 ; 63
7e: 0f 90 pop r0
80: 1f 90 pop r1
82: 18 95 reti
00000084 <__vector_1>:
84: e8 94 clt
86: ea cf rjmp .-44 ; 0x5c
00000088 <__vector_2>:
88: 68 94 set
8a: e8 cf rjmp .-48 ; 0x5c
Das geht aber nur mit 2 ISRs :-( Ausserdem wird das T-Flag durch die ISR zerstört. Weiter tragisch ist das nicht, weil es nicht von gcc verwendet wird. Aber sonstwo kann man es auch nicht mehr verwenden.
Das in einem globalen Register zu merken geht auch nicht. Das Register kann nur R16-R31 sein, weil R0-R15 kein andi, ori kennen. Und von den Registern kann man keines global machen, ausser evtl. R26, R27, was aber überall zu deutlich mieserem Code führen durfte!
Der Return Code steht immer auf dem Stack (auch beim call).
Aber geh', was sie nicht sagen.
Aber welche return-addresse steht beim Interrupt normalerweise dort ? richtig !
SprinterSB
24.02.2006, 11:02
@PickNick: Das Problem ist dann, in func_tion zu entscheiden, welche IRQ auftrat. __builtin_return_address(0) funktioniert nicht für avr-gcc. Dazu müsste man wissen, welche/wieviel Register verwendet werden, und was im Prolog gepusht wird, wie groß der Frame ist (falls vorhanden), etc
Ich würde schon gerne bei C bleiben, und nicht in asm schreiben...
Welcher Interrupt, ist aus der return addresse erkennbar (die return addresse zeigt ja in dem Fall mitten in die vektoren rein).
Geht natürlich nur bei "R"call (16 Bit ) die andern 16 Bit für den reti
Sprinter: Irgendwann mußt du wissen, ob du Hochsprache schreibst oder nicht. Solche platformabhängigen Schweinereien werden mit Recht von höheren Sprachen nicht supported.
@picknick. Latürnich :D Ich Depp.
@Sprinter: Mit dem Push meinte ich die Parameterübergabe über den Stack. R24 kann ja beliebig verwendet werden.
Was ist denn an meinem Vorschlag
void _VECTOR(2)( void ) __attribute__ ((naked));
void _VECTOR(2)( void )
{
foo(2);
__asm__ __volatile__ ("reti" ::);
}
auszusetzen? Kleiner ist der Assembler Code kaum zu bekommen.
Das funktioniert auch für mehr als zwei ISRs und die aufgerufene Funktion foo() weiss, von welcher ISR der Aufruf stammt.
BTW. RJUMP wird, da die maximale Sprunglänge begrenzt ist, nicht immer funktionieren.
SprinterSB
24.02.2006, 11:26
Die return-Adresse zeigt nicht in die VecTab, weil in der VecTab ein jump steht (und nicht ein call). Das Problem ist nicht, die ret-Adresse auszuwerten, sondern an die Ort der ret-Adresse zu kommen. Das einzige, was es dafür gibt, wäre __builtin_return_address (ist aber ein Erratum, s.o.).
Die Return-Adresse oben wäre __vector_10+2, was der Linker zu einer Konstanten auflöst. Aber wie formulierst du den Ort der Return-Adresse?
Schreiben will ich in C, keine Frage. Aber die Welt ist eben nicht Schwarz-Weiß. Manchmal muss man sich in die Unterwelt begeben. Und den Aufenthalt dort macht man so kurz und knapp es eben geht.
SprinterSB
24.02.2006, 11:30
@ogni: Das geht nicht, weil dir foo() Register kaputt macht, die nicht wieder hergestellt werden. Du schreist also ISR mit normalem Funktions-Prolog/Epilog, was die Register-Sicherung angeht. Das crasht!
Hier mal ein etwas kompletteres Beispiel, bei dem man sieht, dass bei foo() nichts kaputt geschrieben wird. Es geht eigentlich nur um R24. Das kann man in der ISR ja noch per asm pushen/popen
#include <avr/interrupt.h>
#include <avr/signal.h>
#include <inttypes.h>
void foo( uint8_t whichISR )
{
volatile uint8_t isr = whichISR;
}
void _VECTOR(1)( void ) __attribute__ ((naked));
void _VECTOR(1)( void )
{
foo(1);
__asm__ __volatile__ ("reti" ::);
}
void _VECTOR(2)( void ) __attribute__ ((naked));
void _VECTOR(2)( void )
{
foo(2);
__asm__ __volatile__ ("reti" ::);
}
void _VECTOR(3)( void ) __attribute__ ((naked));
void _VECTOR(3)( void )
{
foo(3);
__asm__ __volatile__ ("reti" ::);
}
Wie assembliert mit -Os zu:
.file "scratch.c"
.arch atmega48
__SREG__ = 0x3f
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__tmp_reg__ = 0
__zero_reg__ = 1
.global __do_copy_data
.global __do_clear_bss
.section .debug_abbrev,"",@progbits
.Ldebug_abbrev0:
.section .debug_info,"",@progbits
.Ldebug_info0:
.section .debug_line,"",@progbits
.Ldebug_line0:
.text
.Ltext0:
.global foo
.type foo, @function
foo:
.LFB3:
.LM1:
/* prologue: frame size=1 */
push r28
push r29
in r28,__SP_L__
in r29,__SP_H__
sbiw r28,1
in __tmp_reg__,__SREG__
cli
out __SP_H__,r29
out __SREG__,__tmp_reg__
out __SP_L__,r28
/* prologue end (size=10) */
.LM2:
std Y+1,r24
/* epilogue: frame size=1 */
adiw r28,1
in __tmp_reg__,__SREG__
cli
out __SP_H__,r29
out __SREG__,__tmp_reg__
out __SP_L__,r28
pop r29
pop r28
ret
/* epilogue end (size=9) */
/* function foo size 20 (1) */
.LFE3:
.size foo, .-foo
.global __vector_1
.type __vector_1, @function
__vector_1:
.LFB4:
.LM3:
/* prologue: frame size=0 */
/* prologue: naked */
/* prologue end (size=0) */
.LM4:
ldi r24,lo8(1)
rcall foo
.LM5:
/* #APP */
reti
/* #NOAPP */
/* epilogue: frame size=0 */
/* epilogue: naked */
/* epilogue end (size=0) */
/* function __vector_1 size 4 (4) */
.LFE4:
.size __vector_1, .-__vector_1
.global __vector_2
.type __vector_2, @function
__vector_2:
.LFB5:
.LM6:
/* prologue: frame size=0 */
/* prologue: naked */
/* prologue end (size=0) */
.LM7:
ldi r24,lo8(2)
rcall foo
.LM8:
/* #APP */
reti
/* #NOAPP */
/* epilogue: frame size=0 */
/* epilogue: naked */
/* epilogue end (size=0) */
/* function __vector_2 size 4 (4) */
.LFE5:
.size __vector_2, .-__vector_2
.global __vector_3
.type __vector_3, @function
__vector_3:
.LFB6:
.LM9:
/* prologue: frame size=0 */
/* prologue: naked */
/* prologue end (size=0) */
.LM10:
ldi r24,lo8(3)
rcall foo
.LM11:
/* #APP */
reti
/* #NOAPP */
/* epilogue: frame size=0 */
/* epilogue: naked */
/* epilogue end (size=0) */
/* function __vector_3 size 4 (4) */
.LFE6:
.size __vector_3, .-__vector_3
.Letext0:
.section .debug_line
.long .LELT0-.LSLT0
.LSLT0:
.word 2
.long .LELTP0-.LASLTP0
.LASLTP0:
.byte 0x1
.byte 0x1
.byte 0xf6
.byte 0xf5
.byte 0xa
.byte 0x0
.byte 0x1
.byte 0x1
.byte 0x1
.byte 0x1
.byte 0x0
.byte 0x0
.byte 0x0
.byte 0x1
.ascii ".."
.byte 0
.ascii "C:/Programme/WinAVR/bin/../lib/gcc/avr/3.4.3/../../../../avr"
.ascii "/include"
.byte 0
.byte 0x0
.string "stdint.h"
.uleb128 0x2
.uleb128 0x0
.uleb128 0x0
.string "scratch.c"
.uleb128 0x1
.uleb128 0x0
.uleb128 0x0
.byte 0x0
.LELTP0:
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM1
.byte 0x4
.uleb128 0x2
.byte 0x19
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM2
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM3
.byte 0x19
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM4
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM5
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM6
.byte 0x1a
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM7
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM8
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM9
.byte 0x1a
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM10
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM11
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .Letext0
.byte 0x0
.uleb128 0x1
.byte 0x1
.LELT0:
.section .debug_info
.long 220
.word 2
.long .Ldebug_abbrev0
.byte 0x2
.uleb128 0x1
.long .Ldebug_line0
.word .Letext0
.word .Ltext0
.long .LASF10
.byte 0x1
.long .LASF11
.long .LASF12
.uleb128 0x2
.long .LASF0
.byte 0x1
.byte 0x6
.uleb128 0x3
.long .LASF13
.byte 0x1
.byte 0x46
.long 51
.uleb128 0x2
.long .LASF1
.byte 0x1
.byte 0x8
.uleb128 0x4
.string "int"
.byte 0x2
.byte 0x5
.uleb128 0x2
.long .LASF2
.byte 0x2
.byte 0x7
.uleb128 0x2
.long .LASF3
.byte 0x4
.byte 0x5
.uleb128 0x2
.long .LASF4
.byte 0x4
.byte 0x7
.uleb128 0x2
.long .LASF5
.byte 0x8
.byte 0x5
.uleb128 0x2
.long .LASF6
.byte 0x8
.byte 0x7
.uleb128 0x5
.long 152
.byte 0x1
.string "foo"
.byte 0x2
.byte 0x6
.byte 0x1
.word .LFB3
.word .LFE3
.byte 0x6
.byte 0x6c
.byte 0x93
.uleb128 0x1
.byte 0x6d
.byte 0x93
.uleb128 0x1
.uleb128 0x6
.long .LASF14
.byte 0x2
.byte 0x5
.long 40
.byte 0x1
.byte 0x68
.uleb128 0x7
.string "isr"
.byte 0x2
.byte 0x7
.long 152
.byte 0x2
.byte 0x91
.sleb128 1
.byte 0x0
.uleb128 0x8
.long 40
.uleb128 0x9
.byte 0x1
.long .LASF7
.byte 0x2
.byte 0xc
.byte 0x1
.word .LFB4
.word .LFE4
.byte 0x8
.byte 0x90
.uleb128 0x20
.byte 0x93
.uleb128 0x1
.byte 0x90
.uleb128 0x21
.byte 0x93
.uleb128 0x1
.uleb128 0x9
.byte 0x1
.long .LASF8
.byte 0x2
.byte 0x14
.byte 0x1
.word .LFB5
.word .LFE5
.byte 0x8
.byte 0x90
.uleb128 0x20
.byte 0x93
.uleb128 0x1
.byte 0x90
.uleb128 0x21
.byte 0x93
.uleb128 0x1
.uleb128 0x9
.byte 0x1
.long .LASF9
.byte 0x2
.byte 0x1c
.byte 0x1
.word .LFB6
.word .LFE6
.byte 0x8
.byte 0x90
.uleb128 0x20
.byte 0x93
.uleb128 0x1
.byte 0x90
.uleb128 0x21
.byte 0x93
.uleb128 0x1
.byte 0x0
.section .debug_abbrev
.uleb128 0x1
.uleb128 0x11
.byte 0x1
.uleb128 0x10
.uleb128 0x6
.uleb128 0x12
.uleb128 0x1
.uleb128 0x11
.uleb128 0x1
.uleb128 0x25
.uleb128 0xe
.uleb128 0x13
.uleb128 0xb
.uleb128 0x3
.uleb128 0xe
.uleb128 0x1b
.uleb128 0xe
.byte 0x0
.byte 0x0
.uleb128 0x2
.uleb128 0x24
.byte 0x0
.uleb128 0x3
.uleb128 0xe
.uleb128 0xb
.uleb128 0xb
.uleb128 0x3e
.uleb128 0xb
.byte 0x0
.byte 0x0
.uleb128 0x3
.uleb128 0x16
.byte 0x0
.uleb128 0x3
.uleb128 0xe
.uleb128 0x3a
.uleb128 0xb
.uleb128 0x3b
.uleb128 0xb
.uleb128 0x49
.uleb128 0x13
.byte 0x0
.byte 0x0
.uleb128 0x4
.uleb128 0x24
.byte 0x0
.uleb128 0x3
.uleb128 0x8
.uleb128 0xb
.uleb128 0xb
.uleb128 0x3e
.uleb128 0xb
.byte 0x0
.byte 0x0
.uleb128 0x5
.uleb128 0x2e
.byte 0x1
.uleb128 0x1
.uleb128 0x13
.uleb128 0x3f
.uleb128 0xc
.uleb128 0x3
.uleb128 0x8
.uleb128 0x3a
.uleb128 0xb
.uleb128 0x3b
.uleb128 0xb
.uleb128 0x27
.uleb128 0xc
.uleb128 0x11
.uleb128 0x1
.uleb128 0x12
.uleb128 0x1
.uleb128 0x40
.uleb128 0xa
.byte 0x0
.byte 0x0
.uleb128 0x6
.uleb128 0x5
.byte 0x0
.uleb128 0x3
.uleb128 0xe
.uleb128 0x3a
.uleb128 0xb
.uleb128 0x3b
.uleb128 0xb
.uleb128 0x49
.uleb128 0x13
.uleb128 0x2
.uleb128 0xa
.byte 0x0
.byte 0x0
.uleb128 0x7
.uleb128 0x34
.byte 0x0
.uleb128 0x3
.uleb128 0x8
.uleb128 0x3a
.uleb128 0xb
.uleb128 0x3b
.uleb128 0xb
.uleb128 0x49
.uleb128 0x13
.uleb128 0x2
.uleb128 0xa
.byte 0x0
.byte 0x0
.uleb128 0x8
.uleb128 0x35
.byte 0x0
.uleb128 0x49
.uleb128 0x13
.byte 0x0
.byte 0x0
.uleb128 0x9
.uleb128 0x2e
.byte 0x0
.uleb128 0x3f
.uleb128 0xc
.uleb128 0x3
.uleb128 0xe
.uleb128 0x3a
.uleb128 0xb
.uleb128 0x3b
.uleb128 0xb
.uleb128 0x27
.uleb128 0xc
.uleb128 0x11
.uleb128 0x1
.uleb128 0x12
.uleb128 0x1
.uleb128 0x40
.uleb128 0xa
.byte 0x0
.byte 0x0
.byte 0x0
.section .debug_pubnames,"",@progbits
.long 67
.word 2
.long .Ldebug_info0
.long 224
.long 100
.string "foo"
.long 157
.string "__vector_1"
.long 179
.string "__vector_2"
.long 201
.string "__vector_3"
.long 0
.section .debug_aranges,"",@progbits
.long 16
.word 2
.long .Ldebug_info0
.byte 0x2
.byte 0x0
.word .Ltext0
.word .Letext0-.Ltext0
.word 0
.word 0
.section .debug_str,"MS",@progbits,1
.LASF5:
.string "long long int"
.LASF12:
.string "D:\\priv\\Tech\\atmel\\learning\\scratch\\default"
.LASF2:
.string "unsigned int"
.LASF10:
.string "GNU C 3.4.3"
.LASF4:
.string "long unsigned int"
.LASF6:
.string "long long unsigned int"
.LASF14:
.string "whichISR"
.LASF13:
.string "uint8_t"
.LASF1:
.string "unsigned char"
.LASF3:
.string "long int"
.LASF7:
.string "__vector_1"
.LASF8:
.string "__vector_2"
.LASF9:
.string "__vector_3"
.LASF0:
.string "signed char"
.LASF11:
.string "../scratch.c"
/* File "../scratch.c": code 32 = 0x0020 ( 13), prologues 10, epilogues 9 */
foo() macht keine Register kaputt, und da der Mega48 ein kleineres Flash hat, wird auch RCALL benutzt. foo() kann durch den Übergabeparameter feststellen von wo der Aufruf kommt, und in den ISR steht der richtige RETI. Die interne Variable in foo ist im Beispiel nur volatile damit der Compiler sie nicht wegoptimiert.
Hier mit retten von r24. Jetzt sollte nichts mehr kaputt gehen:
#include <avr/interrupt.h>
#include <avr/signal.h>
#include <inttypes.h>
void foo( uint8_t whichISR )
{
volatile uint8_t isr = whichISR;
}
void _VECTOR(1)( void ) __attribute__ ((naked));
void _VECTOR(1)( void )
{
__asm__ __volatile__ ("push r24" ::);
foo(1);
__asm__ __volatile__ ("pop" ::);
__asm__ __volatile__ ("reti" ::);
}
void _VECTOR(2)( void ) __attribute__ ((naked));
void _VECTOR(2)( void )
{
__asm__ __volatile__ ("push r24" ::);
foo(2);
__asm__ __volatile__ ("pop" ::);
__asm__ __volatile__ ("reti" ::);
}
void _VECTOR(3)( void ) __attribute__ ((naked));
void _VECTOR(3)( void )
{
__asm__ __volatile__ ("push r24" ::);
foo(3);
__asm__ __volatile__ ("pop" ::);
__asm__ __volatile__ ("reti" ::);
}
.file "scratch.c"
.arch atmega48
__SREG__ = 0x3f
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__tmp_reg__ = 0
__zero_reg__ = 1
.global __do_copy_data
.global __do_clear_bss
.section .debug_abbrev,"",@progbits
.Ldebug_abbrev0:
.section .debug_info,"",@progbits
.Ldebug_info0:
.section .debug_line,"",@progbits
.Ldebug_line0:
.text
.Ltext0:
.global foo
.type foo, @function
foo:
.LFB3:
.LM1:
/* prologue: frame size=1 */
push r28
push r29
in r28,__SP_L__
in r29,__SP_H__
sbiw r28,1
in __tmp_reg__,__SREG__
cli
out __SP_H__,r29
out __SREG__,__tmp_reg__
out __SP_L__,r28
/* prologue end (size=10) */
.LM2:
std Y+1,r24
/* epilogue: frame size=1 */
adiw r28,1
in __tmp_reg__,__SREG__
cli
out __SP_H__,r29
out __SREG__,__tmp_reg__
out __SP_L__,r28
pop r29
pop r28
ret
/* epilogue end (size=9) */
/* function foo size 20 (1) */
.LFE3:
.size foo, .-foo
.global __vector_1
.type __vector_1, @function
__vector_1:
.LFB4:
.LM3:
/* prologue: frame size=0 */
/* prologue: naked */
/* prologue end (size=0) */
.LM4:
/* #APP */
push r24
.LM5:
/* #NOAPP */
ldi r24,lo8(1)
rcall foo
.LM6:
/* #APP */
pop
.LM7:
reti
/* #NOAPP */
/* epilogue: frame size=0 */
/* epilogue: naked */
/* epilogue end (size=0) */
/* function __vector_1 size 8 (8) */
.LFE4:
.size __vector_1, .-__vector_1
.global __vector_2
.type __vector_2, @function
__vector_2:
.LFB5:
.LM8:
/* prologue: frame size=0 */
/* prologue: naked */
/* prologue end (size=0) */
.LM9:
/* #APP */
push r24
.LM10:
/* #NOAPP */
ldi r24,lo8(2)
rcall foo
.LM11:
/* #APP */
pop
.LM12:
reti
/* #NOAPP */
/* epilogue: frame size=0 */
/* epilogue: naked */
/* epilogue end (size=0) */
/* function __vector_2 size 8 (8) */
.LFE5:
.size __vector_2, .-__vector_2
.global __vector_3
.type __vector_3, @function
__vector_3:
.LFB6:
.LM13:
/* prologue: frame size=0 */
/* prologue: naked */
/* prologue end (size=0) */
.LM14:
/* #APP */
push r24
.LM15:
/* #NOAPP */
ldi r24,lo8(3)
rcall foo
.LM16:
/* #APP */
pop
.LM17:
reti
/* #NOAPP */
/* epilogue: frame size=0 */
/* epilogue: naked */
/* epilogue end (size=0) */
/* function __vector_3 size 8 (8) */
.LFE6:
.size __vector_3, .-__vector_3
.Letext0:
.section .debug_line
.long .LELT0-.LSLT0
.LSLT0:
.word 2
.long .LELTP0-.LASLTP0
.LASLTP0:
.byte 0x1
.byte 0x1
.byte 0xf6
.byte 0xf5
.byte 0xa
.byte 0x0
.byte 0x1
.byte 0x1
.byte 0x1
.byte 0x1
.byte 0x0
.byte 0x0
.byte 0x0
.byte 0x1
.ascii ".."
.byte 0
.ascii "C:/Programme/WinAVR/bin/../lib/gcc/avr/3.4.3/../../../../avr"
.ascii "/include"
.byte 0
.byte 0x0
.string "stdint.h"
.uleb128 0x2
.uleb128 0x0
.uleb128 0x0
.string "scratch.c"
.uleb128 0x1
.uleb128 0x0
.uleb128 0x0
.byte 0x0
.LELTP0:
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM1
.byte 0x4
.uleb128 0x2
.byte 0x19
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM2
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM3
.byte 0x19
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM4
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM5
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM6
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM7
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM8
.byte 0x1a
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM9
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM10
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM11
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM12
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM13
.byte 0x1a
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM14
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM15
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM16
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .LM17
.byte 0x15
.byte 0x0
.uleb128 0x3
.byte 0x2
.word .Letext0
.byte 0x0
.uleb128 0x1
.byte 0x1
.LELT0:
.section .debug_info
.long 220
.word 2
.long .Ldebug_abbrev0
.byte 0x2
.uleb128 0x1
.long .Ldebug_line0
.word .Letext0
.word .Ltext0
.long .LASF10
.byte 0x1
.long .LASF11
.long .LASF12
.uleb128 0x2
.long .LASF0
.byte 0x1
.byte 0x6
.uleb128 0x3
.long .LASF13
.byte 0x1
.byte 0x46
.long 51
.uleb128 0x2
.long .LASF1
.byte 0x1
.byte 0x8
.uleb128 0x4
.string "int"
.byte 0x2
.byte 0x5
.uleb128 0x2
.long .LASF2
.byte 0x2
.byte 0x7
.uleb128 0x2
.long .LASF3
.byte 0x4
.byte 0x5
.uleb128 0x2
.long .LASF4
.byte 0x4
.byte 0x7
.uleb128 0x2
.long .LASF5
.byte 0x8
.byte 0x5
.uleb128 0x2
.long .LASF6
.byte 0x8
.byte 0x7
.uleb128 0x5
.long 152
.byte 0x1
.string "foo"
.byte 0x2
.byte 0x6
.byte 0x1
.word .LFB3
.word .LFE3
.byte 0x6
.byte 0x6c
.byte 0x93
.uleb128 0x1
.byte 0x6d
.byte 0x93
.uleb128 0x1
.uleb128 0x6
.long .LASF14
.byte 0x2
.byte 0x5
.long 40
.byte 0x1
.byte 0x68
.uleb128 0x7
.string "isr"
.byte 0x2
.byte 0x7
.long 152
.byte 0x2
.byte 0x91
.sleb128 1
.byte 0x0
.uleb128 0x8
.long 40
.uleb128 0x9
.byte 0x1
.long .LASF7
.byte 0x2
.byte 0xc
.byte 0x1
.word .LFB4
.word .LFE4
.byte 0x8
.byte 0x90
.uleb128 0x20
.byte 0x93
.uleb128 0x1
.byte 0x90
.uleb128 0x21
.byte 0x93
.uleb128 0x1
.uleb128 0x9
.byte 0x1
.long .LASF8
.byte 0x2
.byte 0x16
.byte 0x1
.word .LFB5
.word .LFE5
.byte 0x8
.byte 0x90
.uleb128 0x20
.byte 0x93
.uleb128 0x1
.byte 0x90
.uleb128 0x21
.byte 0x93
.uleb128 0x1
.uleb128 0x9
.byte 0x1
.long .LASF9
.byte 0x2
.byte 0x20
.byte 0x1
.word .LFB6
.word .LFE6
.byte 0x8
.byte 0x90
.uleb128 0x20
.byte 0x93
.uleb128 0x1
.byte 0x90
.uleb128 0x21
.byte 0x93
.uleb128 0x1
.byte 0x0
.section .debug_abbrev
.uleb128 0x1
.uleb128 0x11
.byte 0x1
.uleb128 0x10
.uleb128 0x6
.uleb128 0x12
.uleb128 0x1
.uleb128 0x11
.uleb128 0x1
.uleb128 0x25
.uleb128 0xe
.uleb128 0x13
.uleb128 0xb
.uleb128 0x3
.uleb128 0xe
.uleb128 0x1b
.uleb128 0xe
.byte 0x0
.byte 0x0
.uleb128 0x2
.uleb128 0x24
.byte 0x0
.uleb128 0x3
.uleb128 0xe
.uleb128 0xb
.uleb128 0xb
.uleb128 0x3e
.uleb128 0xb
.byte 0x0
.byte 0x0
.uleb128 0x3
.uleb128 0x16
.byte 0x0
.uleb128 0x3
.uleb128 0xe
.uleb128 0x3a
.uleb128 0xb
.uleb128 0x3b
.uleb128 0xb
.uleb128 0x49
.uleb128 0x13
.byte 0x0
.byte 0x0
.uleb128 0x4
.uleb128 0x24
.byte 0x0
.uleb128 0x3
.uleb128 0x8
.uleb128 0xb
.uleb128 0xb
.uleb128 0x3e
.uleb128 0xb
.byte 0x0
.byte 0x0
.uleb128 0x5
.uleb128 0x2e
.byte 0x1
.uleb128 0x1
.uleb128 0x13
.uleb128 0x3f
.uleb128 0xc
.uleb128 0x3
.uleb128 0x8
.uleb128 0x3a
.uleb128 0xb
.uleb128 0x3b
.uleb128 0xb
.uleb128 0x27
.uleb128 0xc
.uleb128 0x11
.uleb128 0x1
.uleb128 0x12
.uleb128 0x1
.uleb128 0x40
.uleb128 0xa
.byte 0x0
.byte 0x0
.uleb128 0x6
.uleb128 0x5
.byte 0x0
.uleb128 0x3
.uleb128 0xe
.uleb128 0x3a
.uleb128 0xb
.uleb128 0x3b
.uleb128 0xb
.uleb128 0x49
.uleb128 0x13
.uleb128 0x2
.uleb128 0xa
.byte 0x0
.byte 0x0
.uleb128 0x7
.uleb128 0x34
.byte 0x0
.uleb128 0x3
.uleb128 0x8
.uleb128 0x3a
.uleb128 0xb
.uleb128 0x3b
.uleb128 0xb
.uleb128 0x49
.uleb128 0x13
.uleb128 0x2
.uleb128 0xa
.byte 0x0
.byte 0x0
.uleb128 0x8
.uleb128 0x35
.byte 0x0
.uleb128 0x49
.uleb128 0x13
.byte 0x0
.byte 0x0
.uleb128 0x9
.uleb128 0x2e
.byte 0x0
.uleb128 0x3f
.uleb128 0xc
.uleb128 0x3
.uleb128 0xe
.uleb128 0x3a
.uleb128 0xb
.uleb128 0x3b
.uleb128 0xb
.uleb128 0x27
.uleb128 0xc
.uleb128 0x11
.uleb128 0x1
.uleb128 0x12
.uleb128 0x1
.uleb128 0x40
.uleb128 0xa
.byte 0x0
.byte 0x0
.byte 0x0
.section .debug_pubnames,"",@progbits
.long 67
.word 2
.long .Ldebug_info0
.long 224
.long 100
.string "foo"
.long 157
.string "__vector_1"
.long 179
.string "__vector_2"
.long 201
.string "__vector_3"
.long 0
.section .debug_aranges,"",@progbits
.long 16
.word 2
.long .Ldebug_info0
.byte 0x2
.byte 0x0
.word .Ltext0
.word .Letext0-.Ltext0
.word 0
.word 0
.section .debug_str,"MS",@progbits,1
.LASF5:
.string "long long int"
.LASF12:
.string "D:\\priv\\Tech\\atmel\\learning\\scratch\\default"
.LASF2:
.string "unsigned int"
.LASF10:
.string "GNU C 3.4.3"
.LASF4:
.string "long unsigned int"
.LASF6:
.string "long long unsigned int"
.LASF14:
.string "whichISR"
.LASF13:
.string "uint8_t"
.LASF1:
.string "unsigned char"
.LASF3:
.string "long int"
.LASF7:
.string "__vector_1"
.LASF8:
.string "__vector_2"
.LASF9:
.string "__vector_3"
.LASF0:
.string "signed char"
.LASF11:
.string "../scratch.c"
/* File "../scratch.c": code 44 = 0x002c ( 25), prologues 10, epilogues 9 */
SprinterSB
24.02.2006, 11:54
Ok, wenn du jetzt etwas mehr machst in foo (auf die Variable isr müsstest du pollen, also könnte man auch gleich auf die IRQ-Flags pollen und das ganze Interrupt-Zeug weglassen, was aber wiederum nicht geht...).
foo verändert z.B. das SREG -- es wird nicht wieder hergestellt!. Und wenn sich foo entscheidet, R0, R1, R18..R27, R30 oder R31 anzupacken, werden die auch nicht gesichert...
Im Endeffekt schaffst du nur mit Assembler, das wasserdicht zu bekommen, weil du von avr-gcc nicht erfahren kannst, welche Register er benutzt (oder doch?).
Du hast recht. Man könnte in foo() einmal den Prolog der "normalen" ISR (SREG und Register sichern) sowie den Epilog (SREG und register restaurieren) reinschreiben. Also als Makro den inline Assembler Code für Prolog und Epilog.
Für die Übergabeparameter müssen dann nochmal die entsprechenden Register in der ISR gesichert/restauriert werden.
Unterm Strich bleibt dann doch zumindest eine gehörige Portin inline Assembler übrig.
SprinterSB
24.02.2006, 12:47
Im Endeffekt wäre man also quasi bei einer reinen asm-Lösung...
Am besten gefällt mir bislang die Lösung von PickNick. Bleibt nur die Frage, wie man an den Ort der return-Adresse kommt.
Chertikov schreibt, er habe __builtin_return_address nicht implementiert, weil das eh niemand brauche *grrr*
Na ja
push
push
...
push
IN ZL, SPL
IN ZH, SPH
LDD R24, Z+ Offset // =lsb-byte Low(return addr)
(r24 >> 1) --> Vector-# // das geht jetz aber auf SREG
Offset hängt ab, was alles gepusht wird
Beim rechnen auf WORD /BYTE aufpassen, was man halt braucht.
SprinterSB
24.02.2006, 13:27
Schon klar, wie's in asm ginge.
An diesen Offset komm ich eben nicht dran von C aus :-(
lst-nachschauen und hart reincodieren schliess ich jetzt man aus, weil es keine Möglichkeit gibt, das nach einem Rebuild auf Korrektheit zu prüfen (ausser jedes Mal das asm zu kontrollieren).
Würd' ich, glaub ich, nicht machen, aber du könntest den stack aufwärts lesen, bis du eine return addresse aus dem Vector-bereich hast. Das machst du beim ersten Interrupt einmal und merkst dir das dann Offset.
naja, es bleibt eine Sauerei.
SprinterSB
24.02.2006, 14:38
1. In der VecTab stehen nur rjmp-Befehle. Auf dem Stack findet sich also keine return-Adresse, die in die VecTab zeigt.
2. Wer sagt, daß nicht eine Reg-Kombination gepusht wurde, die so interpretierbar wäre. Insbesondere wird R1 gepusht, das fast immer 0 sein dürfte, was die Wahrscheinlickeit einer Fehlinterpretation stark erhöht. Jedenfalls würd ich so was nicht als funktionierenden Code ansehen, auch dann, wenn er in einem speziellen Fall das tut, was man möchte.
Ja, stimmt, ist wahr.
Also, ich würde dann eher versuchen, die ganze Interrrupt-geschichte dem C aus der Hand zu nehmen und dann aber konsequent als assembler module extra zu machen, also eigentlich nur pro- und epilog, natürlich.
Dieses Modul ruft dann foo(vec-nr) als normale function auf.
Warum dann nicht einfach mit -mcall-prologues compilieren und die ISRs naked attributieren? Das hält foo() kurz und sorgt auch dafür, dass die Register sauber bleiben (und man bleibt weitestgehend bei C).
foo() und die ISR sollten dann in einer eigenen Datei sein, da sonst alles mit -call-prologues compiliert wird was boshafterweise auch zu größeren Code führen kann, da der gcc-avr bei der Parameterübergabe in Registern schon sehr gut optimiert.
SprinterSB
24.02.2006, 22:29
Leider geht -mcall-prologues nur für "mormale" Funktionen, auf ISRs hat das keinen Effekt.
Nebenbei: ich hab eben das Projekt mal mit dieser Option generiert, es schluckt dann 40 Bytes Flash mehr! (1bc9 anstatt 1b74, ohne .data und Konstanten).
Das liegt daran, daß nur eine Funktion (ausser den ISRs) überhaupt einen nennenswert fetten Prolog hat (von insgesamt 84 Funktionen ohne ISRs). Ich werd die Option aber mal im Auge behalten. GCC scheint zu wissen, ob sich für eine Funktion call-prolog rechnet. Funktionen ohne bzw. mit kleinem Prolog packt er nicht an.
Der Code, den avr-gcc macht, ist eben ziemlich dicht. Da noch was einsparen ist nicht ganz einfach...
Mit den ISRs hab ich's so gelöst:
In einer naked ISR lösch ich ihr enable-Flag (ist bitadressierbar, geht also, ohne ein Register zu brauchen oder SREG zu ändern). Danach spring ich mit rjmp zu der eigentlichen signal-Funktion, die aber nicht in der VecTab steht (weil sie nicht __vector_n heisst).
Diese Funktion hat nen normalen ISR-Rahmen. In ihr schau ich mir die enable-Bits an. That's it!
Also ähnlich wie die Lösung mit dem T-Flag oben. Ich weiß, welche IRQs aktiv sind und welche nicht, bzw könnte mir das auch merken. Die IRQs arbeiten als "one shot", und werden sowieso in ihrer eigenen ISR deaktiviert.
Das alles ist C, bis auf eine Zeile asm für den Sprung, den ich als inline machen muss :-) Dabei ist's wasserdicht und nicht mal hässlich -- zumindest nicht hässlicher als C für µC ohnehin schon ist...
Funktioniert auch prima, und der Code sieht super aus. Geändert hat sich nix, ausser dem Code-Schrumpf :-)
Nochmal vielen Dank für eure Mühe und euren Gehirnschmalz!
Bei avrfreaks gab's eine ähnliche Diskussion und Jörg Wunsch hat noch eine Alternative gefunden:
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=36278
SprinterSB
10.03.2006, 10:51
Das Problem wird dort leider nicht besprochen.
Zwar werden mehrere IRQs auf die gleiche ISR abgebildet (wie auch immer), aber dort ist es nicht möglich zu unterscheiden, welche IRQ getriggert wurde.
Stimmt, hatte ich übersehen.
SprinterSB
10.03.2006, 11:10
Ist ja net schlimm, das Problem ist ja gelöst inzwischen. Trotzdem nochmal danke. :-)
Powered by vBulletin® Version 4.2.5 Copyright ©2024 Adduco Digital e.K. und vBulletin Solutions, Inc. Alle Rechte vorbehalten.