在開發 ESP32 的應用程式時, 有時候會需要取得指向函式的指位器, 像是要設定中斷的話, 就必須提供一個函式作為中斷觸發時的處理常式, 如果採用一般的函式, 只要直接寫函式名稱就可以了, 例如這個程式:
int count = 0;
void ARDUINO_ISR_ATTR isr(void) {
count++;
}
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
pinMode(26, INPUT_PULLUP);
attachInterrupt(26, isr, RISING);
}
void loop() {
// put your main code here, to run repeatedly:
delay(200); // this speeds up the simulation
Serial.println(count);
}
但如果你想要使用類別中的成員函式當成中斷處理常式, 直接這樣寫會出錯:
class Button {
public:
int count = 0;
void ARDUINO_ISR_ATTR isr(void) {
count++;
}
};
Button b;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
pinMode(26, INPUT_PULLUP);
attachInterrupt(26, Button::isr, RISING);
}
void loop() {
// put your main code here, to run repeatedly:
delay(200); // this speeds up the simulation
Serial.println(b.count);
}
它會說你用錯非靜態的類別成員函式:
sketch.ino: In function 'void setup()':
sketch.ino:17:31: error: invalid use of non-static member function 'void Button::isr()'
attachInterrupt(26, Button::isr, RISING);
^~~
如果改成透過實例, 也一樣會出錯:
attachInterrupt(26, b.isr, RISING);
如果改成這樣想要取得成員函式的位址:
attachInterrupt(26, &b.isr, RISING);
它會告訴你不允許取得實例的成員函式位址, 也沒辦法幫你把成員函式轉換成一般函式:
sketch.ino: In function 'void setup()':
sketch.ino:17:26: error: ISO C++ forbids taking the address of a bound member function to form a pointer to member function. Say '&Button::isr' [-fpermissive]
attachInterrupt(26, &b.isr, RISING);
^~~
sketch.ino:17:37: error: cannot convert 'void (Button::*)()' to 'void (*)()'
attachInterrupt(26, &b.isr, RISING);
^
實際上成員函式就跟一般函式是一樣的, 只不過叫用成員函式的時候, C++ 編譯器會幫你把綁定的實例傳入成為 this 指向的物件。如果你寫過 Python, 對這一點就會很有感。
為了解決這個問題, C++ 提供有 std::bind
函式, 只要傳入類別中成員函式的位址, 以及要綁定在哪一個物件上叫用它, 它就會幫你建立一個 std::function
類別的物件, 並且紀錄成員函式的位址與物件, 當你叫用這個 std:function
類別實例時, 它會幫你轉去叫用成員函式, 並且把記錄的物件傳入。
ESP32 就搭配這個機制, 另外在 FunctionalInterrupt.h
檔中提供了相對應版本的 attachInterrupt
函式, 剛剛的程式就可以改寫如下的版本:
#include<FunctionalInterrupt.h>
class Button {
public:
int count = 0;
void ARDUINO_ISR_ATTR isr(void) {
count++;
}
};
Button b;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
pinMode(26, INPUT_PULLUP);
attachInterrupt(26, std::bind(&Button::isr, &b), RISING);
}
void loop() {
// put your main code here, to run repeatedly:
delay(200); // this speeds up the simulation
Serial.println(b.count);
}
要注意:
一定要引入
FunctionalInterrupt.h
, 否則會引用到原始版本的attachInterrupt
函式, 這個版本只接受voidFuncPtr
型別的函式, 它實際上是void (*voidFuncPtr)(void);
, 不接受std::function
類別實例,FunctionalInterrupt.h
內的版本 才能接受std::function
類別實例。叫用
bind
時記得傳入物件的位址, 否則會變成複製物件, 如果在中斷處理函式中會修改物件的成員變數, 就會變成修改到副本, 而不是你傳入的那個物件。
Top comments (0)