Hallo zusammen,
als ich mein USB-Projekt für meinen AT90USB1287 in eine statische
Bibliothek für den USB-Code und ein Anwendungsprojekt unterteilt habe,
bin ich auf ein merkwürdiges Problem mit den Interrupts beim Linken
gestoßen. Das Problem habe ich mit dem nachfolgenden Beispiel
nachstellen können. Ich habe die folgenden Dateien:
Lib.h
1
#ifndef LIB_H_
2
#define LIB_H_
3
4
#include<avr/io.h>
5
#include<avr/interrupt.h>
6
7
externvolatileuint8_ti;
8
9
uint8_tGet(void);
10
11
#endif /* LIB_H_ */
Lib.c
1
#include"Lib.h"
2
3
volatileuint8_ti=0x00;
4
5
uint8_tGet(void)
6
{
7
returni;
8
}
LibInt.c
1
#include"Lib.h"
2
3
volatileuint8_ti;
4
5
ISR(USB_GEN_vect)
6
{
7
i++;
8
}
main.c
1
#include"Lib.h"
2
3
volatileuint8_tA=0;
4
intmain(void)
5
{
6
while(1)
7
{
8
A=Get();
9
A++;
10
}
11
}
Die Daten Lib.c und LibInt.c werden zur libLib kompiliert und dann mit
main.c gelinkt. Wenn ich mir nun das Mapping anschaue, dann sehe ich das
folgende:
1
0x000000b8 __bad_interrupt
2
0x000000b8 __vector_14
3
0x000000b8 __vector_10
4
0x000000b8 __vector_16
5
0x000000b8 __vector_18
6
0x000000b8 __vector_20
7
0x000000ba . = ALIGN (0x2)
Anscheinend würde der Interruptvektor 10 nicht an die korrekte Adresse
gelinkt, was exakt dem Fehlerbild aus meinem USB-Projekt entspricht.
Daher wird der Interrupt nicht angesprungen.
Ich verwende
AtmelStudio 7.0.1222
gcc version 5.4.0 (AVR_8_bit_GNU_Toolchain_3.6.1_1750)
Hat jemand eine Idee, wieso die ISR nicht korrekt gelinkt wird?
Danke und Gruß
Wo liest Du denn etwas von USB ein? Wo hast Du die entsprechenden
Register gesetzt?
Keine Aktion die zu einem Interrupt führt - kein entsprechendes Register
gesetzt - kein Vektor zu aktivieren.
Hugo H. schrieb:> Wo liest Du denn etwas von USB ein?>> Keine Aktion die zu einem Interrupt führt - kein Interrupt erforderlich> - kein Vektor zu aktivieren.
Es geht mir in dem gezeigten Beispiel auch nur um das fehlende Linken.
Dem Linker dürfte ja egal sein, dass der Interrupt in der Hardware nicht
aktiviert ist und somit nie angesprungen wird.
Im eigentlichen Projekt ist der Interrupt aktiviert. Dort funktioniert
der Code auch ordnungsgemäß, bis ich den USB-Code in eine separate
statische Bibliothek schiebe und diese Bibliothek dann mit dem
Application-Code linken möchte. Dann tritt wieder der selbe Fehler auf
und Vektor 10 wird nicht mit der korrekten Adresse versehen. Da das
USB-Projekt aber zu groß und umfänglich für eine Frage ist, habe ich
versucht das Problem mit dem oben gezeigten Projekt nachzustellen.
Es geht also nicht um die dynamische Funktion des Codes, sondern in
erster Linie darum, was der Linker aus dem gegebenen Code macht. Und da
scheint er die ISR für den Interruptvektor 10 einfach zu ignorieren und
ich weiß nicht wieso.
Den Sinn dieses Codefragments verstehe ich nicht. Du deklarierst die
externe Variable i (in Lib.h) und definierst dann gleich nochmals ein
weiteres i.
Ich würde diese sinnlose Zeile entfernen -- abgesehen davon, dass es
keine wirklich gute Idee ist, eine Variable mit dem aussagekräftigen
Namen 'i' global zu definieren...
Grüßle
Volker
>> Den Sinn dieses Codefragments verstehe ich nicht. Du deklarierst die> externe Variable i (in Lib.h) und definierst dann gleich nochmals ein> weiteres i.> Ich würde diese sinnlose Zeile entfernen -- abgesehen davon, dass es> keine wirklich gute Idee ist, eine Variable mit dem aussagekräftigen> Namen 'i' global zu definieren...>> Grüßle> Volker
Hallo Volker,
danke für die Antwort. Anscheinend lag da ein Missverständnis
meinerseits im Bezug auf das Schlüsselwort "extern" vor. Ich dachte das
ich die Variablen noch einmal in allen benötigten Source-Files
deklarieren müsste.
Ich habe das überflüssige i mal entfernt, aber das Problem bleibt
(leider) weiter bestehen.
Der Name ist (in diesem kurzen Beispiel) einfach willkürlich gewählt um
das Problem zu erzeugen. In den "richtigen" Codes haben die Variablen
natürlich aussagekräftige Namen :) - das Beispiel hier ist eher ein
Anschauungsbeispiel ohne sinnvolle Funktion.
In dem Ursprungsprojekt habe ich das Problem dadurch behoben, dass ich
ein paar Funktionen in das Source-File mit der ISR kopiert habe. Nun
wird alles korrekt gelinkt. Aber mich würde dennoch echt brennend
interessieren, wieso der Linker die ISR in dem konstruierten Beispiel
irgnoriert und wie man dem das abgewöhnen kann.
Was passiert, wenn du testweise mal den Namen der Interruptfunktion
in deiner Library aenderst?
Beschwert der Linker sich dann, oder holt er einfach die falsche
Adresse fuer den Interrupt woanders her?
Vermutlich zeigt die falsche Adresse auf eine weak Definition.
Hallo zusammen,
@Hugo:
Ich habe das Projekt mal angehängt.
@Abyssaler Einspeiser:
Was meinst du mit Funktionsnamen ändern? Den ISR-Block kann ich nicht
umbenennen. Eine Warnung oder einen Fehler erhalte ich nicht (siehe
Output).
Hallo Daniel,
ich wage zu bezweiflen, dass es eine gute Idee ist, Interrupthandler in
einer echten Bibliothek abzulegen (und nicht nur in einem Modul, was
dummerweise in der Arduino-Welt als "lib" bezeichnet wird).
Ich befürchte, dass der Linker nicht erkennen kann, ob Handler und
Vektor benötigt werden.
Grüßle
Volker
Der Linker holt nur die Objekte aus Libraries, die ansonsten undefiniert
bleiben würden. Wenn's für die ISRs Weak-Definitionen gibt, hat er dafür
keinen Grund.
Aus:
ISR(USB_GEN_vect)
Mach:
ISR(Ein fuer den Controller gueltiger Vektorname)
Die Vektoradressen holt er sich scheinbar ueber:
C:/Program Files
(x86)/Atmel/Studio/7.0/Packs/Atmel/ATmega_DFP/1.3.300/gcc/dev/at90usb128
7/avr51/crtat90usb1287.o
Und deine ISR(USB_GEN_vect)
taucht im Mapfile gar nicht auf.
Wenn es nicht anders geht, wirst du den Teil anpassen muessen der
die "crtat90usb1287.o" erzeugt.
Wie oben schon beschrieben sind vermutlich die "Weak" Vorbelegungen der
Interrupthandler das Problem. die Datei "LibInt.c" enthält außer der ISR
keine weiteren genutzen Symbole. Packt man solch ein Objektfile in eine
Library (".a") und liegen für die gesuchten Symbole außerdem "Weak"
Definitionen vor, dann wird der Linker das Objektfile aus der Library
gar nicht in Betracht ziehen.
Aber eigentlich hast Du es fast richtig gemacht, denn die Variable "i"
ist ja ein solches Symbol, Du hast es leider nur doppelt definiert. Du
musst das Symbol in der LibInt.c definieren, so dass der Linker
gezwungen wird, das "i" von der LibInt.o aus der libLib.a zu holen und
damit kommt dann automatisch den ganzen Inhalt der "LibInt.c", auch die
Vectordefinition.
Probiere das erstmal:
In der .h Datei "external volatile uint8_t i;" ist korrekt, in der
LibInt.c dann natürlich auch "volatile uint8_t i;" und in der "Lib.c"
die definition "volatile uint8_t i;" komplett entfernen. Dadurch nutzt
der Linker dann die Definition aus LibInt.c und bekommt dann hoffentlich
auch die ISR mit.
Es kann aber sein, dass es trotzdem nicht funktioniert. Denn der Linker
löst die Symbole in einer bestimmten Reihenfolge auf. Und zwar so, das
normalerweise der Code wo das Symbol benutzt wird (Die crt*.o) vor
demjenigen der das Symbol liefert "libLib.a" ausgewertet werden muss.
Gerade bei Weak Symbolen gibt das öfter mal Probleme. (Merke: Weak
meiden wo es geht!)
Dazu kommt die Gemeinheit, dass der Linker sich bei ".a" Dateien auch
etwas anders verhält als bei ".o" Dateien, selbst wenn die ".a" Dateien
genau die ".o" enthalten.
Edit: Rechtschreibung.
Andreas M. schrieb:>> Probiere das erstmal:>> In der .h Datei "external volatile uint8_t i;" ist korrekt, in der> LibInt.c dann natürlich auch "volatile uint8_t i;" und in der "Lib.c"> die definition "volatile uint8_t i;" komplett entfernen. Dadurch nutzt> der Linker dann die Definition aus LibInt.c und bekommt dann hoffentlich> auch die ISR mit.> Edit: Rechtschreibung.
Diese vorgehensweise löst das Problem:
Ist (meiner Meinung nach) zwar keine so schöne Lösung, da ich persönlich
die Source Lib.c als "oberstes Modul" betrachte und da gerne die
Variablendeklaration vornehmen wollen würde (in diesem konstruierten
Problem. Im eigentlichen Projekt ist das Problem ohnehin behoben worden,
da ich einige Funktionen mit in den Interruptcode geschoben habe, sodass
dort auch etwas mehr als die ISR liegt.
Dennoch haben die Erklärungen hier geholfen das Problem zu
identifizieren. Das die Interrupthandler mit "weak" deklariert werden
wusste ich bisher noch nicht. Das ist eine sehr schöne Fehlerquelle, die
sich gerade hier gezeigt hat.
Danke für die Hilfen und die Denkanstöße :)
Funktioniert denn folgendes?
crt*.o explizit angeben mit -nostartfiles und nach dem crt*.o
-Wl,-u,__vector_10.
Aber selbst wenn das geht, ist es hässlich: Man muss die IRQ-Nummer
kennen, und wenn die ISR mal rausfliegt weil nicht mehr benötigt gibt's
ein Linkerfehler.
Evtl geht aus das: libLib.a implementiert die Routine, aber nicht als
ISR. In main.c wird dann die ISR implementiert und ruft die
Implementierung von libLib.c auf.
Falls main.c die ISR "normal" implementiert, gibt das wegen des
Funktionsaufrufs allerdings Overhead, den man in der ISR nicht unbedingt
haben will. In diesem Falle wäre die Routine in libLib.c eine normale
Funktion. Oder man implementiert die Routine als ISR mit komischem
Name, und macht dann in main nen hässlichen Hack wie
1
#include<avr/io.h>
2
#include<avr/interrupt.h>
3
4
// In libLib.h
5
externvoid__vector_blah(void);
6
7
// In main.c
8
ISR(USB_GEN_vect,ISR_NAKED)
9
{
10
__asmvolatile("%~jmp %x0"::"s"(__vector_blah));
11
}
12
13
// In libLib.c
14
ISR(__vector_blah)
15
{
16
// ...
17
}
womit sich der Overhead auf ein XJMP reduziert.
Aber dann muss main.c "Wissen" über die USB-IRQs haben.
Johann L. schrieb:> Funktioniert denn folgendes?>> crt*.o explizit angeben mit -nostartfiles und nach dem crt*.o> -Wl,-u,__vector_10.>> Aber selbst wenn das geht, ist es hässlich: Man muss die IRQ-Nummer> kennen, und wenn die ISR mal rausfliegt weil nicht mehr benötigt gibt's> ein Linkerfehler.
Das wäre mir etwas zu "hacky". Bei so einer Lösung bin ich mir nicht
einmal sicher, dass ich das nicht irgendwann mal vergesse und mich dann
blöde suche.
Johann L. schrieb:> Evtl geht aus das: libLib.a implementiert die Routine, aber nicht als> ISR. In main.c wird dann die ISR implementiert und ruft die> Implementierung von libLib.c auf.
Das würde ich dann schon eher machen, wobei mir das auch nicht gefällt,
da ich dann in "main.c" die ISR habe und die möchte ich eigentlich raus
haben. Das Ziel soll sein allen notwendigen Code in die Lib zu packen
ohne irgendwo in der Applikation dann noch notwendigen Code für die Lib
erzeugen zu müssen.
Johann L. schrieb:> Falls main.c die ISR "normal" implementiert, gibt das wegen des> Funktionsaufrufs allerdings Overhead, den man in der ISR nicht unbedingt> haben will. In diesem Falle wäre die Routine in libLib.c eine normale> Funktion. Oder man implementiert die Routine als ISR mit komischem> Name, und macht dann in main nen hässlichen Hack wie
Interessanterweise frisst der Linker nicht einmal dieses Konstruct (wohl
wieder der selbe Mist mit dem weak Attribut):
Lib.h
1
#ifndef LIB_H_
2
#define LIB_H_
3
4
#include<avr/io.h>
5
#include<avr/interrupt.h>
6
7
externvolatileuint8_ti;
8
9
uint8_tGet(void);
10
11
externvoidMyISR(void);
12
13
#endif /* LIB_H_ */
LibInt.c
1
#include"Lib.h"
2
3
voidMyISR(void)
4
{
5
i++;
6
}
7
8
ISR(USB_GEN_vect)
9
{
10
MyISR();
11
}
1
0x000000b8 __bad_interrupt
2
0x000000b8 __vector_14
3
0x000000b8 __vector_10
4
0x000000b8 __vector_16
Es bleibt wohl dabei, dass ich einfach dann weiterhin zusätzliche
Funktionen mit den Interruptcode packen muss. An für sich auch nicht
ganz so tragisch, da ich dann dort einfach eine Funktion zum An- und
Ausschalten der Interrupts, bzw. die Überprüfung der Interruptquelle
einbaue. Dadurch habe ich etwas Code in der Datei und es passt
funktional auch noch ganz gut zusammen.
Die in der Doku zur Lösung des Problems vorgschlagene Vorgehensweise
funktioniert. Klingt vielleicht komisch, ist aber so.
https://www.nongnu.org/avr-libc/user-manual/library.html
Platziert man in libInt.c eine weitere Funktion, die aus main (oder
sonstwo aus dem Hauptprogramm) aufgerufen wird, findet der linker auch
die ISR.
Sinnvollerweise ist das z.B die eh benötigte Initialisierung des
Interrupts, oder etwas ähnliches, was da logischerweise eh dazugehört.
Oliver
Daniel K. schrieb:> Interessanterweise frisst der Linker nicht einmal dieses Konstruct (wohl> wieder der selbe Mist mit dem weak Attribut):>> LibInt.c>> void MyISR(void)> { [...] }>> ISR(USB_GEN_vect)
In meinem Beispiel ist diese ISR in main. Dein Code ist ja genau wie
das Original: USB_GEN_vect in lib.a. In den Kommentaren hab ich
angedeutet, was wo hin muss.
> {> MyISR();> }
Auf jeden Fall: Keine ISR mit bekanntem Namen in lib.a implementieren,
was impliziert dass diese nach main bzw. irgendein modul.o muss. Je
nach Overhead wird der ISR-Code in lib.a als ISR(__vector*) oder normale
Funktion implementiert. Wie gesagt darf __vector* kein bekannter
ISR-Name sein, also nicht USB_GEN_vect und auch nicht __vector_10.
Wie macht das eigentlich Arduino? Soweit ich weiß wird da der gesamnte
Code erst in eine lib.a gepackt. Oder hab ich das was falsch
aufgeschnappt?
Oliver S. schrieb:> Die in der Doku zur Lösung des Problems vorgschlagene Vorgehensweise> funktioniert. Klingt vielleicht komisch, ist aber so.>> https://www.nongnu.org/avr-libc/user-manual/library.html>> Platziert man in libInt.c eine weitere Funktion, die aus main (oder> sonstwo aus dem Hauptprogramm) aufgerufen wird, findet der linker auch> die ISR.
Auch mit -ffunction-sections?