Forum: Mikrocontroller und Digitale Elektronik esp32 isr in Klasse


von Reiner W. (reiner_w)


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?

: Bearbeitet durch User
von Foobar (asdfasd)


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?

von Andreas M. (amesser)


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.

von Foobar (asdfasd)


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.

: Bearbeitet durch User
von Reiner W. (reiner_w)


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.

von Foobar (asdfasd)


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

von Reiner W. (reiner_w)


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.

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.