Just to explain. In C/C++, the compiler wants to know what a function looks like before it can 'use' it. So it wants to know what type of arguments are required and what the function will return.
So you have to either put the function before its first use (as you did) or add a prototype before the function's first use.
One of the processes (often referred to as the arduino builder) in the Arduino IDE does a reasonable job at adding the prototype before the compile is started but it does sometimes fail to do it correctly.
The prototype in your case would be
void IRAM_ATTR testISR() ;
After that the function itself can be anywhere in your code.
Your modified code would look like
const int testPin = D1;
volatile bool flag = false;
void IRAM_ATTR testISR();
void setup()
{
Serial.begin(115200);
pinMode(testPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(testPin), testISR, CHANGE);
Serial.println("Setup complete");
}
void loop()
{
if (flag)
{
Serial.println("Interrupt Triggered!");
flag = false;
}
delay(1000);
}
void IRAM_ATTR testISR()
{
flag = true;
}
What happens without the function prototype or if you don't move the function to the top of your code is that the arduino builder does not generate the prototype, more than likely due to the IRAM_ATTR attribute.
For your original code, the below is the code that is actually compiled after the builder did its job.
#include <Arduino.h>
#line 1 "C:\\Users\\bugge\\AppData\\Local\\Temp\\.arduinoIDE-unsaved2024925-22056-188eqqd.d8an\\sketch_oct25a\\sketch_oct25a.ino"
const int testPin = D1;
volatile bool flag = false;
#line 4 "C:\\Users\\bugge\\AppData\\Local\\Temp\\.arduinoIDE-unsaved2024925-22056-188eqqd.d8an\\sketch_oct25a\\sketch_oct25a.ino"
void setup();
#line 11 "C:\\Users\\bugge\\AppData\\Local\\Temp\\.arduinoIDE-unsaved2024925-22056-188eqqd.d8an\\sketch_oct25a\\sketch_oct25a.ino"
void loop();
#line 19 "C:\\Users\\bugge\\AppData\\Local\\Temp\\.arduinoIDE-unsaved2024925-22056-188eqqd.d8an\\sketch_oct25a\\sketch_oct25a.ino"
void testISR();
#line 4 "C:\\Users\\bugge\\AppData\\Local\\Temp\\.arduinoIDE-unsaved2024925-22056-188eqqd.d8an\\sketch_oct25a\\sketch_oct25a.ino"
void setup() {
Serial.begin(115200);
pinMode(testPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(testPin), testISR, CHANGE);
Serial.println("Setup complete");
}
void loop() {
if (flag) {
Serial.println("Interrupt Triggered!");
flag = false;
}
delay(1000);
}
void testISR() {
flag = true;
}
Please note the below in above
#line 19 "C:\\Users\\bugge\\AppData\\Local\\Temp\\.arduinoIDE-unsaved2024925-22056-188eqqd.d8an\\sketch_oct25a\\sketch_oct25a.ino"
void testISR();
If you now add the IRAM_ATTR to your ISR function, the builder will not generate the prototype and hence the compile fails.
#include <Arduino.h>
#line 1 "C:\\Users\\bugge\\AppData\\Local\\Temp\\.arduinoIDE-unsaved2024925-22056-188eqqd.d8an\\sketch_oct25a\\sketch_oct25a.ino"
const int testPin = D1;
volatile bool flag = false;
#line 4 "C:\\Users\\bugge\\AppData\\Local\\Temp\\.arduinoIDE-unsaved2024925-22056-188eqqd.d8an\\sketch_oct25a\\sketch_oct25a.ino"
void setup();
#line 12 "C:\\Users\\bugge\\AppData\\Local\\Temp\\.arduinoIDE-unsaved2024925-22056-188eqqd.d8an\\sketch_oct25a\\sketch_oct25a.ino"
void loop();
#line 4 "C:\\Users\\bugge\\AppData\\Local\\Temp\\.arduinoIDE-unsaved2024925-22056-188eqqd.d8an\\sketch_oct25a\\sketch_oct25a.ino"
void setup()
{
Serial.begin(115200);
pinMode(testPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(testPin), testISR, CHANGE);
Serial.println("Setup complete");
}
void loop()
{
if (flag)
{
Serial.println("Interrupt Triggered!");
flag = false;
}
delay(1000);
}
void IRAM_ATTR testISR()
{
flag = true;
}
Manually adding the prototype makes that the compiler now knows about the testISR() function before its first 'use'.
You might encounter a similar problem in the future for other things.
You can find the file that is actually compiled (yourSketch.ino.cpp) if you enable verbose output during compilation under file/preferences in the IDE.
After a compile, copy the content of the output to a text editor and search for .ino.cpp; you will find a path to the file.