问题描述
我想让我的班级采用一个函数“回调存根”,在存根之前和之后添加一些处理的东西,然后将整个事情作为回调传递给中断。不同的存根可以附加到类的不同实例。
我收到了如下评论中所示的错误。它的工作原理是获取一个函数指针,将其存储为一个属性,然后将其传递给 attachInterrupt 函数。但是,添加小序曲是行不通的。
在setInterrupt()中,我得到指向绑定函数的指针只能用于调用函数C/C++(300)
altSetInterrupt() 但是编译正常。
问题:是否有 * & 或 -> 的组合可以使这项工作?
已编辑:删除 Jerry Jeremiah 指出的一个愚蠢的错误
#include <Arduino.h>
#define MYPIN 1
// Declarations
class myClass {
public:
myClass();
void setInterrupt();
void altSetInterrupt();
void setCallBackStub( void (*cb)());
private:
void (*myCallBackStub)();
void myCallBack();
};
void mySillyCB();
// DeFinitions
myClass::myClass():
myCallBackStub(nullptr)
{}
void myClass::setCallBackStub( void (*cb)()) {
myCallBackStub = cb;
}
void myClass::myCallBack() {
Serial.println("Wohoo");
myCallBackStub();
}
void myClass::setInterrupt() {
attachInterrupt(MYPIN,this->myCallBack,HIGH); // a pointer to a bound function may only be used to call the functionC/C++(300)
}
void myClass::altSetInterrupt() {
attachInterrupt(MYPIN,this->myCallBackStub,HIGH); // no compiler error here
}
void mySillyCB() {
Serial.println("Called Back");
}
// Code
myClass theThing;
void setup() {
theThing.setCallBackStub( mySillyCB);
theThing.setInterrupt();
}
void loop() {}
解决方法
在 myCallBack() 中,我得到标识符 "myCallBackStub" is undefinedC/C++(20)
您已将此定义为自由函数而不是实现方法。你有:
void myCallBack() {
但你应该:
void myClass::myCallBack() {
因此,范围内没有这样的名称 myCallBackStub
。
在setInterrupt()中,我得到一个指向绑定函数的指针,该函数只能用于调用函数C/C++(300)
this->myCallBack
不是 C++ 中的一流值;它是绑定到特定实例 (*this
) 的成员函数。你唯一能做的就是调用它(比如 this->myCallBack()
)。
值得注意的是,您不能将其转换为函数指针。这是因为非静态成员函数需要一个实例来调用函数,而自由函数指针(attachInterrupt
接受的)没有额外的机制来传达这种状态。即使您对指向成员函数的指针 (&myClass::myCallBack
) 使用了正确的语法,这仍然不起作用,因为您无法在指向函数的指针和指向成员函数的指针之间进行转换。
您可以传递this->myCallBackStub
,因为这实际上是一个函数指针值。
由于 attachInterrupt()
状态无论如何都是全局的,如果不使您的机制全局化,您不会获得很多好处。您遇到的主要挑战是回调函数不接受任何参数,因此您甚至无法判断使用的是哪个引脚/模式。
要解决这个问题,您需要为每个引脚/模式组合使用不同的非成员回调函数,并且必须在编译时提前知道这些,因为您(大概)不想要进入运行时发射蹦床的复杂性。
这可以在编译时使用宏来完成。每次调用宏都应该做几件事:
- 声明一个全局回调函数。
- 声明全局回调函数对象的全局向量,全局回调函数将调用该向量。
- 可能调用
attachInterrupt()
,具体取决于这是否可以在静态初始化期间完成。
这是此概念的示例实现,可能需要进行调整才能在生产中使用。您可以使用以下内容创建一个类似于 interrupts.hpp
的标题:
#include <functional>
#include <vector>
// These types may need adjusting to match Arduino's types.
using Pin_t = int;
using Mode_t = int;
using InterruptCallback_t = std::function<void()>;
using InterruptCallbackList_t = std::vector<InterruptCallback_t>;
using RawInterruptCallback_t = void (*)();
#define DECLARE_INTERRUPT(pin,mode) DECLARE_INTERRUPT_(pin,mode)
#define DECLARE_INTERRUPT_(pin,mode) \
void add_interrupt_callback_##pin##_##mode (InterruptCallback_t);
DECLARE_INTERRUPT(0,0)
// And so on...
然后在实现文件interrupts.cpp
中:
#include <utility>
#include "interrupts.hpp"
static void apply_interrupt_callback(InterruptCallbackList_t const & cbList) {
for (const auto & cb : cbList) {
cb();
}
}
#define IMPLEMENT_INTERRUPT(pin,mode) IMPLEMENT_INTERRUPT_(pin,mode)
#define IMPLEMENT_INTERRUPT_(pin,mode) \
static InterruptCallbackList_t generated_interrupt_callbacks_##pin##_##mode ; \
static void generated_interrupt_callback_##pin##_##mode() { \
apply_interrupt_callback( generated_interrupt_callbacks_##pin##_##mode ); \
} \
void add_interrupt_callback_##pin##_##mode (InterruptCallback_t cb) { \
( generated_interrupt_callbacks_##pin##_##mode ).emplace_back(std::move(cb)); \
} \
static int generated_interrupt_registration_##pin##_##mode = ( \
attachInterrupt(pin,generated_interrupt_callback_##pin##_##mode,mode),\
0 \
);
IMPLEMENT_INTERRUPT(0,0)
// And so on...
这将为您提供的每个引脚/模式组合生成一组函数和全局变量。 interrupts.hpp
提供的接口将公开 add_interrupt_callback_N_M
,其中 N
和 M
分别是引脚和模式常量。你可以传递任何函数或函子(借助 std::function<void()>
给这个函数,它会在中断发生时被调用。
请注意,此示例代码中没有互斥锁。为简单起见,我假设一个单线程环境,其中不能抢先发生中断回调。如果这些假设不成立,则需要更改代码。