Arduino类中attachInterrupt无法关联按钮按下函数的问题
Arduino类中使用attachInterrupt的问题解决
问题根源
Arduino的attachInterrupt只接受无参数、无返回值的全局函数或静态成员函数,而类的非静态成员函数会隐含一个this指针(用于访问对象的成员变量和方法),函数签名不匹配,所以无法直接作为中断服务函数(ISR)传入。
另外还有一个隐藏风险:你原代码的中断函数里调用了delay()、Serial.println()这类函数,这在ISR里是绝对禁止的——ISR要尽可能简短,不能阻塞,也不能调用依赖中断的函数(比如Serial依赖UART中断,delay依赖定时器中断),否则会导致系统异常。
解决方案
用静态成员函数作为中转ISR,配合一个指向Game对象的静态指针,让静态函数能访问到对象的成员;同时把耗时/非ISR安全的操作移到loop()中处理,用标志位触发事件。
修改后的完整代码
#include <Arduino.h> class Game { private: bool started; bool over; int winner; int reactionTime; unsigned long startTime; int button1Pin; int button2Pin; int led1Pin; int led2Pin; int redPin; int greenPin; int bluePin; int buzzerPin; int player1Score; int player2Score; // 新增:标记玩家按下按钮的事件标志位 bool player1Pressed; bool player2Pressed; // 新增:静态指针,指向当前Game对象 static Game* instance; // 原中断逻辑改为私有成员函数,仅处理状态和标志位 void handlePlayer1Press() { if (!over && started) { over = true; winner = 1; reactionTime = millis() - startTime; player1Pressed = true; player1Score++; } } void handlePlayer2Press() { if (!over && started) { over = true; winner = 2; reactionTime = millis() - startTime; player2Pressed = true; player2Score++; } } // 新增:静态中转ISR函数 static void isrButton1() { if (instance) { instance->handlePlayer1Press(); } } static void isrButton2() { if (instance) { instance->handlePlayer2Press(); } } public: Game(int b1, int b2, int l1, int l2, int r, int g, int b, int bz) { button1Pin = b1; button2Pin = b2; led1Pin = l1; led2Pin = l2; redPin = r; greenPin = g; bluePin = b; buzzerPin = bz; started = false; over = false; winner = 0; reactionTime = 0; player1Score = 0; player2Score = 0; player1Pressed = false; player2Pressed = false; pinMode(button1Pin, INPUT_PULLUP); pinMode(button2Pin, INPUT_PULLUP); pinMode(led1Pin, OUTPUT); pinMode(led2Pin, OUTPUT); pinMode(redPin, OUTPUT); pinMode(greenPin, OUTPUT); pinMode(bluePin, OUTPUT); pinMode(buzzerPin, OUTPUT); // 初始化静态指针为当前对象 instance = this; // 绑定静态中转函数到中断 attachInterrupt(digitalPinToInterrupt(button1Pin), isrButton1, FALLING); attachInterrupt(digitalPinToInterrupt(button2Pin), isrButton2, FALLING); } void start() { Serial.println("Press any button to start the game"); while (digitalRead(button1Pin) && digitalRead(button2Pin)) { // INPUT_PULLUP模式下,按钮按下为LOW,等待任意按钮触发 } started = true; Serial.println("Game started!"); } void update() { // 处理玩家1获胜逻辑 if (player1Pressed) { player1Pressed = false; digitalWrite(led1Pin, HIGH); digitalWrite(redPin, LOW); digitalWrite(greenPin, HIGH); digitalWrite(bluePin, LOW); tone(buzzerPin, 440, 500); Serial.println("Player 1 wins!"); Serial.print("Reaction time: "); Serial.print(reactionTime); Serial.println("ms"); // 闪烁效果 for (int i = 0; i < 5; i++) { digitalWrite(led1Pin, HIGH); digitalWrite(greenPin, HIGH); digitalWrite(bluePin, HIGH); delay(100); digitalWrite(led1Pin, LOW); digitalWrite(greenPin, LOW); digitalWrite(bluePin, LOW); delay(100); } delay(2000); resetGame(); } // 处理玩家2获胜逻辑 if (player2Pressed) { player2Pressed = false; digitalWrite(led2Pin, HIGH); digitalWrite(redPin, LOW); digitalWrite(greenPin, HIGH); digitalWrite(bluePin, LOW); tone(buzzerPin, 440, 500); Serial.println("Player 2 wins!"); Serial.print("Reaction time: "); Serial.print(reactionTime); Serial.println("ms"); // 闪烁效果 for (int i = 0; i < 5; i++) { digitalWrite(led2Pin, HIGH); digitalWrite(greenPin, HIGH); digitalWrite(bluePin, HIGH); delay(100); digitalWrite(led2Pin, LOW); digitalWrite(greenPin, LOW); digitalWrite(bluePin, LOW); delay(100); } delay(2000); resetGame(); } } void blinkRgbLed() { if (started && !over) { int randomTime = random(3000, 6000); digitalWrite(redPin, HIGH); digitalWrite(greenPin, LOW); digitalWrite(bluePin, LOW); delay(randomTime); digitalWrite(redPin, LOW); digitalWrite(greenPin, HIGH); digitalWrite(bluePin, LOW); startTime = millis(); } } void resetGame() { digitalWrite(led1Pin, LOW); digitalWrite(led2Pin, LOW); digitalWrite(redPin, LOW); digitalWrite(greenPin, LOW); digitalWrite(bluePin, LOW); over = false; winner = 0; reactionTime = 0; startTime = 0; started = false; start(); // 自动进入下一轮等待 } int getPlayer1Score() { return player1Score; } int getPlayer2Score() { return player2Score; } }; // 初始化静态指针 Game* Game::instance = nullptr; // 创建Game对象 Game game(2, 3, 6, 7, 9, 10, 11, 12); void setup() { Serial.begin(9600); randomSeed(analogRead(0)); game.start(); delay(250); } void loop() { game.update(); // 处理按钮按下后的逻辑 game.blinkRgbLed(); }
关键修改说明
- 静态指针与中转ISR:静态指针
instance指向当前Game对象,静态ISR函数通过这个指针调用对象的成员函数,解决了非静态成员函数无法直接作为ISR的问题。 - ISR安全优化:中断函数仅做状态更新和标志位设置,所有耗时操作(闪烁、延迟、串口输出)都移到
update()方法中,在loop()里执行,避免ISR阻塞系统。 - 游戏流程优化:重置游戏后自动调用
start(),进入下一轮等待,让游戏可以连续进行。
内容的提问来源于stack exchange,提问作者Eclipse
相关产品推荐
相关产品推荐

