STM32 Interrupt Handler in statischer Klasse

OP #6347729
Lesenswert?

Wie teile ich meiner lieben CubeIDE denn mit, dass der SPI Interrupt 
eine bestimmte Funktion einer Klasseninstanz aufrufen soll?

Mein erfolgloser Versuch bisher:

main.cpp
1
int main(void) {
2
    //...
3
    System system;
4
    //...
5
}

system.cpp
1
System::System(){
2
    //...
3
    HAL_SPI_Receive_IT(&hspi1, spi_interface_buffer, 1);
4
}
5
//...
6
extern "C" void System::Callback_SPI_Interface() {
7
  uint32_t dummy = 23;
8
  dummy++;
9
}

stm32l4xx_it.c
1
extern (*Callback_SPI_Interface)(void);
2
//...
3
void SPI1_IRQHandler(void)
4
{
5
    /* USER CODE BEGIN SPI1_IRQn 0 */
6
    Callback_SPI_Interface();
7
    /* USER CODE END SPI1_IRQn 0 */
8
    HAL_SPI_IRQHandler(&hspi1);
9
    /* USER CODE BEGIN SPI1_IRQn 1 */
10
    /* USER CODE END SPI1_IRQn 1 */
11
}
OP #6347748
Lesenswert?

Damit müsste die Funktion über ihren Pointer global erreichbar sein.
1
void (*Callback_ptr)(void);
2

3
void main(void){
4
  System system;
5
  Callback_ptr = &(system.Callback_SPI_Interface);
6
}

Funktioniert aber nicht, da folgender Fehler auftritt:
cannot convert 'void (System::*)()' to 'void (*)()' in assignment
Gast #6347814
Lesenswert?

Die Memberfunktion kann in der Klassendefinition als 'static' deklariert 
werden. Dann sind in der Funktion aber keine Zugriffe auf 
Membervariablen möglich weil der this pointer fehlt. Den kann die ISR 
auch nicht mitgeben, dafür ist dann eine Hilfsfunktion nötig.
OP #6347851
Lesenswert?

Johannes S. schrieb:
> Die Memberfunktion kann in der Klassendefinition als 'static' deklariert
> werden. Dann sind in der Funktion aber keine Zugriffe auf
> Membervariablen möglich weil der this pointer fehlt. Den kann die ISR
> auch nicht mitgeben, dafür ist dann eine Hilfsfunktion nötig.

Ok, dass funktioniert jetzt. Also es wird fehlerfrei kompiliert. Hier 
der aktuelle Code:

main.cpp
1
// global visible pointer to actual static member function of class
2
void (*Callback_SPI_Interface_Ptr)(void) = &(System::Callback_SPI_Interface);
3
//...
4
int main(void) {
5
    //...
6
    // initialisation of System object
7
    System system;
8
    //...
9
}


system.h
1
class System {
2
private:
3
  //...
4
public:
5
  System();
6
  // set function as a static member
7
  static void Callback_SPI_Interface();
8
};
system.cpp
1
System::System(){
2
    //...
3
    // enable receiving interrupt?
4
    HAL_SPI_Receive_IT(&hspi1, spi_interface_buffer, 1);
5
}
6
//...
7
// make funktion callable from C source code
8
extern "C" void System::Callback_SPI_Interface() {
9
  uint32_t dummy = 99;
10
}


stm32l4xx_it.c
1
// pointer to static member function is declared outside of this file
2
extern (*Callback_SPI_Interface_Ptr)(void);
3
//...
4
void SPI1_IRQHandler(void)
5
{
6
    /* USER CODE BEGIN SPI1_IRQn 0 */
7
    Callback_SPI_Interface_Ptr();
8
    /* USER CODE END SPI1_IRQn 0 */
9
    HAL_SPI_IRQHandler(&hspi1);
10
    /* USER CODE BEGIN SPI1_IRQn 1 */
11
    /* USER CODE END SPI1_IRQn 1 */
12
}

Eine Frage noch. Kann man das so machen. Oder gibt es da elegantere 
Möglichkeiten. Ich bin immer wieder am überlegen, ob nicht C doch 
sinnvoller wäre... Sehr unschlüssig...
#6347854
Lesenswert?

Anonymous U. schrieb:
> Ich bin immer wieder am überlegen, ob nicht C doch
> sinnvoller wäre... Sehr unschlüssig...

Ich würde mal sagen wenn es schon an einem ISR scheitert, ist C 
vielleicht doch die bessere Lösung. Schlussendlich kannst du den Komfort 
von C++ eh nicht richtig genießen und musst dir wieder um jede 
Speicherallokation Gedanken machen. Das kannst du auch in C haben. Es 
gibt sicher Projekte, wo der zusätzliche Aufwand Sinn ergibt, aber oft 
ist es auch sinnvoll, einfach beim Bewährten zu bleiben.
OP #6348289
Lesenswert?

oooops schrieb:
> Anonymous U. schrieb:
>> Eine Frage noch. Kann man das so machen. Oder gibt es da elegantere
>> Möglichkeiten.
>
> Warum krampfhaft in der main() eine Klasseninstanz anlegen und
> einen globalen Pointer darauf? Gibt es dafür triftige Gründe?

Meine Klassen benötigen eine bereits initialisierte HAL. Ich könnte zwar 
eine init()-Funktion in die Klasse integrieren, aber wenn dieser Code im 
Konstruktor steht, finde ich es schöner.

Felix U. schrieb:
> Es
> gibt sicher Projekte, wo der zusätzliche Aufwand Sinn ergibt, aber oft
> ist es auch sinnvoll, einfach beim Bewährten zu bleiben.

Ich war lange am überlegen, ob ich C oder C++ verwenden sollte. Ich bin 
da eher erstmal zögerlich Neuem gegenüber. Aber dann dachte ich zurück 
an meine Anfänge mit ASM und wie ich mich erst gegen C gesträubt habe 
:-) So here we go..
Gast #6348355
Lesenswert?

ich benutze mittlerweile auch viel C++ und sehe darin keinen Nachteil. 
Dazu Mbed-os, das ebenfalls sehr konsequent C++ nutzt und auch das API 
bietet alles in Klassen an.
Die Interrupts kann man da auch bequem im Usercode abarbeiten:
Im RAM ist ein Bereich für die Kopie der Interrupt Vektoren reserviert. 
Die ISR kann fest in die Tabelle wenn sie zur Kompilierzeit bekannt ist, 
kann aber zur Laufzeit einfach mit
1
void NVIC_SetVector(IRQn_Type IRQn, uint32_t vector);
umgebogen werden.

Für den Fall das man eine Methode als ISR nutzen möchte gibt es einen 
Helfer names CThunk. Das ist eine Trampolin Funktion die sich einen 
Callback merkt und als ISR arbeitet. Kostet zusätzliche indirekte calls, 
aber die nehme ich für den Luxus in Kauf. Dafür spare ich mir die lange 
universelle Behandlung in der HAL ISR.
Der Vorteil ist, das ich meine Komponente schön kapseln kann und die bei 
Bedarf den ISR Kram implizit behandelt. Es muss keine ISR im Core vorab 
reserviert werden (Arduino) oder eine unübersichtliche 'ich kann alle 
Ints' (HAL) implementiert werden.

Benutzt habe ich das z.B. in einer Timerklasse. Der gebe ich den 
gewünschten Callback mit und intern wird die Interruptbehandlung 
gemacht.

Auszugsweise sieht das so aus, Timerkram weggelassen:
1
class HWTimer {
2
public:
3
    HWTimer(int id, chrono::microseconds  period, Callback<void()> cb, bool onePulseMode);
4

5
    ~HWTimer();
6
    void start(chrono::microseconds period = 0us);
7
    void stop();
8

9
private:
10
    void callTimerFn();
11

12
private:
13
    int _id;
14
    bool _onePulseMode;         
15
    Callback<void()> _cb;       // user callback on timeout
16
    CThunk<HWTimer> _irq;       // helper for setting static irq member function
17
    void *_pConfig;             // generic pointer, used for implementation dependent structures
18
};
19

20

21
HWTimer::HWTimer(int id, chrono::microseconds us_period, Callback<void()> cb, bool onePulseMode) :
22
    _id(id),
23
    _onePulseMode(onePulseMode),
24
    _cb(cb),
25
    _irq(this)
26
{
27
    // set IRQ ISR, use CThunk to handle member function call from static
28
     _irq.callback(&HWTimer::callTimerFn);
29

30
    // activate IRQ in NVIC
31
    NVIC_SetVector(TimIRQTab[id], (uint32_t)_irq);
32
    NVIC_EnableIRQ(TimIRQTab[id]);
33
}
34

35
void HWTimer::callTimerFn()
36
{
37
    if (__HAL_TIM_GET_FLAG((TIM_HandleTypeDef*)_pConfig, TIM_FLAG_UPDATE) == SET) {
38
        __HAL_TIM_CLEAR_FLAG((TIM_HandleTypeDef*)_pConfig, TIM_FLAG_UPDATE);
39

40
        _cb();
41
    }
42
}

Den CThunk könnte man auch aus dem OS rausoperieren, der setzt nichts OS 
spezifisches vorraus. Aber da habe ich keine Ambitionen, das OS hat ja 
noch mehr zu bieten das ich benutzen möchte.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren