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.
>> 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.
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
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?
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