如何在Arduino中使用C ++成员函数作为中断处理程序?

时间:2019-05-31 05:40:59

标签: c++ arduino esp32

Arduino的attachInterrupt需要一个void(*)()类型的回调函数,但是我想将其传递给成员函数。由于其隐式this参数,我不能在这里使用C ++成员函数。

背景

我知道可以将C ++成员函数用作回调。例如,FreeRTOS的xTaskCreate(...)采用类型为void(*)(*)的回调函数。
isocpp.org has a nice FAQ关于将成员函数用作回调。

在此related question用户中,thiton写道:

  

大多数理智的回调库允许您将此void *参数传递给函数,以在其中包含用户定义的数据

也许Arduino库不是“理智的”?还是为了简化Arduino API而做出的设计决定?

它在... attachInterrupt里面

我正在为ESP32编程。 In the arduino-esp32 implementation of attachInterrupt,有一个名为__attachInterruptFunctionalArg(...)的函数似乎完全可以实现我想要的功能,但是由于它不是Arduino API的一部分,因此我很犹豫将其包含在一个供公众使用的项目中可能会破裂。

示例程序

// An attempt to summarize https://github.com/pierremolinaro/acan2517/issues/4

#include <stdio.h>
#include <stdint.h>
#include <functional>

#define IRAM_ATTR __attribute__((section(".iram1")))

// from `esp32-hal-gpio.c`
typedef void (*voidFuncPtrArg)(void*);
extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, void * arg, int intr_type, bool functional);

// from Arduino `FunctionalInterrupt.cpp`
void attachInterrupt(uint8_t pin, std::function<void(void)> intRoutine, int mode);

void IRAM_ATTR interruptFunctional(void* arg);

// from Arduino `FunctionalInterrupt.h`
struct InterruptArgStructure {
    std::function<void(void)> interruptFunction;
};

// from ACAN2517
class ACAN2517
{
    public: ACAN2517 (const int interrupt_pin);
    public: void begin (void (* inInterruptServiceRoutine) (void));
    public: void begin_functional (void (* inInterruptServiceRoutine) (void *), void *);
    public: void isr(void);
    private: const int interrupt_pin;
};

ACAN2517::ACAN2517 (const int interrupt_pin):
    interrupt_pin(interrupt_pin)
{};

#define FALLING 0

// This won't work with a member function
void ACAN2517::begin (void (* inInterruptServiceRoutine) (void)) {
    attachInterrupt(interrupt_pin, inInterruptServiceRoutine, FALLING);
}

// This will, but is prone to breakage when the Arduino internals change
void ACAN2517::begin_functional (void (* inInterruptServiceRoutine) (void *), void *arg)
{
    __attachInterruptFunctionalArg(interrupt_pin, inInterruptServiceRoutine, arg, FALLING, true);
}

void ACAN2517::isr(void)
{
    printf("fhtagn");
}

//===
// User code begin
//===

#define N_DRIVERS 3

ACAN2517 g_driver(23);  // Initializing a driver instance statically
ACAN2517 *drivers[N_DRIVERS];

void call_ACAN_isr(void *arg)
{
    ACAN2517 *driver = (ACAN2517 *)arg;
    driver->isr();
}

int main()
{
    g_driver.begin( []{g_driver.isr();} ); // No problem

    for (int i = 0; i < N_DRIVERS; i++)
    {
        drivers[i] = &ACAN2517(i);
        drivers[i]->begin( []{drivers[i]->isr();} );
        // ERROR
        // static void lambda []void ()->void::_FUN()
        // an enclosing-function local variable cannot be referenced in a lambda body unless it is in the capture list
    }

    for (int i = 0; i < N_DRIVERS; i++)
    {
        drivers[i] = &ACAN2517(i);
        drivers[i]->begin( [i]{drivers[i]->isr();} );
        // ERROR
        // no suitable conversion function from "lambda []void ()->void" to "void (*)()" exists
    }

    for (int i = 0; i < N_DRIVERS; i++)
    {
        drivers[i] = &ACAN2517(i);
        ACAN2517 *driver = drivers[i];
        drivers[i]->begin_functional( [driver]{driver->isr();}, driver);
        // Not sure how to get this to work in a lambda...
    }

    for (int i = 0; i < N_DRIVERS; i++)
    {
        drivers[i] = &ACAN2517(i);
        ACAN2517 *driver = drivers[i];
        drivers[i]->begin_functional( call_ACAN_isr, driver);
        // OK
    }
}

//===
// User code end
//===

// from esp32-hal-gpio.c
extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, void * arg, int intr_type, bool functional)
{
    // ...
}

// from Arduino `FunctionalInterrupt.cpp`
void attachInterrupt(uint8_t pin, std::function<void(void)> intRoutine, int mode)
{
    // use the local interrupt routine which takes the ArgStructure as argument
    __attachInterruptFunctionalArg (pin, (voidFuncPtrArg)interruptFunctional, new InterruptArgStructure{intRoutine}, mode, true);
}

void IRAM_ATTR interruptFunctional(void* arg)
{
    InterruptArgStructure* localArg = (InterruptArgStructure*)arg;
    if (localArg->interruptFunction)
    {
      localArg->interruptFunction();
    }
}

1 个答案:

答案 0 :(得分:0)

  

由于这里隐含了this参数,因此我不能在这里使用C ++成员函数。

是的,这就是问题所在,如果您的API没有提供使您存储一些其他数据(例如this指针)的东西,那么没有额外的代码就无法解决这个问题。

您可以做的只是: 编写自己的wrapper并将回调注册到原始处理程序。但这会产生另一个间接性,从而增加延迟。

另一种方法不是那么简单,但是慢了一点: 编写您的own interrupt handler和回调注册。有了arduino库的原始资源后,您只需替换attachInterrupt函数周围的内容即可。

对不起,但是没有任何其他软件,没有办法为this生成数据存储。