Forum: Mikrocontroller und Digitale Elektronik Modulare Programierung und Shared Interrupts?


von Anfänger (Gast)


Lesenswert?

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.

von Claus M. (energy)


Lesenswert?

In jedem Modul eine inline Funktion die von einer zentralen stelle wo 
die eigentliche isr implementiert ist aufgerufen werden.

von dunno.. (Gast)


Lesenswert?

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

von georg (Gast)


Lesenswert?

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

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


Lesenswert?

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.

von Pascal C. (Gast)


Lesenswert?

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?

von c-hater (Gast)


Lesenswert?

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

von Stefan F. (Gast)


Lesenswert?

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.

von georg (Gast)


Lesenswert?

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

von W.S. (Gast)


Lesenswert?

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:
1
#define idPorthandler 5
2
event = (pinnummer<<11) | (richtung<<10) | idPorthandler;

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

von Gerd E. (robberknight)


Lesenswert?

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

von Bernd K. (prof7bit)


Lesenswert?

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.

von Μαtthias W. (matthias) Benutzerseite


Lesenswert?

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

von A. S. (Gast)


Lesenswert?

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.

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


Lesenswert?

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.

von Μαtthias W. (matthias) Benutzerseite


Lesenswert?

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

von Bernd K. (prof7bit)


Lesenswert?

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
WEAK void hook_porta_bit0() {}
11
WEAK void hook_porta_bit1() {}
12
WEAK void hook_porta_bit2() {}
13
WEAK void hook_porta_bit3() {}
14
15
16
17
/**
18
 * Interrupt für Beispielhardware
19
 * mit Phantasie-Registern, es geht
20
 * ums Prinzip.
21
 */
22
void PORTA_IRQHandler() {
23
    u32 flags = 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
void hook_porta_bit2() {
6
    warp_drive_set_speed(WARP_0);
7
    shields_up();
8
    red_alert();
9
}
10
11
int main() {
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.

: Bearbeitet durch User
von Axel S. (a-za-z0-9)


Lesenswert?

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?

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.