0
votes
  1. The Goal:
  • I am writing a really simple program in the Arduino environment using an Arduino Nano. (the old ones).
  • I am trying to have pin 2 act as an interrupt that then call a buzzer function. Please see code for clarity.
  1. The problem:
  • When I run the below code with "buzzer_make_sound" in "loop()" the buzzer works as expected.
  • When the interrupt is triggered and the callback function is called, it does not execute timers and the buzzer properly and causes the buzzer not to sound at all and the timers do weird things.
  1. Code
#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. Terminal prints:

When running the "buzzer_make_sound()" in loop:

Buzzer entry
Buzzer exit
2001

When triggering the interrupt:

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

The strange thing is that when triggering the interrupt, it finishes the task instantly. There is no 2 second delay.

Anyone got any idea at all what is going on? Do Interrupts stop timers? If so, how does one work with timer dependent things?

1

1 Answers

2
votes

Problem description taken from https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/

Solution taken from my brain.

millis(), delay(), and micros() all use interrupts to do their job, so can't be called when inside of another interrupt routine. On microprocessors, when in an interrupt, in general you cannot enter another interrupt (the other interrupts will be "scheduled", and when you leave the current interrupt, those interrupts will be called in order of priority). Some processors have interrupt preemption, wherein higher priority interrupts can interrupt lower priority interrupts, but the nano processor (ATmega238) does not. See the datasheet here https://www.microchip.com/wwwproducts/en/ATmega328. Search for "interrupt" or "priority".

Anyway, the documentation for interrupts on the Arduino website reads

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.

and

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.

One solution is to use delayMicroseconds(), but that violates the suggestion that interrupts should be as short and quick as possible. Generally you use them to place data into a queue or set a flag or fill a buffer, and then let the real work get taken care of when that queue or flag is checked in your main loop. You shouldn't let an ISR do nothing for a period of time (like just sitting in delay).

I would modify your code to have the interrupt set a global boolean flag called buzzer_scheduled (make sure it's specified as volatile as suggested above). Modify buzzer_make_sound() to unset this flag. Modify your loop() to check for this flag, and call buzzer_make_sound() if it's set.