中断导致奇怪的计时器行为

Interrupts causing strange timer behavior

  1. 目标:
  1. 问题:
  1. 代码
#include "includes.h"    // this includes arduino.h 

void callback_rc_receive();
void buzzer_make_sound();

void setup() {
  Serial.begin(115200);

  pinMode(RC_INPUT_CHANNEL1_PIN, INPUT);      // pin 5
  pinMode(RC_INPUT_CHANNEL2_PIN, INPUT);      // pin 6
  pinMode(RC_INPUT_CHANNEL3_PIN, INPUT);      // pin 7
  pinMode(RC_INPUT_CHANNEL4_PIN, INPUT);      // pin 8
  pinMode(RC_INCOMING_SIGNAL_TRIGGER_PIN, INPUT);      // pin 2
  pinMode(BUZZER1_PIN, OUTPUT);                        // pin 3

  pinMode(LED_BUILTIN,OUTPUT);

  attachInterrupt(digitalPinToInterrupt(RC_INCOMING_SIGNAL_TRIGGER_PIN), callback_rc_receive, RISING);

  ReceiverOne.channel1State = 0;            // typedef struct
  ReceiverOne.channel2State = 0;
  ReceiverOne.channel3State = 0;
  ReceiverOne.channel4State = 0;

}

void loop() {
  //buzzer_make_sound();                      // this sounds the buzzer
}


void buzzer_make_sound(){
  Serial.println("Buzzer entry");
  int startTime = millis();

  tone(BUZZER1_PIN, 2000);
  delay(1000);
  noTone(BUZZER1_PIN);
  delay(1000);

  Serial.println("Buzzer exit");
  Serial.println(millis() - startTime);
}

void callback_rc_receive(){
  
  if (digitalRead(RC_INPUT_CHANNEL1_PIN) == 1){
    Serial.println("1");

    buzzer_make_sound();

  }else if (digitalRead(RC_INPUT_CHANNEL2_PIN) == 1){
    Serial.println("2");
  }else if (digitalRead(RC_INPUT_CHANNEL3_PIN) == 1){
    Serial.println("3");
  }else if (digitalRead(RC_INPUT_CHANNEL4_PIN) == 1){
    Serial.println("4");
  }else{
    Serial.println("Error");
  }

}
  1. 终端打印:

当运行循环中的“buzzer_make_sound()”时:

Buzzer entry
Buzzer exit
2001

触发中断时:

1
Buzzer entry
Buzzer exit
0
1
Buzzer entry
Buzzer exit
0
1
Buzzer entry
Buzzer exit
65536
1
Buzzer entry
Buzzer exit
65536

奇怪的是,当触发中断时,它立即完成了任务。没有2秒延迟。

有人知道发生了什么事吗?中断会停止定时器吗?如果是这样,如何处理依赖于计时器的东西?

问题描述摘自 https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/

从我的大脑中提取的解决方案。

millis()delay()micros() 都使用中断来完成它们的工作,因此不能在另一个中断例程内调用。在微处理器上,当处于一个中断时,一般情况下你不能进入另一个中断(其他中断会被“调度”,当你离开当前中断时,那些中断将按优先级顺序被调用)。有些处理器有中断抢占,高优先级的中断可以打断低优先级的中断,但nano处理器(ATmega238)没有。请参阅此处的数据表 https://www.microchip.com/wwwproducts/en/ATmega328。搜索“中断”或“优先级”。

无论如何,Arduino 网站上关于中断的文档是这样写的

Inside the attached function, delay() won’t work and the value returned by millis() will not increment. Serial data received while in the function may be lost. You should declare as volatile any variables that you modify within the attached function. See the section on ISRs below for more information.

Generally, an ISR should be as short and fast as possible. If your sketch uses multiple ISRs, only one can run at a time, other interrupts will be executed after the current one finishes in an order that depends on the priority they have. millis() relies on interrupts to count, so it will never increment inside an ISR. Since delay() requires interrupts to work, it will not work if called inside an ISR. micros() works initially but will start behaving erratically after 1-2 ms. delayMicroseconds() does not use any counter, so it will work as normal.

一种解决方案是使用delayMicroseconds(),但这违反了中断应尽可能短和快的建议。通常,您使用它们将数据放入队列或设置标志或填充缓冲区,然后在主循环中检查队列或标志时让真正的工作得到处理。您不应该让 ISR 在一段时间内什么都不做(比如只是延迟)。

我会修改您的代码,让中断设置一个名为 buzzer_scheduled 的全局布尔标志(确保按照上面的建议将其指定为 volatile)。修改 buzzer_make_sound() 以取消设置此标志。修改您的 loop() 以检查此标志,如果已设置则调用 buzzer_make_sound()