Forum: Mikrocontroller und Digitale Elektronik Interrupts verstehen


von Martin M. (murmele)


Lesenswert?

Hallo ich möchte einmal kurz verstehen, wie man die Interrupts so setzt, 
dass die eigene Funktion aufgerufen wird. z.B. Bei einem STM32 muss ich 
ja das richtige Bit setzen, um den Interrupt zu aktivieren. Wenn ein 
Interrupt kommt, wird in der Interrupttabelle nachgeschaut auf welche 
Adresse dieser Vektor zeigt und dann wird dort hingesprungen. Stimmt das 
so?

Aber wenn ich so die Interruptvektortabelle anschaue(STM32F411RE) dann 
gibt es dort doch deutlich mehr Interrupte als in der Einleitung 
angegeben(52 maskable interrupt channels) oder ist das die maximale 
Anzahl an Interrupts zugleich?

von Carsten M. (ccp1con)


Lesenswert?

Ich kenne nur PICs und da ist es nicht möglich seine eigenen 
Interruptroutinen zu definieren (auch wenn da noch scheinbar jede Menge 
Platz in der Tabelle ist, aber die Hardware ist für die anderen, 
unbenutzten Ints nicht vorhanden).

Aber du kannst dir ganz einfach helfen, indem du einen Int, der gerade 
frei ist für deine Zwecke benutzt. Du schreibst einfach wie üblich die 
ISR (z.B. für ADC) und dann kannst du irgendwo im Programm das Interrupt 
Flag setzten und die Interrupt Service Routine wird aufgerufen. Solltes 
natürlich ADC nicht anschalten.

von auch Martin (Gast)


Lesenswert?

Beim STM32 werden teilweise mehrere Interruptquellen auf einen Vektor 
gemapped. Man muß die dann per Software wieder auseinanderdröseln, wenn 
man mehrer Interrupts benutzt die zu einem Vektor gehören.

von Axel S. (a-za-z0-9)


Lesenswert?

Martin M. schrieb:
> Hallo ich möchte einmal kurz verstehen, wie man die Interrupts so setzt,
> dass die eigene Funktion aufgerufen wird.

Die genaue Vorgehensweise hängt von der verwendeten Toolchain ab. Bei 
den ARMs kocht da jede ein bißchen ihr eigenes Süppchen. Am Ende geht es 
darum, die Addresse deiner ISR an die richtige Stelle der Vektortabelle 
zu bekommen. Meist (aber nicht immer) reicht es dazu, wenn deine 
Funktion den richtigen "magischen" Funktionsnamen hat.

Bei anderen Toolchains mußt du bei der Funktionsdefinition ein Pragma 
angeben a'la

1
void meine_ISR(void) __interrupt(10)
2
{
3
  ...
4
}

wobei die 10 hier nur ein Beispiel ist (für den 10. Eintrag in der 
Vektortabelle). Und wenn du den Startup-Code selber schreibst wie z.B. 
dieser Mann hier: http://eleceng.dit.ie/frank/arm/ dann kannst du die 
Vektortabelle von Hand füllen. Das sieht dann z.B. so aus:

1
const void * Vectors[] __attribute__((section(".vectors"))) =
2
{
3
    (void *)0x20001000, /* Top of stack (4k) */
4
    init,               /* Reset Handler */
5
    Default_Handler,    /* NMI */
6
    Default_Handler,    /* Hard Fault */
7
    Default_Handler,    /* MemManage */
8
    Default_Handler,    /* Reserved  */
9
    Default_Handler,    /* Reserved */
10
    Default_Handler,    /* Reserved */
11
    Default_Handler,    /* Reserved */
12
    Default_Handler,    /* Reserved */
13
    Default_Handler,    /* Reserved */
14
    Default_Handler,    /* SVCall */
15
    Default_Handler,    /* Reserved */
16
    Default_Handler,    /* Reserved */
17
    Default_Handler,    /* PendSV */
18
    Default_Handler,    /* SysTick */
19
    /* External interrupt handlers follow */
20
    Default_Handler,    /* 0: WWDG */
21
    Default_Handler,    /* 1: Reserved */
22
    Default_Handler,    /* 2: RTC */
23
    Default_Handler,    /* 3: FLASH */
24
...
25
};
26
27
...
28
29
void Default_Handler()
30
{
31
    while(1);
32
}

Hier würdest du dann einfach den Funktionsnamen der ISR an der passenden 
Stelle in die Tabelle einsetzen.

> wenn ich so die Interruptvektortabelle anschaue(STM32F411RE) dann
> gibt es dort doch deutlich mehr Interrupte als in der Einleitung
> angegeben(52 maskable interrupt channels) oder ist das die maximale
> Anzahl an Interrupts zugleich?

Die Vektortabelle ist für alle µC einer Familie (hier: STM32F4) gleich 
groß. Da aber nicht alle Mitglieder der Familie über die gleiche 
Peripherie-Ausstattung verfügen, ist der eine oder andere Vektor 
unbenutzt. Der wird dann auch nie angesprungen.

Darüber hinaus ist die Tabelle keine reine Interrupt-Tabelle, sondern 
eine kombinierte Exception/Interrupt-Tabelle. Die ersten 16 Einträge 
sind für Exceptions reserviert. Danach können bis zu 240 weitere 
Einträge für Interrupts kommen. Die meisten realen Implementierungen 
haben aber eine deutlich kürzere Interrupt-Tabelle.

von W.S. (Gast)


Lesenswert?

Martin M. schrieb:
> Hallo ich möchte einmal kurz verstehen, wie man die Interrupts so setzt,
> dass die eigene Funktion aufgerufen wird. z.B. Bei einem STM32 muss ich
> ja das richtige Bit setzen, um den Interrupt zu aktivieren.

Laß dich nicht verwirren. Axel hat es zwar aus Sicht von C beschrieben, 
aber das ist mehr Verschleierung als Erklärung.

Also: Die Cortexe haben einen Interrupt-Controller (NVIC) in die CPU 
eingebaut und dieser NVIC handhabt alle Interrupts sowie alles, was man 
braucht, um einen Interrupt auch softwaemäßig auslösen zu können. Aber 
das alles scheint hier NICHT dein Problem zu sein.

Was du hier verstehen mußt, ist folgendes:
Ab Adresse 0 hat bei den Cortexen eine Vektortabelle zu stehen. Es sind 
alles 32 Bit Adressen und die Tabelle kann je nach Chip unterschiedlich 
lang sein. Die allerersten Einträge sind hingegen von ARM festgelegt und 
sie beinhalten neben den Vektoren für interne Exceptions auch den 
Startup nach folgendem Schema:

Nach dem Reset lädt die CPU den Inhalt von Vektor 0 in den Stackpointer 
und den Inhalt von Vektor 1 in den Programmcounter.

Für alle anderen Vektoren ist eine eiserne Regel geschaffen worden: 
Dort, in der Quelle, wo sie deklariert werden, müssen die damit 
bezeichneten Default-Interrupthandler als WEAK gekennzeichnet sein. Das 
ist nötig, damit der Linker beim Vorhandensein eines echten Handlers 
(der sich ja in der Regel in einer ganz anderen Quelle befindet) die 
Adresse eben dieses echten Handlers in die Vektortabelle einträgt - und 
nicht etwa die Adresse des Defaulthandlers.

Ich finde, daß man mit der Möglichkeit, einen Startupcode auch in C 
formulieren zu können, es zwar gut gemeint hat, aber dabei das genaue 
Gegenteil erreicht hat. Es hat dazu geführt, daß die Leute genau 
DESHALB nicht mehr verstehen, wie das Ganze eigentlich funktioniert.

Also: Wenn alles richtig hingeschrieben ist, dann enthält der 
Startupcode die Vektortabelle und den/die Defaulthandler, welche als 
"WEAK" gekennzeichnet sind.

Du hingegen schreibst dir für deine Peripherie den Handler, den du 
tatsächlich brauchst. Aber: du mußt ihn EXAKT GENAUSO benennen wie der 
entsprechende Defaulthandler heißt. Sonst kriegt der Linker das nicht 
auf die Reihe und dein Handler wird garnicht in die Vektortabelle 
eingetragen.

Etwas anderes ist es, den betreffenden Interrupt auch tatsächlich 
freizuschalten, da muß man den NVIC bemühen. Beispiel: NVIC_ISERx = 
(1<<IrNummer); Näheres MUSS man im RefManual nachlesen.

W.S.

von Axel S. (a-za-z0-9)


Lesenswert?

W.S. schrieb:
> Laß dich nicht verwirren. Axel hat es zwar aus Sicht von C beschrieben,
> aber das ist mehr Verschleierung als Erklärung.

Ähhm. Verwirrung stiftest hier eher du.

> Ab Adresse 0 hat bei den Cortexen eine Vektortabelle zu stehen.

Die Tabelle ist i.d.R. relozierbar, Adresse 0 ist also keineswegs so in 
Stein gemeißelt wie du behauptest. Außerdem ist die Adresse der 
Vektortabelle auch wieder nur ein Detail, das für das Verständnis der 
Funktionsweise ohne Belang ist.

> Für alle anderen Vektoren ist eine eiserne Regel geschaffen worden:
> Dort, in der Quelle, wo sie deklariert werden, müssen die damit
> bezeichneten Default-Interrupthandler als WEAK gekennzeichnet sein. Das
> ist nötig, damit der Linker beim Vorhandensein eines echten Handlers
> (der sich ja in der Regel in einer ganz anderen Quelle befindet) die
> Adresse eben dieses echten Handlers in die Vektortabelle einträgt - und
> nicht etwa die Adresse des Defaulthandlers.

Und das ist ein weiteres Implementierungsdetail. Es setzt voraus daß der 
Linker der Toolchain überhaupt weak symbols kennt. Und selbst wenn - 
man muß es nicht so machen. Siehe das von mir gegebene Beispiel mit der 
Vektortabelle als C-Array.

Und in Assembler würde man das schon gar nicht so machen. Da würde man 
schlicht und einfach schreiben:
1
.section "vectors"
2
.long top_of_stack
3
.long reset
4
.long ...
5
...

Und die Labels der Funktionen direkt in die Tabelle schreiben.

> Ich finde, daß man mit der Möglichkeit, einen Startupcode auch in C
> formulieren zu können, es zwar gut gemeint hat, aber dabei das genaue
> Gegenteil erreicht hat. Es hat dazu geführt, daß die Leute genau
> DESHALB nicht mehr verstehen, wie das Ganze eigentlich funktioniert.

Überhaupt nicht. Die Verwendung von weak symbols ist eine weitere Hürde 
beim Verständnis der Zusammenhänge. Und es ist gut wenn man den Leuten 
die Möglichkeit gibt, Erfolgserlebnisse zu erzielen auch ohne alle 
vorhandenen Hürden gleich beim ersten Anlauf überspringen zu müssen. Die 
Funktionsweise des Linkers, insbesondere die Verwendung von weak symbols 
und generischem Startup-Code können sie danach ja immer noch lernen.

> Also: Wenn alles richtig hingeschrieben ist, dann enthält der
> Startupcode die Vektortabelle und den/die Defaulthandler, welche als
> "WEAK" gekennzeichnet sind.

Nur für deine höchst private Meinung davon, was "richtig" ist.

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.