Hi,
ich versuche eine größere main.c in viele kleine wiederverwendbare
Module (.c+.h) aufzuteilen.
Wie macht man das mit den shared Interrupts?
Beim stm32 gibt es den EXTI4_15, der ist für 12 gpio Interrupts
zuständig.
Die Module können ja nicht alle den selben Interrupt definieren.
Jedes modul registriert bei der initialisierung seine eigene isr beim
isr-modul als callback (funktionspointer)
Kommt der interrupt, wird die isr des isr moduls aufgerufen, diese
feuert die registrierten callbacks.
Auf diese art ist es sogar zur laufzeit veränderlich..
Anfänger schrieb:> Die Module können ja nicht alle den selben Interrupt definieren.
Wieso nicht - die ISR muss halt als erstes prüfen, welcher Pin den
Interrupt ausgelöst hat. Natürlich gibt es diese ISR nur einmal, da gibt
es viele Möglichkeiten, z.B. ein eigenes Modul.
Bei Systemen wie PCI werden shared Interrupts bei der Installation
verkettet, jeder muss prüfen "bin ich das", wenn ja wird was gemacht,
und dann an die nächste ISR in der Kette übergeben. Aber so ein Aufwand
ist für ein paar GPIOs überflüssig.
Georg
dunno.. schrieb:> Jedes modul registriert bei der initialisierung seine eigene isr beim> isr-modul als callback (funktionspointer)>> Kommt der interrupt, wird die isr des isr moduls aufgerufen, diese> feuert die registrierten callbacks.
Eine saubere Lösung läuft am Ende darauf hinaus. Leider ist der Overhead
(Laufzeit, extra RAM-Bedarf) nicht unbeträchtlich. Und vor allem: man
kriegt ihn auch dann, wenn man den trivialen Fall hat, daß es für alle
genutzten Interrupts jeweils nur eine ISR gibt.
Kann man die 'ISR-Unterfunktionen' nicht irgend wie zur Linkzeit der
ISR-Hauptfunktionen bekannt machen, ohne das die ISR-Hauptfunktionen auf
die
Existenz aller ISR-Unterfunktionen besteht?
Pascal C. schrieb:> Kann man die 'ISR-Unterfunktionen' nicht irgend wie zur Linkzeit der> ISR-Hauptfunktionen bekannt machen, ohne das die ISR-Hauptfunktionen auf> die> Existenz aller ISR-Unterfunktionen besteht?
In Assembler geht sowas problemlos und ohne allzu großen Overhead (das
Asm-Äquivalent von inline-Funktionen ist mit diesem Mechanismus leider
auch nicht möglich)
Der Trick ist: sorge dafür, das der Aufruf zunächst erstmal als nop
(oder besser: als Folge von nops für die größmögliche call-Instruktion)
assembliert wird.
Der aufzurufende Code muss dann die nops durch den tatsächlich zum
Aufruf nötigen Code ersetzen. Ist kein aufzurufender Code vorhanden,
bleibt's eben bei den nops...
Mit ein wenig Makro-Zucker kann man sowas in Asm sogar sehr gut lesbar
schreiben...
Angesichts der Tatsache, dass C auch nix anderes als ein aufgedonnerter
Makro-Assembler ist, sollte es also auch in C irgendwie machbar sein.
Aber wahrscheinlich nur mit den C-typischen unwahrscheinlich abstrusen
syntaktischen Verrenkungen, die vielleicht dafür sorgen, dass es am Ende
wie gewünscht funktioniert, aber natürlich C-typisch auch dafür, dass
absolut niemand (außer dem Verfasser natürlich) beim ersten Lesen des
Codes erkennen kann, was da passiert...
Dass dieser Beitrag vom c-hater kam, war ja klar. Dann habe ich nochmal
hoch gescrollt und sah, dass ich Recht hatte.
Auf jeden Fall merkt man mal wieder sehr deutlich, dass der c-hater die
Sprache hasst, obwohl (oder weil) er sie nicht wirklich kennt.
Pascal C. schrieb:> Kann man die 'ISR-Unterfunktionen' nicht irgend wie zur Linkzeit der> ISR-Hauptfunktionen bekannt machen
Warum ausgerechnet dann? In den Sourcefiles kann man das, egal welche
Sprache, mit einem IFDEF-Konstrukt erledigen, oder beim Power On wird
eine ISR-Tabelle aufgebaut, da gibt es auch viele Möglichkeiten, z.B.
Verketten wie ich oben schon beschrieben habe. Der Linker bietet da die
wenigsten Möglichkeiten. Bei heutigen IDEs hat man eh wenig Einfluss auf
den Linker, und auf Anhieb fällt mir auch nicht ein wie man den dazu
bringt statt einer nicht vorhandenen ISR eine NOP-ISR einzubinden.
Georg
Anfänger schrieb:> Wie macht man das mit den shared Interrupts?
Am besten: GARNICHT!
Versuche doch bitte, deine Funktionalität wirklich in verschiedene
Ebenen aufzuteilen. Also eine Quelle für den/die Lowlevel-Funktionalität
der Serielle(n), eine andere Quelle für den I2C, eine Quelle für die
Lowlevel-Funktionen des Displays und eine andere (GDI) für die
High-Level Funktionalität des Displays (Text, Linien usw. zeichnen) und
so weiter.
Also das Trennen des Gefummels in den Peripherie-Cores und der
Behandlung und Pufferung der I/O-Ströme von den höheren Schichten, also
den Algorithmen usw.
Offenbar willst du mit irgendwelchen Zustandsänderungen an diversen Pins
des Controllers einen zuständigen Handler starten, der dann irgendwas
macht, aber du hast viele verschiedene Handler für viele _nicht
zusammenhängende_ Dinge, die von vielen verschiedenen Pins angestoßen
werden sollen.
Sowas mit Tabellen von Callback-Funktionen tun zu wollen, halte ich für
einen viel zu absturzfreudigen halsbrecherischen Zirkus. Deshalb schlag
ich dir vor, es mit einem Event-System zu tun.
Das geht dann etwa so:
- ein einziger Interrupt-Handler ist für die Interrupts aus deinen
diversen Pins zuständig. Er macht nur eines: er findet heraus, welcher
Pin das war und dann packt er einen Event (z.B. ein unsigned long) mit
der Information "Porthandler" und "Pinnummer" und "h->l" oder "l->h" in
eine Event-Queue. Das war's für ihn.
- so ein Event ist also ne ausreichend große Zahl, die einen Identcode
und bei Bedarf auch noch ein paar Bits für Zusatz-Informationen
enthalten kann.
Beispiel: Bits 0..9 sind für den Identcode (Maus=1, Tastatur=2,
Systemuhr=3, Touchscreen=4, Porthandler=5 usw.), die restlichen Bits
10..31 sind dann für quasi Parameter verfügbar. Sagen wir mal so:
- die Event-Queue ist schlichtweg ein üblicher Ringpuffer, wo man mit
einem Handler
bool AddEvent(unsigned long aEvent);
so einen Event hineinbekommt und mit zwei anderen Handlern
bool EventAval(void)
unsigned long GetEvent(void)
die Events wieder herausbekommt.
- in deiner Grundschleife fragst du einfach nur die Eventqueue ab und
wenn du dort was findest, dann verteilst du das der Reihe nach an alle
Pinhandler. Ein jeder davon guckt, ob das "sein" Pin war und wenn, dann
tut er halt was er soll, wenn nicht, dann macht er nix.
Auf die Weise hast du alles voneinander getrennt - und das ohne
Callback's und ohne Pointerlisten.
W.S.
W.S. schrieb:> Anfänger schrieb:>> Wie macht man das mit den shared Interrupts?>> Am besten: GARNICHT!
[...]
> Sowas mit Tabellen von Callback-Funktionen tun zu wollen, halte ich für> einen viel zu absturzfreudigen halsbrecherischen Zirkus. Deshalb schlag> ich dir vor, es mit einem Event-System zu tun.
Sehe ich auch so.
Allerdings muss man sich so ein Event-System nicht unbedingt selbst
bauen, sowas gibt es schon fertig, nennt sich RTOS.
Je nach RTOS gibt es verschiedene Methoden um die Events aus dem
Interrupthandler in die eigentlichen Handlingfunktionen zu bekommen.
Z.B. threads die blockieren, bis sie eine Message gesendet bekommen.
Ich verwende als RTOS gerne Chibios, dort gibt es ein fertiges
Event-System für sowas:
http://www.chibios.org/dokuwiki/doku.php?id=chibios:book:kernel_events
Pascal C. schrieb:> Kann man die 'ISR-Unterfunktionen' nicht irgend wie zur Linkzeit der> ISR-Hauptfunktionen bekannt machen, ohne das die ISR-Hauptfunktionen auf> die> Existenz aller ISR-Unterfunktionen besteht?
Mit weak könnte was gehen. Wenn Du es geschickt anstellst (und auch LTO
verwendest) werden womöglich alle leeren default-Implementierungen
wegoptimiert und die vorhandenen strong implementierungen werden an Ort
und Stelle geinlined.
Bernd K. schrieb:> Pascal C. schrieb:>> Kann man die 'ISR-Unterfunktionen' nicht irgend wie zur Linkzeit der>> ISR-Hauptfunktionen bekannt machen, ohne das die ISR-Hauptfunktionen auf>> die>> Existenz aller ISR-Unterfunktionen besteht?>> Mit weak könnte was gehen. Wenn Du es geschickt anstellst (und auch LTO> verwendest) werden womöglich alle leeren default-Implementierungen> wegoptimiert und die vorhandenen strong implementierungen werden an Ort> und Stelle geinlined.
Genau so. Der Ansatz ist genau der gleiche wie mit der Vektortabelle
auch. Mit LTO sollte der Overhead bei exakt null liegen im Gegensatz zu
der oben skizzierten Lösung mit zur Laufzeit eingehängt en
Funktionspointer.
Matthias
Pascal C. schrieb:> Kann man die 'ISR-Unterfunktionen' nicht irgend wie zur Linkzeit der> ISR-Hauptfunktionen bekannt machen, ohne das die ISR-Hauptfunktionen auf> die> Existenz aller ISR-Unterfunktionen besteht?
Ja, das geht, über linker-sections, in denen Funktionspointer liegen.
Ist nur relativ viel Aufwand. Da ist eine linkListe einfacher, in die
sich jeder eintragen kann.
Bernd K. schrieb:> Pascal C. schrieb:>> Kann man die 'ISR-Unterfunktionen' nicht irgend wie zur Linkzeit der>> ISR-Hauptfunktionen bekannt machen, ohne das die ISR-Hauptfunktionen auf>> die>> Existenz aller ISR-Unterfunktionen besteht?>> Mit weak könnte was gehen. Wenn Du es geschickt anstellst (und auch LTO> verwendest) werden womöglich alle leeren default-Implementierungen> wegoptimiert und die vorhandenen strong implementierungen werden an Ort> und Stelle geinlined.
Schön. Nur löst das nicht das Problem des TO. Denn auf diese Weise
kannst du jede Default-Implementierung (weak symbol) nur genau einmal
überschreiben. Sobald zwei Module sich in den selben Interrupt
einklinken wollen, knallt es beim Linken.
Axel S. schrieb:> Bernd K. schrieb:>> Pascal C. schrieb:>>> Kann man die 'ISR-Unterfunktionen' nicht irgend wie zur Linkzeit der>>> ISR-Hauptfunktionen bekannt machen, ohne das die ISR-Hauptfunktionen auf>>> die>>> Existenz aller ISR-Unterfunktionen besteht?>>>> Mit weak könnte was gehen. Wenn Du es geschickt anstellst (und auch LTO>> verwendest) werden womöglich alle leeren default-Implementierungen>> wegoptimiert und die vorhandenen strong implementierungen werden an Ort>> und Stelle geinlined.>> Schön. Nur löst das nicht das Problem des TO. Denn auf diese Weise> kannst du jede Default-Implementierung (weak symbol) nur genau /einmal/> überschreiben. Sobald zwei Module sich in den selben Interrupt> einklinken wollen, knallt es beim Linken.
Ich hab das jetzt so interpretiert als ob der TO den Vektor EXTI4_15 der
ja für 12 externe Interrupts zuständig ist gerne so implementieren würde
als wären es zwölf separate Vektoren. Und das ließe sich mit
weak-Funktionen und einer Verteilerfunktion erreichen.
Mehrere Funktionen pro Pin gehen dann nicht mehr so einfach. Evtl.
könnte man das mit C++ constexpr zur Compilerzeit lösen.
Matthias
Axel S. schrieb:> chön. Nur löst das nicht das Problem des TO. Denn auf diese Weise> kannst du jede Default-Implementierung (weak symbol) nur genau einmal
Nein, ich hab mir das so gedacht:
1
#define WEAK __attribute((weak))
2
3
4
/**
5
* define empty weak default handlers,
6
* they will be optimized away if not
7
* overridden by application provided
8
* hook functions.
9
*/
10
WEAKvoidhook_porta_bit0(){}
11
WEAKvoidhook_porta_bit1(){}
12
WEAKvoidhook_porta_bit2(){}
13
WEAKvoidhook_porta_bit3(){}
14
15
16
17
/**
18
* Interrupt für Beispielhardware
19
* mit Phantasie-Registern, es geht
20
* ums Prinzip.
21
*/
22
voidPORTA_IRQHandler(){
23
u32flags=PORTA->IRQFLAGS;
24
25
if(flags&(1UL<<0))
26
hook_porta_bit0();
27
28
if(flags&(1UL<<1))
29
hook_porta_bit1();
30
31
if(flags&(1UL<<2))
32
hook_porta_bit2();
33
34
if(flags&(1UL<<3))
35
hook_porta_bit3();
36
37
// [...]
38
39
// clear all flags that were set
40
PORTA->IRQFLAGS=flags;
41
}
Und in irgendeiner anderen C-Datei dann:
1
/**
2
* Implement the IRQ-Hook for Port A bit 2
3
* to detect broken deflector energy relay
4
*/
5
voidhook_porta_bit2(){
6
warp_drive_set_speed(WARP_0);
7
shields_up();
8
red_alert();
9
}
10
11
intmain(){
12
make_it_so();
13
}
Sollte dazu führen daß alles verschwindet bis auf das dritte if und
obige Hook-Funktion wird wahrscheinlich auch komplett in den IRQHandler
geinlined.
Bernd K. schrieb:> Axel S. schrieb:>> chön. Nur löst das nicht das Problem des TO. Denn auf diese Weise>> kannst du jede Default-Implementierung (weak symbol) nur genau einmal>> Nein, ich hab mir das so gedacht:
Wie gesagt: das deckt nur einen Spezialfall ab; wenn zwar nur ein
Vektor für N verschiedene Interruptquellen (du nutzt hier Bits in einem
Register als Marker für die Quelle) benutzt wird und man höchstens
eine ISR pro Quelle registrieren will. Dann geht das natürlich.
Aber im allgemeinen Fall will man diese Einschränkung nicht. Sondern
jedes Modul soll sich in jeden Interrupt einklinken können. Im Zweifel
auch mehrere in den gleichen Interrupt. Das ist durchaus sinnvoll, wenn
das z.B. ein Timekeeper-Interrupt ist. Aber auch in anderen Fällen, z.B.
für einen UART, wo Empfangs- und Sende-Events über den gleichen Vektor
signalisiert werden. Denn warum sollte man den Sendepuffer nicht als
separates Modul realisieren?
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