Forum: Mikrocontroller und Digitale Elektronik (STM32F103) Bei Modularisierung Interrupt-Methode dynamisch zuweisen


von Minti (Gast)


Lesenswert?

Hallo allerseits,

ich wollte meinen Code modularisieren, um später einfach neue Projekte 
zusammen zubasteln.

Beschreibung des Problems am Beispiel: Es gibt 2 Module (Modul_A und 
Modul_B), die jeweils einen Timer benötigen. Die Signaturen deren 
Konfigurationsmethoden sehen folgendermaßen aus:
1
void Modul_A_config(TIM_TypeDef* TIMx, ...);
2
void Modul_B_config(TIM_TypeDef* TIMx, ...);
In der Main-Methode kann ich für jeden Modul einen Timer festlegen:
1
Modul_A_config(TIM2, ...);
2
Modul_B_config(TIM3, ...);
So weit, so gut. Die Schwierigkeiten entstehen bei Interrupt-Methoden. 
Ich komme nicht dahin, wie man den Methodenrumpf TIM2_IRQHandler durch 
TIM3_IRQHandler dynamisch ersetzt. Kann man mittels von Methodenzeiger 
die Interruptmethode auf eine andere Methode (z.B. DoCycleWork_A) 
"umleiten"? Gibt es überhaupt eine Lösung für dieses Problem?

Minti

von Roland H. (batchman)


Lesenswert?

Minti schrieb:
> Die Schwierigkeiten entstehen bei Interrupt-Methoden.
> Ich komme nicht dahin, wie man den Methodenrumpf TIM2_IRQHandler durch
> TIM3_IRQHandler dynamisch ersetzt. Kann man mittels von Methodenzeiger
> die Interruptmethode auf eine andere Methode (z.B. DoCycleWork_A)
> "umleiten"? Gibt es überhaupt eine Lösung für dieses Problem?

Ich habe es noch nie versucht, aber die Adressen der ISRs können vom 
Flash ins RAM verlagert werden. So sollte es möglich sein, zur Laufzeit 
dort die gewünschten Adressen einzutragen.

Ginge auch über die von Dir beschriebene Methoden mit Funktionszeigern 
in der ISR: In der ISR wird einfach ein Funktion über einen Zeiger 
aufgerufen.

Die statische Variante verwendet #define/#if:
1
#define MODUL_A_TIMER 1
2
3
#if MODUL_A_TIMER == 1
4
5
void void TIM1_IRQHandler() {
6
  TIM_ClearITPendingBit(TIM1, TIM_FLAG_Update);
7
  modulAIsr();
8
}
9
10
#elif MODUL_A_TIMER == 2
11
12
void void TIM2_IRQHandler() {
13
  TIM_ClearITPendingBit(TIM2, TIM_FLAG_Update);
14
  modulAIsr();
15
}
16
17
...

Es stellt sich die Frage, welchen Vorteil der dynamische Ansatz im 
Vergleich zur statischen #define/#if-Variante bietet, sofern man nicht 
tatsächlich während der Laufzeit mehrfach zwischen verschiedenen ISRs 
wechseln will.

Das Problem ist die Vielfalt der Timer beim stm32, und beim stm32 kommt 
hinzu, dass teilweise die gleiche ISR von mehreren Timer-Interrupts 
angesprungen werden. Positiv beim stm32 ist, dass er so viele Timer hat, 
dass man sich den Luxus erlauben kann, grundsätzlich einem Modul einen 
Timer fix zuzuweisen - eben den, dessen Fähigkeiten am besten passen.

Da die Anzahl der Timer bzw. der Peripherie limitiert ist, sind das 
irgendwie alle "singletons", und das gilt insbesondere bei der ISR.

von Minti (Gast)


Lesenswert?

Hi Roland,

Danke für deine Antwort!

Bei der statischer Variante müsste man für jedes Modul fast das dasselbe 
Schreiben. Ich verfolgen den Lösungsweg mit Funktionszeigern. Klappt 
jetzt noch nicht (Vertue mich irgendwo mit Zeigern). Allerdings wäre die 
Lösung mit "Ersetzen" z.B. TIM2_IRQHandler zu TIM3_IRQHandler besser.
So sieht der aktuelle Stand:
1
//Util.h
2
3
int (*pTIM2_IRQ)(void);
4
int (*pTIM3_IRQ)(void);
5
... //und so weiter
6
7
8
//Util.c
9
10
void TIM2_IRQHandler(){
11
pTIM2_IRQ();
12
}
13
14
void TIM3_IRQHandler(){
15
pTIM3_IRQ();
16
}
17
... //und so weiter
Die Signaturen der Konfigurationsmethoden werden folgendermaßen 
erweitert:
1
void Modul_A_config(TIM_TypeDef* TIMx, int (*fkt)(void), ...);
2
void Modul_B_config(TIM_TypeDef* TIMx, int (*fkt)(void), ...);

z.B. Konfigurationsmethode des Moduls A:
1
void Modul_A_config(TIM_TypeDef* TIMx, int (*fkt)(void), ...){
2
fkt = DoCycleWork_A;
3
... // rest
4
}

Der Aufruf in der Main sieht so aus:
1
Modul_A_config(TIM2, pTIM2_IRQ, ...);
2
Modul_B_config(TIM3, pTIM3_IRQ, ...);
Wie ich aber schon erwähnt habe, es funktioniert zur Zeit nicht. Das 
Programm stürzt ab. Sieht jemand den Fehler auf dem ersten Blick?

- Minti

von A. B. (funky)


Lesenswert?

Ich habe das auch mal versucht...nicht bei Timern aber bei den UARTS.
Habs dann aber wieder sein gelassen. Man muss ja teilweise auch noch 
spezielle Pins zuweisen jenachdem welchen UART man z.B. benutzt.
Das Sinnvoll variabel zu gestalten hätte bei mir länger gedauert, als es 
von Hand einmal einzutragen.
Das sind ja normalerweise auch Konfigurationen, die sich während des 
Programmablaufs nicht ändern

Aber wenn du es hinbekommen hast, gerne posten :)

von Minti (Gast)


Lesenswert?

Der Fehler lag bei der Funktionssignatur der Konfigurationsmethoden von 
Modulen. Statt normalen Zeiger muss man den Zeiger vom Funktionszeiger 
übergeben. Nach Korrektur:
1
void Modul_A_config(TIM_TypeDef* TIMx, int (**fkt)(void), ...){
2
   *fkt = DoCycleWork_A;
3
   ... // rest
4
}
Der Aufruf der Methode in der Main:
1
Modul_A_config(TIM2, &pTIM2_IRQ, ...);
Ich habe es noch nicht auf µC getestet, da ich ihn nicht zur Hand habe. 
Aber in einem mini C-Programm habe ich es nach programmiert.

@A. B. (funky)
In meinem Fall arbeite ich auf einer Platine, also die Pins sind immer 
gleich. Über eine mir nicht bekannte Zeitspanne entstanden viele 
Projekte für diese Platine. Daher wollte ich es modularisieren, um 
leichter diese zu einem Projekt zu verbinden, und um gleichzeitig 
spätere Projekte einfacher zu integrieren.
Ich wüßte auch keine schnelle Möglichkeit noch die Pins dynamisch zu 
zuweisen.

- Minti

von Roland H. (batchman)


Lesenswert?

> Bei der statischer Variante müsste man für jedes Modul fast das dasselbe
> Schreiben.

Klar. Es hält sich in Grenzen. Jede der beschriebenen Methoden hat ihre 
Vor- und Nachteile.

> void Modul_A_config(TIM_TypeDef* TIMx, int (**fkt)(void), ...){
>    *fkt = DoCycleWork_A;
>    ... // rest
> }

Ich sehe hier zwei Probleme. Zum ersten ist der "logische" Parameter 
TIMx in zwei tatsächliche aufgeteilt. D. h. es besteht die Gefahr, dass 
z. B. TIM1 und der Zeiger der ISR für TIM2 übergeben wird. Ich würde 
hier die beiden in einer Klasse zusammenfassen.

Zum anderen: Wo findet die Initialisierung des Timers statt? Der 
aufrufende Teil sollte dies nicht tun. Dann aber findet die 
Initialisierung in jedem Modul statt, und dann hast Du das gleiche 
Problem, nämlich "für jedes Modul fast das dasselbe" schreiben.

Bei mir ist die Timer-Konfiguration in einer separaten C++-Klasse. D. h. 
die Module rufen Methoden dieser Klasse auf.

> Allerdings wäre die
> Lösung mit "Ersetzen" z.B. TIM2_IRQHandler zu TIM3_IRQHandler besser.

Es gäbe noch eine weitere Methode, welche mittels "token pasting" den 
Namen der ISR mittels Präprozessor zusammenbaut. Das würde die 
Tipparbeit im statischen Fall reduzieren:
1
#define MODUL_A_TIMER 1
2
3
#define MODUL_A_TIMER_ISR CONCAT(TIM, MODUL_A_TIMER)_IRQHandler
4
5
void MODUL_A_TIMER_ISR(void) {
6
 ...
7
}

> Ich wüßte auch keine schnelle Möglichkeit noch die Pins dynamisch zu
> zuweisen.

Funktioniert analog. Geht über Zeiger oder aber ein Objekt wird an das 
Modul übergeben, und das Modul ruft uart.init() auf. Oder mit "token 
pasting" (im statischen Fall).

von Peter D. (peda)


Lesenswert?

Ich würde für jeden Timer getrennte Vektoren und getrennte Handler 
benutzen.
Das mag dann zwar ähnlicher Code sein, aber es müssen dann nicht ständig 
Pointer berechnet und aufgelöst werden. D.h. das Programm ist in jedem 
Falle schneller und der Code auch nicht nennenswert größer.


Peter

von Minti (Gast)


Lesenswert?

Roland H. schrieb:
> Ich sehe hier zwei Probleme. Zum ersten ist der "logische" Parameter
> TIMx in zwei tatsächliche aufgeteilt. D. h. es besteht die Gefahr, dass
> z. B. TIM1 und der Zeiger der ISR für TIM2 übergeben wird. Ich würde
> hier die beiden in einer Klasse zusammenfassen.

ja, danke für den Hinweis. Ich werde dann zu einem Struct 
zusammenführen, da ich in C programmiere.

Roland H. schrieb:
> Zum anderen: Wo findet die Initialisierung des Timers statt? Der
> aufrufende Teil sollte dies nicht tun. Dann aber findet die
> Initialisierung in jedem Modul statt, und dann hast Du das gleiche
> Problem, nämlich "für jedes Modul fast das dasselbe" schreiben.
>
> Bei mir ist die Timer-Konfiguration in einer separaten C++-Klasse. D. h.
> die Module rufen Methoden dieser Klasse auf.

ja, stimmt. Ich werde dann eine Funktion dafür schreiben.

Roland H. schrieb:
> Es gäbe noch eine weitere Methode, welche mittels "token pasting" den
> Namen der ISR mittels Präprozessor zusammenbaut.

Danke, wieder was Neues gelernt.

Peter Dannegger schrieb:
> Ich würde für jeden Timer getrennte Vektoren und getrennte Handler
> benutzen.

Leider habe ich dich nicht verstanden. Was meinst du mit Vektoren?

von Roland H. (batchman)


Lesenswert?

> ja, danke für den Hinweis. Ich werde dann zu einem Struct
> zusammenführen, da ich in C programmiere.

[...]

> ja, stimmt. Ich werde dann eine Funktion dafür schreiben.

struct + function = class ;-)

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.