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?
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.
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.
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
voidmeine_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:
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.
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.
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.