www.mikrocontroller.net

Forum: Compiler & IDEs Eigene Interruptvektortabelle mit WinAVR


Autor: Jochen W (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Simon K. (simon) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: holger (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
holger wrote:

> Das ist doch alles Unsinn.

Hast du dir mein Posting auch gelesen?

Autor: holger (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Jochen W (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
SPI mit Interrupt?  Das macht man doch nur, wenn man viel Zeit hat.

Autor: Jochen W (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Jochen W (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Jochen W (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Peter (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Jochen W (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Jochen W (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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
out     SPDR, r16
geht nicht. hat jemand ne idee wie man SPDR anspricht?
#include <avr/io.h>
#include <avr/interrupt.h>

  .section .vectors,"ax",@progbits
  .global  __vectors
  .func  __vectors
__vectors:

.org 0x0000
  jmp    __ctors_end        ; reset handler

.org 0x000c
  out     SPDR, r16
  rjmp  IntPinChange0          ; pin change 0 handler

.org 0x0044
  out     SPDR, r23
  rjmp    IntSPI


IntPinChange0:
  nop
  
IntSPI:
  nop  
  .endfunc

  .section .init9,"ax",@progbits
  jmp    main

Autor: Jochen W (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
#include <avr/io.h>

  .section .vectors,"ax",@progbits
  .global  __vectors
  .func  __vectors
__vectors:

.org 0x0000                  ; reset handler
  jmp    __ctors_end;__init

.org 0x000c                  ; pin change 0 handler
  out     _SFR_IO_ADDR(SPDR), r16
  rjmp  IntPinChange0

.org 0x0044                  ; SPI handler
  out     _SFR_IO_ADDR(SPDR), r23
  rjmp    IntSPI

.org 0x0048                  ; UART RX handler
  jmp    __vector_18
  


IntPinChange0:
  nop
  reti
IntSPI:
  nop  
  reti
  .endfunc


  .section .init9,"ax",@progbits
  jmp    main

3. List-File (*.lss) erzeugen lassen. Hier sieht man folgendes:
00000000 <__vectors>:
       0:  0c 94 2a 00   jmp  0x54  ; 0x54 <__ctors_end>
  ...
       c:  0e bd         out  0x2e, r16  ; 46
       e:  1e c0         rjmp  .+60       ; 0x4c <IntPinChange0>
  ...
      44:  7e bd         out  0x2e, r23  ; 46
      46:  04 c0         rjmp  .+8        ; 0x50 <IntSPI>
      48:  0c 94 74 01   jmp  0x2e8  ; 0x2e8 <__vector_18>

0000004c <IntPinChange0>:
      4c:  00 00         nop
      4e:  18 95         reti

00000050 <IntSPI>:
      50:  00 00         nop
      52:  18 95         reti

00000054 <__ctors_end>:
...

4. Wir sehen, dass pro Interruptvektor ein out und ein rjmp Platz haben. 
Geil oder?

Gruß,
Jochen

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Jochen W (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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)

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Urs (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Jochen W (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.