c 函数的 C++ 回调

C++ callback for c function

是的,,,我知道有很多关于这个的话题,我想我已经阅读了其中的大部分,但是要么我不理解答案,要么我无法适应它们我的 "case".

请注意,我的背景是电子设计,而不是软件设计,所以对于你们中的某些人来说,我的问题可能看起来很愚蠢,但是...我被卡住了。

我设计了一块用于物联网的 pcb 板。它基于 ESP32 模块。我有 5 个按钮连接到 ESP。 ESP32-IDF 对我来说太复杂了,所以我尝试使用 Ardiuno 框架。现在开始变得复杂了。

为了检测和去抖按钮,我创建了一个名为 Button 的 C++ class。下面可以看到骨架。

class Button {
..
..

private:
  void memberCallback() {
    ...
  }



public:
  Button(const uint8_t gpio ) {
    ..
    attachInterrup(digitalPinToInterrupt(gpio), memberCallback, FALLING);
    ..
  }

..

}

我还没有找到任何方法来定义 "memberCallback" 而不会导致编译错误或根本不工作。

这一定是个常见问题,请提出解决方案:)

编辑。 看来我表达得不够清楚,抱歉。 - 我知道如果我将 memberCallback 设为静态,它至少会编译。问题是我计划使用 5 个这样的实例。静态回调意味着所有实例都将 运行 相同的代码。 5 个实例意味着 5 个不同的中断。我如何识别它们。

使您的 memberCallback 函数静态化。将您的代码更改为:

class Button {
..
..

private:
  static void memberCallback() {
    ...
  }



public:
  Button(const uint8_t gpio ) {
    ..
    attachInterrup(digitalPinToInterrupt(gpio), memberCallback, FALLING);
    ..
  }

..

}

当您查看 attachInterrupt 的文档时,它说 ISR 是一个不带参数的函数。它的类型是 void(*)() (或者很可能实际上是 extern "C" void(*)() 但我还没有对确切的类型进行足够彻底的研究)。你的 memberCallback 看起来 好像它不带参数但实际上不是这样:在 [non-static] 成员函数中你有一个隐式参数: this,指向需要确定使用哪个Button对象的对象的指针。 memberCallback 的类型是 void (Button::*)(),与 void(*)() 不兼容。这意味着您将不能直接使用[非static]成员函数。

正如其他答案中所建议的那样,您可以使用 static 成员函数。但是,这有两个问题:

  1. 该函数只能访问 static 个成员(函数和变量)。
  2. 函数不会是 extern"C"`。

您每 Button 需要注册一个函数。对于一个看起来像这样的 Button

class Button
{
    // ....
public:
    void memberCallback();
    Button(int gpio, Button*& ptr, void(*isr)()) {
        ptr = this;
        attachInterrupt(digitalPinToInterrupt(gpio), isr, FALLING);
    }
};

Button* button1;
extern "C" void button1ISR() {
    button1ISR->memberCallback();
}

// create you Button somewhere, e.g.:
int main() {
    Button b1(gpio, button1, button1ISR);
    // ...
}

ESP32 Arduino Core 在

上有一个如何准确执行此操作的示例

https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/GPIO/FunctionalInterrupt

我将在此处引用其中的代码:

#include <Arduino.h>
#include <FunctionalInterrupt.h>

#define BUTTON1 16
#define BUTTON2 17

class Button
{
public:
    Button(uint8_t reqPin) : PIN(reqPin){
        pinMode(PIN, INPUT_PULLUP);
        attachInterrupt(PIN, std::bind(&Button::isr,this), FALLING);
    };
    ~Button() {
        detachInterrupt(PIN);
    }

    void IRAM_ATTR isr() {
        numberKeyPresses += 1;
        pressed = true;
    }

private:
    const uint8_t PIN;
    volatile uint32_t numberKeyPresses;
    volatile bool pressed;
}

重要的是:

  • #include <FunctionalInterrupt.h> - 这让你 std::bind() 和一个稍微不同的 attachInterrupt() 声明使这个工作
  • 使用 std::bind(&Button::isr,this) 将中断处理程序绑定到对象。
  • 确保将中断处理程序声明为 IRAM_ATTR 以确保它的代码将保持加载到 RAM 中。
  • 确保将要在中断处理程序中修改的任何实例变量声明为 volatile,以便 C++ 编译器知道它们在没有警告的情况下进行了更改。

std::bind()是一个标准的C++库函数;有 documentation about it online.

也就是说,我很关心你打算在中断处理程序中做什么。

中断处理程序需要 运行 很短的时间,并且他们需要非常小心地调用其他函数并确保数据结构处于不一致状态。除非您在中断处理程序之外锁定中断(您应该尽可能少地这样做),否则数据结构很容易处于不一致状态。

Arduino Core 中的示例很好 - 它只是更改了几个变量。不止于此 - 调用 Serial.println() 之类的函数或分配内存或创建对象并不安全。