Forum: Mikrocontroller und Digitale Elektronik Fehlender Interrupt nach dem Linken


von Daniel K. (daniel_k80)


Lesenswert?

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
 extern volatile uint8_t i;
8
9
 uint8_t Get(void);
10
11
#endif /* LIB_H_ */

Lib.c
1
#include "Lib.h"
2
3
volatile uint8_t i = 0x00;
4
5
uint8_t Get(void)
6
{
7
  return i;
8
}

LibInt.c
1
#include "Lib.h"
2
3
volatile uint8_t i;
4
5
ISR(USB_GEN_vect)
6
{
7
  i++;
8
}

main.c
1
#include "Lib.h"
2
3
volatile uint8_t A = 0;
4
int main(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ß

von Hugo H. (hugohurtig1)


Lesenswert?

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.

von Daniel K. (daniel_k80)


Lesenswert?

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.

von Volker B. (Firma: L-E-A) (vobs)


Lesenswert?

Daniel K. schrieb:

> LibInt.c
>
1
> #include "Lib.h"
2
> 
3
> volatile uint8_t i;
4
>

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

von Daniel K. (daniel_k80)


Lesenswert?

Volker B. schrieb:
> Daniel K. schrieb:
>
>> LibInt.c
>>
1
>> #include "Lib.h"
2
>>
3
>> volatile uint8_t i;
4
>>
>
> 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.

von Abyssaler Einspeiser (Gast)


Lesenswert?

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.

von Hugo H. (hugohurtig1)


Lesenswert?

Ein AS-Projekt wäre nett gewesen - egal, ich habe das Original mal 
"nachgebaut".

Bezogen auf den Int-Vector sieht für mich alles gut aus:

Disassembly of section .text:

00000000 <__vectors>:
   0:  4b c0         rjmp  .+150      ; 0x98 <__ctors_end>
   2:  00 00         nop
   4:  59 c0         rjmp  .+178      ; 0xb8 <__bad_interrupt>
   6:  00 00         nop
   8:  57 c0         rjmp  .+174      ; 0xb8 <__bad_interrupt>
   a:  00 00         nop
   c:  55 c0         rjmp  .+170      ; 0xb8 <__bad_interrupt>
   e:  00 00         nop
  10:  53 c0         rjmp  .+166      ; 0xb8 <__bad_interrupt>
  12:  00 00         nop
  14:  51 c0         rjmp  .+162      ; 0xb8 <__bad_interrupt>
  16:  00 00         nop
  18:  4f c0         rjmp  .+158      ; 0xb8 <__bad_interrupt>
  1a:  00 00         nop
  1c:  4d c0         rjmp  .+154      ; 0xb8 <__bad_interrupt>
  1e:  00 00         nop
  20:  4b c0         rjmp  .+150      ; 0xb8 <__bad_interrupt>
  22:  00 00         nop
  24:  49 c0         rjmp  .+146      ; 0xb8 <__bad_interrupt>
  26:  00 00         nop
  28:  4b c0         rjmp  .+150      ; 0xc0 <__vector_10>
  2a:  00 00         nop
...
ISR(USB_GEN_vect)
{
  c0:  1f 92         push  r1



In .map:

.text.__vector_10
                0x000000c0       0x22 LibInt.o
                0x000000c0                __vector_10

von Daniel K. (daniel_k80)


Angehängte Dateien:

Lesenswert?

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

von Volker B. (Firma: L-E-A) (vobs)


Lesenswert?

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

von Markus F. (mfro)


Lesenswert?

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.

von Abyssaler Einspeiser (Gast)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

Markus F. schrieb:
> 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.

Und deshalb muß man ihm einen geben:

https://www.nongnu.org/avr-libc/user-manual/library.html

Abyssaler Einspeiser schrieb:
> Aus:
>
> ISR(USB_GEN_vect)
>
> Mach:
>
> ISR(Ein fuer den Controller gueltiger Vektorname)

aus iousbxx6_7.h:
1
/* USB General Interrupt Request */
2
#define USB_GEN_vect_num    10
3
#define USB_GEN_vect      _VECTOR(10)

Sollte eigentlich passen...

Oliver

von Andreas M. (amesser)


Lesenswert?

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.

von Daniel K. (daniel_k80)


Lesenswert?

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:
1
 .text.__vector_10
2
                0x000000e6       0x34 ../../Lib/Debug\libLib.a(LibInt.o)
3
                0x000000e6                __vector_10
4
                0x0000011a                . = ALIGN (0x2)

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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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
extern void __vector_blah (void);
6
7
// In main.c
8
ISR (USB_GEN_vect, ISR_NAKED)
9
{
10
    __asm volatile ("%~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.

von Daniel K. (daniel_k80)


Lesenswert?

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
 extern volatile uint8_t i;
8
9
 uint8_t Get(void);
10
 
11
 extern void MyISR(void);
12
13
#endif /* LIB_H_ */

LibInt.c
1
#include "Lib.h"
2
3
void MyISR(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.

von Oliver S. (oliverso)


Lesenswert?

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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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?

von Oliver S. (oliverso)


Lesenswert?

Auch mit -ffunction-sections.

Compileraufrufe:

lib.c
avr-gcc -Wall -Os -fpack-struct -fshort-enums -ffunction-sections 
-fdata-sections -std=gnu99 -funsigned-char -funsigned-bitfields 
-mmcu=at90usb1287 -DF_CPU=1000000UL -MMD -MP -MF"lib.d" -MT"lib.o" -c -o 
"lib.o" "../lib.c"

main:
avr-gcc -I"C:\pocketcpp\workspace\AVRlib" -Wall -Os -fpack-struct 
-fshort-enums -ffunction-sections -fdata-sections -std=gnu99 
-funsigned-char -funsigned-bitfields -mmcu=at90usb1287 -DF_CPU=1000000UL 
-MMD -MP -MF"main.d" -MT"main.o" -c -o "main.o" "../main.c"

Disassembly of section .text:

00000000 <__vectors>:
   0:  0c 94 4c 00   jmp  0x98  ; 0x98 <__ctors_end>
   4:  0c 94 5e 00   jmp  0xbc  ; 0xbc <__bad_interrupt>
   8:  0c 94 5e 00   jmp  0xbc  ; 0xbc <__bad_interrupt>
   c:  0c 94 5e 00   jmp  0xbc  ; 0xbc <__bad_interrupt>
  10:  0c 94 5e 00   jmp  0xbc  ; 0xbc <__bad_interrupt>
  14:  0c 94 5e 00   jmp  0xbc  ; 0xbc <__bad_interrupt>
  18:  0c 94 5e 00   jmp  0xbc  ; 0xbc <__bad_interrupt>
  1c:  0c 94 5e 00   jmp  0xbc  ; 0xbc <__bad_interrupt>
  20:  0c 94 5e 00   jmp  0xbc  ; 0xbc <__bad_interrupt>
  24:  0c 94 5e 00   jmp  0xbc  ; 0xbc <__bad_interrupt>
  28:  0c 94 79 00   jmp  0xf2  ; 0xf2 <__vector_10>
  2c:  0c 94 5e 00   jmp  0xbc  ; 0xbc <__bad_interrupt>
  30:  0c 94 5e 00   jmp  0xbc  ; 0xbc <__bad_interrupt>
...

000000de <__vector_10>:
  de:  8f 93         push  r24
  e0:  8f b7         in  r24, 0x3f  ; 63
  e2:  8f 93         push  r24
...

Oliver

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.