Forum: Compiler & IDEs Eigene Interruptvektortabelle mit WinAVR


von Jochen W (Gast)


Lesenswert?

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

von Simon K. (simon) Benutzerseite


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von holger (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

holger wrote:

> Das ist doch alles Unsinn.

Hast du dir mein Posting auch gelesen?

von holger (Gast)


Lesenswert?

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

von Jochen W (Gast)


Lesenswert?

> 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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

SPI mit Interrupt?  Das macht man doch nur, wenn man viel Zeit hat.

von Jochen W (Gast)


Lesenswert?

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

von Falk B. (falk)


Lesenswert?

@  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

von Jochen W (Gast)


Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Falk B. (falk)


Lesenswert?

@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

von Jochen W (Gast)


Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Tu dir und uns einen Gefallen: schnapp dir den Quellcode der gcrt1.S
aus der avr-libc, und modifiziere ihn, statt von 0 anzufangen.

von Peter (Gast)


Lesenswert?

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?

von Peter D. (peda)


Lesenswert?

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

von Jochen W (Gast)


Lesenswert?

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?

von Jochen W (Gast)


Lesenswert?

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

von Jochen W (Gast)


Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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

von Jochen W (Gast)


Lesenswert?

> 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)

von Falk B. (falk)


Lesenswert?

@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

von Urs (Gast)


Lesenswert?

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

von Jochen W (Gast)


Lesenswert?

>>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
Noch kein Account? Hier anmelden.