esp32 isr in Klasse

OP #7419864
Lesenswert?

Ich versuche mich gerade in cpp auf einem esp32 einzuarbeiten. Dabei habe ich ein generelles Problem bei der Konvertierung in eine Klasse. Speziell macht hier die isr Probleme.

Hier ein kleines Testprogramm für esp32, kompiliert mit dem Arduino Framework.

1
#include <Arduino.h>
2
#include <esp_timer.h>  // for meassure time
3
#include <stdio.h>
4

5
#include "driver/gpio.h"
6
#include "freertos/FreeRTOS.h"
7
#include "freertos/queue.h"
8
#include "freertos/task.h"
9

10
const gpio_num_t INPUT_PIN = (gpio_num_t)15;
11
const gpio_num_t LED_PIN = (gpio_num_t)2;
12

13
int state = 0;
14
xQueueHandle interputQueue;
15

16
struct isr_data {
17
    int pinNumber;
18
    int64_t isr_time;
19
};
20

21
static void IRAM_ATTR gpio_interrupt_handler(void *args) {
22
    int pinNumber = (int)args;
23
    
24
    isr_data data = {.pinNumber = pinNumber, .isr_time = esp_timer_get_time()};
25

26
    xQueueSendFromISR(interputQueue, &data, NULL);
27
}
28

29
void LED_Control_Task(void *params) {
30
    int pinNumber, count = 0;
31
    isr_data data = {}; // buffer for received struct
32
    static int64_t last_isr_time = 0;
33
    while (true) {
34
        // worker
35
        if (xQueueReceive(interputQueue, &data, portMAX_DELAY)) {
36
            printf("TimeDiff %lld ms GPIO %d was pressed %d times. The state is %d\n",
37
                   (data.isr_time - last_isr_time) / 1000,
38
                   data.pinNumber, 
39
                   count++, 
40
                   gpio_get_level(INPUT_PIN));
41

42
            last_isr_time = data.isr_time;
43

44
            gpio_set_level(LED_PIN, !gpio_get_level(LED_PIN));
45
        }
46
    }
47
}
48

49
void setup() {
50
    pinMode(LED_PIN, OUTPUT);
51
   
52
    gpio_pad_select_gpio(INPUT_PIN);
53
    gpio_set_direction(INPUT_PIN, GPIO_MODE_INPUT);
54
    gpio_pulldown_dis(INPUT_PIN);
55
    gpio_pullup_en(INPUT_PIN);
56
    gpio_set_intr_type(INPUT_PIN, GPIO_INTR_POSEDGE);
57

58
    struct isr_data data = {.pinNumber = INPUT_PIN, .isr_time = 0};
59

60
    interputQueue = xQueueCreate(10, sizeof(isr_data));
61
    xTaskCreate(LED_Control_Task, "LED_Control_Task", 2048, NULL, 1, NULL);
62
    gpio_install_isr_service(0);
63
    gpio_isr_handler_add(INPUT_PIN, gpio_interrupt_handler,(void*) INPUT_PIN);
64
}
65

66
void loop() {}

Das Programm arbeitet wie gewünscht. Es ist nur für mich ein Beispiel für das Zusammenwirken von isr->Queue->ControlTask.

Nun will ich die komplette Funktionalität in eine Klasse auslagern. Und genau das bekomme ich nicht hin. Das Problem ist offenbar die isr.

Mein erster Ansatz war:

1
#include <Arduino.h>
2
#include <esp_timer.h>  // for meassure time
3
#include <stdio.h>
4

5
#include "driver/gpio.h"
6
#include "freertos/FreeRTOS.h"
7
#include "freertos/queue.h"
8
#include "freertos/task.h"
9

10
class GpioControl {
11
   private:
12
    gpio_num_t inputPin;
13
    gpio_num_t ledPin;
14
    xQueueHandle interputQueue;
15
    TaskHandle_t xLedTaskHandle;
16
    struct isr_data {
17
        int pinNumber;
18
        int64_t isr_time;
19
    };
20

21
    static void IRAM_ATTR gpio_interrupt_handler(void *args);
22
    static void LED_Control_Task(void *params);
23

24
   public:
25
    GpioControl(gpio_num_t inputPin, gpio_num_t ledPin);
26
    int get_pin_state();
27
};
28

29
GpioControl::GpioControl(gpio_num_t inputPin, gpio_num_t ledPin) {
30
    this->inputPin = inputPin;
31
    this->ledPin = ledPin;
32

33
    pinMode(ledPin, OUTPUT);
34

35
    gpio_pad_select_gpio(inputPin);
36
    gpio_set_direction(inputPin, GPIO_MODE_INPUT);
37
    gpio_pulldown_en(inputPin);
38
    gpio_pullup_dis(inputPin);
39
    gpio_set_intr_type(inputPin, GPIO_INTR_POSEDGE);
40

41
    interputQueue = xQueueCreate(10, sizeof(isr_data));
42
    xTaskCreate(LED_Control_Task, "LED_Control_Task", 2048, this, 1, &xLedTaskHandle);
43
    gpio_install_isr_service(0);
44
    gpio_isr_handler_add(inputPin, gpio_interrupt_handler, (void *)inputPin);
45
}
46

47
int GpioControl::get_pin_state() {
48
    return gpio_get_level(inputPin);
49
}
50

51
void IRAM_ATTR GpioControl::gpio_interrupt_handler(void *args) {
52
    GpioControl *instance = (GpioControl *)args;
53
    int pinNumber = instance->inputPin;
54

55
    isr_data data = {.pinNumber = pinNumber, .isr_time = esp_timer_get_time()};
56
    xQueueSendFromISR(instance->interputQueue, &data, NULL);
57
}
58

59
void GpioControl::LED_Control_Task(void *params) {
60
    GpioControl *instance = (GpioControl *)params;
61
    int pinNumber, count = 0;
62
    isr_data data = {};  // buffer for received struct
63
    static int64_t last_isr_time = 0;
64
    while (true) {
65
        if (xQueueReceive(instance->interputQueue, &data, portMAX_DELAY)) {
66
            printf("TimeDiff %lld ms GPIO %d was pressed %d times. The state is %d\n",
67
                   (data.isr_time - last_isr_time) / 1000,
68
                   data.pinNumber,
69
                   count++,
70
                   gpio_get_level(instance->inputPin));
71

72
            last_isr_time = data.isr_time;
73

74
            gpio_set_level(instance->ledPin, !gpio_get_level(instance->ledPin));
75
        }
76
    }
77
}
78

79
void setup() {
80
    GpioControl gpioControl((gpio_num_t)15, (gpio_num_t)2);
81
}
82

83
void loop() {

Läßt sich zwar problemlos kompilieren. Aber die isr wird offenbar nicht aufgerufen. ChatGPT hat zwar einiges vorgeschlagen (u.a. eine Wrapperfunktion für die isr) aber eine Lösung fand er auch nicht. Nach einigen Stunden habe ich dann entnervt aufgeben;-) Hat jemand einen Rat, wo es hier klemmen könnte?

#7419866
Lesenswert?

ChatGPT hat zwar einiges vorgeschlagen (u.a. eine Wrapperfunktion für die isr) aber eine Lösung fand er auch nicht.

Das hat er, du siehst sie nur nicht. Ein Interrupthandler ist eine globale Funktion. Soll diese eine Memberfunktion aufrufen, braucht sie eine Instanz der Klasse - eine globale Referenz/Pointer/Objekt. Irgendein dynamisches Object auf dem Stack reicht nicht. Woher soll der Interrupthandler wissen, welche Instanz er nutzen soll?

#7419880
Lesenswert?

gpio_isr_handler_add musst Du ebenfalls "this" als letztes Argument geben. Außerdem hat der Vorposter Recht was die Queue betrifft. Diese speichert nur Pointer. Da kann man nicht die Adresse einer Struktur vom Stack reinstecken, "isr_data data" ist genau so lange definiert, wie die ISR Funktion läuft, danach nicht mehr. Das ist bereits im C-Code falsch.

#7419902
Lesenswert?

gpio_isr_handler_add

Upps, das hatte ich garnicht gesehen. Ich war davon ausgegangen, dass beim esp32 Interrupthandler ähnlich installiert werden wie bei anderen CPUs, per globalem Namen und da ein solcher im o.g. Code nicht vorhanden war, auch keiner installiert wurde.

Mit dieser Funktion ist meinen vorheriger Post ziemlicher Quatsch.

Sorry.

OP #7419970
Lesenswert?

Foobar schrieb:

Das hat er, du siehst sie nur nicht. Ein Interrupthandler ist eine globale Funktion. Soll diese eine Memberfunktion aufrufen, braucht sie eine Instanz der Klasse - eine globale Referenz/Pointer/Objekt. Irgendein dynamisches Object auf dem Stack reicht nicht.

Naja, jedenfalls hat er mir das prinzipielle Problem korrekt beschrieben, so wie ihr auch schon angemerkt habt. Allerdings was sein korrigierter Code nicht compilierbar und über die Hürde konnte er auch nicht springen.

Hier sein Vorschlag:

1
#include <Arduino.h>
2
#include <esp_timer.h>  // for measuring time
3
#include <stdio.h>
4

5
#include "driver/gpio.h"
6
#include "freertos/FreeRTOS.h"
7
#include "freertos/queue.h"
8
#include "freertos/task.h"
9

10
const gpio_num_t BUTTON_PIN = GPIO_NUM_15;
11
const gpio_num_t LED_PIN = GPIO_NUM_2;
12

13
xQueueHandle interruptQueue;
14

15
class Button {
16
private:
17
  gpio_num_t pin;
18
  int count;
19

20
public:
21
  Button(gpio_num_t buttonPin) : pin(buttonPin), count(0) {}
22

23
  void begin() {
24
    gpio_pad_select_gpio(pin);
25
    gpio_set_direction(pin, GPIO_MODE_INPUT);
26
    gpio_pulldown_dis(pin);
27
    gpio_pullup_en(pin);
28
    gpio_set_intr_type(pin, GPIO_INTR_POSEDGE);
29
    gpio_install_isr_service(0);
30
    gpio_isr_handler_add(pin, isrHandlerWrapper, this);
31
  }
32

33
  static void IRAM_ATTR isrHandlerWrapper(void *args) {
34
    Button *button = static_cast<Button *>(args);
35
    button->isrHandler();
36
  }
37

38
  void IRAM_ATTR isrHandler() {
39
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
40
    xQueueSendFromISR(interruptQueue, &pin, &xHigherPriorityTaskWoken);
41
    portYIELD_FROM_ISR();
42
  }
43
};
44

45
Button button(BUTTON_PIN);
46

47
void ButtonTask(void *params) {
48
  gpio_num_t pin;
49
  while (true) {
50
    if (xQueueReceive(interruptQueue, &pin, portMAX_DELAY)) {
51
      printf("Button pressed on pin %d\n", pin);
52
      digitalWrite(LED_PIN, !digitalRead(LED_PIN));
53
    }
54
  }
55
}
56

57
void setup() {
58
  pinMode(LED_PIN, OUTPUT);
59

60
  interruptQueue = xQueueCreate(10, sizeof(gpio_num_t));
61

62
  button.begin();
63

64
  xTaskCreate(ButtonTask, "ButtonTask", 2048, NULL, 1, NULL);
65
}
66

67
void loop() {}

Der Linker gibt dann:

1
.pio/build/esp32dev/src/esp32_gpio_interrupt.cpp.o: in function `Button::isrHandler()':
2
/Volumes/mydocs/PlatformIO/Projects/ESP32_RTOS_ISR_class/src/esp32_gpio_interrupt.cpp:40:(.iram1.28[Button::isrHandlerWrapper(void*)]+0x3): dangerous relocation: l32r: literal placed after use: .literal._ZN6Button17isrHandlerWrapperEPv

aus. Und da steh ich wieder am Anfang.

#7420385
Lesenswert?

Dieser dubiose Fehler scheint durch die CPU-Archtektur bedingt (also kein Code-Fehler). Hier ist es etwas ausführlicher: https://stackoverflow.com/questions/19532826/what-does-a-dangerous-relocation-error-mean

Ich würd mal versuchen, die Wrapper-Funktion anders zu plazieren, z.B. als globale Funktion außerhalb der Klasse. Oder evtl begin() mit dem handler_add auch IRAM_ATTR zu machen (nur damit sie im gleichen segment landen). Solche Fehler sind häßlich und arten oft in viel Rumprobiererei aus ...

OP #7420397
Lesenswert?

Foobar schrieb:

Dieser dubiose Fehler scheint durch die CPU-Archtektur bedingt (also kein Code-Fehler). Hier ist es etwas ausführlicher: https://stackoverflow.com/questions/19532826/what-does-a-dangerous-relocation-error-mean

Ich würd mal versuchen, die Wrapper-Funktion anders zu plazieren, z.B. als globale Funktion außerhalb der Klasse. Oder evtl begin() mit dem handler_add auch IRAM_ATTR zu machen (nur damit sie im gleichen segment landen). Solche Fehler sind häßlich und arten oft in viel Rumprobiererei aus ...

Danke für die hilfreichen Tipps!

handler_add auch IRAM_ATTR zu machen

Daran habe ich noch gar nicht gedacht, das werde ich testen. Außerdem will ich mal schauen, ob espressiv irgendwo in den Beispielen der esp-idf-cxx brauchbare Hinweise versteckt hat. Zum Glück komme ich in meinem Projekt auch ohne Klasse aus. Aber irgendwie wurmt es mich schon, dass das nicht hinzubekommen sein soll.

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