Eigene Interruptvektortabelle mit WinAVR

Gast #1216515
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
Persönliche Seite #1216555
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
Moderator Persönliche Seite #1216565
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.
Gast #1216573
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.
Gast #1216651
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.
Gast #1216658
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
#1216745
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
Gast #1216771
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.
Moderator Persönliche Seite #1216904
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.
#1216920
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
Gast #1216950
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?
#1216992
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
Gast #1217100
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
Gast #1217126
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
Moderator Persönliche Seite #1217131
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.
Gast #1217279
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)
#1217292
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
Gast #1217312
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.
Gast #1217327
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

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren