Hallo, Hat jemand eine Idee, wie man eine eigene Interruptvektortabelle in einem C-Programm implementiert? Mit der Linkeroption -nostartfiles habe ich die automatisch generierte abgeschaltet und in einem asm file (startup.s) habe ich folgendes: .text .global __vectors __vectors: jmp... usw. Problem nur, dass das hinterher irgendwo reingelikt wird und nicht an Adresse 0x0000. Man könnte natürlich versuchen, dem Linker startup.o als erstes zu geben, aber es muß doch einen offiziellen Weg dafür geben. Grund für diese Verrenkung ist, dass ich vor dem jmp in der Interruptvektortabelle noch einen Befehl einfügen will, der so schnell wie möglich ausgeführt werden soll und nicht erst nach dem jmp. Sollte also einer eine Idee haben, würde ich mich über eine Antwort freuen. Viele Grüße, Jochen
Welchen Sinn soll dein Vorhaben haben? EDIT: Grad gesehen. Hm. Das wird nicht einfach. Soweit ich weiß musst du dann deine eigene Tabelle in eine eigene Linkersection packen und die Adresse auf 0 legen. Problem ist aber, dass du nicht nur die Interruptvektortabelle rausgehauen hast, sondern auch noch den restlichen Startup Code.
Jochen W wrote: > Hallo, > > Hat jemand eine Idee, wie man eine eigene Interruptvektortabelle in > einem C-Programm implementiert? Mit der Linkeroption -nostartfiles habe > ich die automatisch generierte abgeschaltet und in einem asm file > (startup.s) habe ich folgendes: > .text > .global __vectors > __vectors: > jmp... > usw. > > Problem nur, dass das hinterher irgendwo reingelikt wird und nicht an > Adresse 0x0000. Man könnte natürlich versuchen, dem Linker startup.o als > erstes zu geben, aber es muß doch einen offiziellen Weg dafür geben. > Grund für diese Verrenkung ist, dass ich vor dem jmp in der > Interruptvektortabelle noch einen Befehl einfügen will, der so schnell > wie möglich ausgeführt werden soll und nicht erst nach dem jmp. > > Sollte also einer eine Idee haben, würde ich mich über eine Antwort > freuen. > Viele Grüße, > Jochen Was für ein Linkerskript verwendest du denn? Im Standard stehen die Vektoren in .vectors. Das ist zwar ein Teil von .text, aber eben ein definierter ;-) Johann
Jochen W wrote: > Grund für diese Verrenkung ist, dass ich vor dem jmp in der > Interruptvektortabelle noch einen Befehl einfügen will, der so schnell > wie möglich ausgeführt werden soll und nicht erst nach dem jmp. Dann kannst du aber den Vektor danach nicht nutzen, oder aber (bei den AVRs >= 16 KiB) du springst aus der eigentlichen Vektortabelle mit rjmp auf eine weitere Tabelle. Ob das wirklich sinnvoll ist? Wenn schon, dann solltest du dir aus der Bibliothek das gcrt1.S in dein Projekt kopieren, passend modifizieren, und dieses dann explizit als Startup-Script linken.
Das ist doch alles Unsinn. Die Interruptvektoren sind beim AVR fest vorgegeben. Wenn überhaupt könnte man z.B. bei Int0 irgendwas machen und wenn Int1 nicht benutzt wird den zum Sprung zur Int0 Routine verwenden.
holger wrote:
> Das ist doch alles Unsinn.
Hast du dir mein Posting auch gelesen?
>> Das ist doch alles Unsinn. >Hast du dir mein Posting auch gelesen? Nein, ich brauchte ein wenig Zeit zum tippen. Das mit dem Unsinn ist nicht auf deine Antwort bezogen. Ein Reload vor meiner Antwort wäre wohl angebracht gewesen.
> Problem ist aber, dass du nicht nur die Interruptvektortabelle > rausgehauen hast, sondern auch noch den restlichen Startup Code. Das wär dann mein nächstes Problem. Der restliche Startupcode bleibt komischerweise sogar drin, nur der jump zu main fehlt dann. Nochmal zur Präzisierung: ich verwende einen ATmega168, AVR-Studio mit WinAVR (der erzeugt das make-File für mich) und bringen tut die eigene Interruptvektortabelle auch was, es wurde in einem pur-asm-Programm schon implementiert. Man kann wertvolle Nanosekunden vom Interrupt bis zum Nachladen der SPI-Schnittstelle gewinnen ;-) (wär sicher nicht nötig wenn Atmel die Hardware nicht so verbockt hätte, beim PIC geht es z.B.) Gruß, Jochen
SPI mit Interrupt? Das macht man doch nur, wenn man viel Zeit hat.
> SPI mit Interrupt? Das macht man doch nur, wenn man viel Zeit hat.
im Slave Mode. Woher willst Du wissen wann es los geht? Und man hat ja
noch anderes zu tun als zu pollen.
@ Jochen W (Gast) >im Slave Mode. Woher willst Du wissen wann es los geht? Und man hat ja >noch anderes zu tun als zu pollen. Wenn dein Programm darauf aufbaut, dass du ZWEI popelige Takte sparen musst, hast du sowieso ein Konzeptproblem. Ausserdem ist das RX Register doppelt gepuffert, da hat man schon ein paar Takte Zeit, die Daten abzuholen. MFG Falk
> Wenn dein Programm darauf aufbaut, dass du ZWEI popelige Takte sparen > musst, hast du sowieso ein Konzeptproblem. ein Konzeptproblem gibt es, aber das ist in dem Protokoll begründet, das bedient werden muss und ist somit nicht änderbar. von außen bekomme ich eine clock und muß mit jedem takt daten liefern. und da bei atmel leider nix doppelt gepuffert ist habe ich nur einen halben takt = 500ns bei 1MHz clock zeit zu antworten. leider schafft man das nicht ganz, aber 800kHz gehen.
Die entscheidende Reaktionszeit (und die bekommt man wirklich nur per "gentlemen agreement" mit dem Master beherrscht) ist doch die auf den /SS vom Master. Danach kann man die SPI pollen, wenn es schnell gehen muss. Dann hat man noch eine kritische Zeit, aber die hängt nicht an irgendwelchen Interruptlatenzen: das erste Byte ist typisch ja ein Kommando, auf das man reagieren muss, und in Abhängigkeit davon muss man das SPDR für die zweite Transaktion laden. Wenn der Master nach den ersten 8 Takten gleich ,,am Stück'' weitertaktet, hat man in der Software keine Chance, die Antwort noch bereit zu stellen. Alles in allem ist meiner Meinung nach ein Controller ein relativ schlecht handhabbarer (bzw. nur mit Einschränkungen, besagtes "gentlemen agreement", dass der Master längere Pausen macht als ihm durch seine Hardware vorgegeben wäre) SPI-Slave. SPI ist ein Schieberegister, und das lässt sich nun einmal am besten in Hardware realisieren. Dort stellt auch das Bereitstellen der Antwort kein großes Problem dar. Wenn man einen gemütlicheren Slave haben will, muss man TWI nehmen.
@Jochen W (Gast) >von außen bekomme ich eine clock AHHHHH! Schon wieder! Kauf dich auch mal Duden! > und muß mit jedem takt daten liefern. und da bei atmel leider > nix doppelt gepuffert ist habe ich nur einen halben takt = 500ns bei > 1MHz clock zeit zu antworten. leider schafft man das nicht ganz, aber >800kHz gehen. Das raustakten macht das SPI-Modul selber. Ebenso das USI-Modul. Man muss nur die Daten vorher rechtzeitig ins Register laden. MFg Falk
Wie definiere ich die Sektion .vectors innerhalb von .text? wenn ich .text .vectors schreibe bekomme ich ../setup.s:10: Error: bad or irreducible absolute expression
Tu dir und uns einen Gefallen: schnapp dir den Quellcode der gcrt1.S aus der avr-libc, und modifiziere ihn, statt von 0 anzufangen.
also wenn es schon um jeden Takt geht, ist es da nicht sinnvoller gleich im ASM zu schreiben. wie soll denn die Interruptvektortabelle denn zum schluss aussehen? rjmp RESET rjmp _inter_1 mov R2, PORTD <- wichitger befehlt rjmp _inter_2 rjmp _inter_3 rjmp _inter_4 an welcher stelle willst du jetzt denn dein RETI unterbringen?
Falk Brunner wrote: > Man > muss nur die Daten vorher rechtzeitig ins Register laden. Genau das ist doch das Problem, von dem er die ganze Zeit redet. Vielleicht wäre folgender Ansatz möglich: Es steht das 1.Byte bereits im SPI-Register. Der /SS-Pin wird mit auf einen externen Interrupt gelegt. Der Master zieht nun /SS auf low und taktet das 1.Byte raus. Nun hat der Slave 8 Takte Zeit, in den externen Interrupt zu springen und pollt dann die ganze Zeit auf das Ready-Flag, um das nächste Byte zu senden. D.h. er bleibt solange in dem externen Interrupthandler, bis /SS wieder auf high geht. Er kann ja eh nichts anderes machen, wärend der Master die Bytes abfordert. Es können auch keinerlei anderen Interrupts verwendet werden, da ja der AVR keine Prioritäten hat. Die 2 Zyklen Ersparnis halte ich nicht für relevant. Der Interruptaufruf dauert ja schon 4 Takte und wenn er gerade zu einem RET kommt, sinds weitere 4 Takte Verzögerung. Man muß also schon mit mindestens 8 Zyklen bis zum 1. Befehl rechnen. Peter
Ah, jetzt habe ich gcrt1.S gefunden. avr-libc ist ja unabhängig von WinAVR. Mit der Vektortabelle fängt an zu funktionieren, nur clr _zero_reg_ geht jetzt nicht. Was muß man includen damit _zero_reg_ bekannt ist?
Interessant ist, dass mit der linkeroption -nostartfiles die initialisierungen drin bleiben. Man muß also nur die Vektortabelle und den Sprung zu Main neu implementieren. So sieht es bisher aus. Nur
1 | out SPDR, r16 |
geht nicht. hat jemand ne idee wie man SPDR anspricht?
1 | #include <avr/io.h> |
2 | #include <avr/interrupt.h> |
3 | |
4 | .section .vectors,"ax",@progbits |
5 | .global __vectors |
6 | .func __vectors |
7 | __vectors: |
8 | |
9 | .org 0x0000 |
10 | jmp __ctors_end ; reset handler |
11 | |
12 | .org 0x000c |
13 | out SPDR, r16 |
14 | rjmp IntPinChange0 ; pin change 0 handler |
15 | |
16 | .org 0x0044 |
17 | out SPDR, r23 |
18 | rjmp IntSPI |
19 | |
20 | |
21 | IntPinChange0: |
22 | nop |
23 | |
24 | IntSPI: |
25 | nop |
26 | .endfunc |
27 | |
28 | .section .init9,"ax",@progbits |
29 | jmp main |
Vielen Dank für die hilfreichen Hinwise, die zur Lösung geführt haben. Ich will jetzt hier nochmal für alle, die auf das gleiche Problem stoßen, die Lösung dokumentieren. 1. Linker-Option -nostartfiles setzen 2. Asm-Datei (z.B. startup.s) anlegen, die so aussieht:
1 | #include <avr/io.h> |
2 | |
3 | .section .vectors,"ax",@progbits |
4 | .global __vectors |
5 | .func __vectors |
6 | __vectors: |
7 | |
8 | .org 0x0000 ; reset handler |
9 | jmp __ctors_end;__init |
10 | |
11 | .org 0x000c ; pin change 0 handler |
12 | out _SFR_IO_ADDR(SPDR), r16 |
13 | rjmp IntPinChange0 |
14 | |
15 | .org 0x0044 ; SPI handler |
16 | out _SFR_IO_ADDR(SPDR), r23 |
17 | rjmp IntSPI |
18 | |
19 | .org 0x0048 ; UART RX handler |
20 | jmp __vector_18 |
21 | |
22 | |
23 | |
24 | IntPinChange0: |
25 | nop |
26 | reti |
27 | IntSPI: |
28 | nop |
29 | reti |
30 | .endfunc |
31 | |
32 | |
33 | .section .init9,"ax",@progbits |
34 | jmp main |
3. List-File (*.lss) erzeugen lassen. Hier sieht man folgendes:
1 | 00000000 <__vectors>: |
2 | 0: 0c 94 2a 00 jmp 0x54 ; 0x54 <__ctors_end> |
3 | ... |
4 | c: 0e bd out 0x2e, r16 ; 46 |
5 | e: 1e c0 rjmp .+60 ; 0x4c <IntPinChange0> |
6 | ... |
7 | 44: 7e bd out 0x2e, r23 ; 46 |
8 | 46: 04 c0 rjmp .+8 ; 0x50 <IntSPI> |
9 | 48: 0c 94 74 01 jmp 0x2e8 ; 0x2e8 <__vector_18> |
10 | |
11 | 0000004c <IntPinChange0>: |
12 | 4c: 00 00 nop |
13 | 4e: 18 95 reti |
14 | |
15 | 00000050 <IntSPI>: |
16 | 50: 00 00 nop |
17 | 52: 18 95 reti |
18 | |
19 | 00000054 <__ctors_end>: |
20 | ... |
4. Wir sehen, dass pro Interruptvektor ein out und ein rjmp Platz haben. Geil oder? Gruß, Jochen
Peter Dannegger wrote: > Vielleicht wäre folgender Ansatz möglich: > > Es steht das 1.Byte bereits im SPI-Register. Was anderes kannst du zum Beginn der SPI-Transaktionen sowieso nicht machen, das ist auch bei allen Hardwareimplementierungen, die ich so kenne, in der Form üblich. Entweder schieben sie im 1. Byte eine 0 raus, oder irgendeinen Standardwert (bspw. ein bestimmtes internes Register). Das könnte man durch Vorladen von SPDR ebenfalls. > Der /SS-Pin wird mit auf einen externen Interrupt gelegt. Das ist sogar schon ein Externinterrupt beim ATmega168 (INT2). Ich würde vermuten, dass man diesen auch unabhängig von der SPI-Funktionalität benutzen kann, aber das wäre natürlich mal experimentell zu prüfen. > Der Master zieht nun /SS auf low und taktet das 1.Byte raus. > Nun hat der Slave 8 Takte Zeit, in den externen Interrupt zu springen > und pollt dann die ganze Zeit auf das Ready-Flag, um das nächste Byte zu > senden. Das Problem, was ich halt sehe ist, wenn der Slave auf das 1. Byte vom Master reagieren muss, bevor er entscheiden kann, was er als Antwort sendet. Das geht nur mit besagtem "gentlemen agreement". > D.h. er bleibt solange in dem externen Interrupthandler, bis /SS wieder > auf high geht. Ja, davon wäre ich sowieso ausgegangen. > Die 2 Zyklen Ersparnis halte ich nicht für relevant. Ich auch nicht.
Jochen W wrote: > 4. Wir sehen, dass pro Interruptvektor ein out und ein rjmp Platz haben. > Geil oder? Dürfte aber in Deinem Fall egal sein, da Du ja eh keine anderen Interrupts verwenden kannst. Interrupts unter GCC sind typisch >50 Zyklen. Peter
> Dürfte aber in Deinem Fall egal sein, da Du ja eh keine anderen > Interrupts verwenden kannst. Klar, daher habe ich in mein Beispiel noch __vector_18 (UART RX) eingebaut, der in C implementiert ist. Das 1. Byte vorladen geht übrigens auch nicht weil die Clock im Ruhezustand high ist und dann "verzählt" sich die SPI-Schnittstelle und gibt nach dem letzten Bit noch eins zuviel aus. Daher kann ich SPDR erst setzen wenn die Clock auf low gegangen ist und das wird mit dem pin change interrupt gemacht. (SPI-Einstellung: CPOL = 0, CPHA = 1)
@Jochen W (Gast) >Das 1. Byte vorladen geht übrigens auch nicht weil die Clock im >Ruhezustand high ist Kein Problem. > und dann "verzählt" sich die SPI-Schnittstelle und >gibt nach dem letzten Bit noch eins zuviel aus. Nö. > Daher kann ich SPDR erst >setzen wenn die Clock auf low gegangen ist und das wird mit dem pin >change interrupt gemacht. Quark. > (SPI-Einstellung: CPOL = 0, CPHA = 1) Dort ist der Fehler. CPOL=1 wäre nicht falsch. ;-) MFG Falk
> 4. Wir sehen, dass pro Interruptvektor ein out und ein rjmp Platz haben. > Geil oder? Nicht bei AVRs mit kleinem Flash (z.B. die, die kein jmp haben), da sind die Einträge nämlich nur ein Befehl (=16bit) groß, das reicht nur für einen rjmp.
>>Das 1. Byte vorladen geht übrigens auch nicht weil die Clock im >>Ruhezustand high ist >Kein Problem. Doch, habs gemessen. >> und dann "verzählt" sich die SPI-Schnittstelle und >>gibt nach dem letzten Bit noch eins zuviel aus. >Nö. Doch, habs gemessen. >> (SPI-Einstellung: CPOL = 0, CPHA = 1) >Dort ist der Fehler. CPOL=1 wäre nicht falsch. ;-) So hat man immerhin einen halben Takt Zeit nachzuladen, mit CPOL = 1, CPHA = 0 kommt der Interrupt erst in dem Augenblick, in dem das 1. Bit des nächsten Bytes schon anfangen soll. Gruß, Jochen
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.