ESP8266/Arduino: Why is it necessary to add the ICACHE_RAM_ATTR macro to ISRs and functions called from there?

17,133

Solution 1

The ICACHE_RAM_ATTR and ICACHE_FLASH_ATTR are linker attributes. Once you compile your sketch, you can say if the function should be stored in the RAM or FLASH (normally you do not set anything: no cache).

ESP8266 is multitasking and the ESP32 has 2 cores. So you can execute your code as multithreading - since it use the RTOS.

And now the problem: The entire flash is used for the program and storage. Reading and writing to the flash can be done only over 1 thread. If you try to access the flash simultaneously over 2 different threads, your ESP will probably crash.

This is because you can put your function in the RAM instead of the flash. So even if you are writing something in the EEPROM or flash, this function can be called without accessing the flash.

With ICACHE_RAM_ATTR you put the function on the RAM.

With ICACHE_FLASH_ATTR you put the function on the FLASH (to save RAM).

Interrupt functions should use the ICACHE_RAM_ATTR. Function that are called often, should not use any cache attribute.

Important: NEVER access your flash inside an interrupt! The interrupt can occur during a flash access, so if you try to access the flash at the same time, you will get a crash (and sometimes this happens after 1-2 hours after you use your device).

Since you have only 32kb of IRAM (Instruction RAM), you should try to put only interrupt functions in the RAM, not all your functions, even if it is possible to do so.

Second question: NO, absolutely no! inline is another compiler flag, so that the compiler will try to put your entire function inside the caller function => convert a function call to c++ code inside your main. This doesn't mean that the compiler will do it, just try it. You can't ask to put the function inside the RAM, if the function does not exist anymore once you compile your sketch.

Solution 2

Many thanks for the ICACHE_RAM_ATTR, I used it on the very top of my code... and of course there are some pins not working as an interrupts, for example in my case I was using board WEMOS D1 Mini Pro, and the pin D0 (GPIO 16) did not work, until I change to the next pin, (GPIO 14) ad it works flawlessly....

The ICACHE_RAM_ATTR works with newer libraries, the outdated 2.5 library works too without this piece of code.

Many thanks!

const uint8_t interruptPin = 14;
volatile byte interruptCounter = 0;
int numberOfInterrupts = 0;
void ICACHE_RAM_ATTR handleInterrupt();

void setup() {

  Serial.begin(9600);
  pinMode(interruptPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, CHANGE);

}

void handleInterrupt() {
  interruptCounter++;
}

void loop() {

  if(interruptCounter>0){

      interruptCounter--;
      numberOfInterrupts++;

      Serial.print("An interrupt has occurred. Total: ");
      Serial.println(numberOfInterrupts);
  }

}
Share:
17,133
x-ray
Author by

x-ray

Updated on June 05, 2022

Comments

  • x-ray
    x-ray about 2 years

    I read that I need to add the ICACHE_RAM_ATTR macro to interrup service routines (ISRs) and to every function that is called from there in my Arduino code for ESP8266 to prevent random crashes. I also found an explanation of what the macro ICACHE_RAM_ATTR does, although I'm not sure if the explanation, which is for the Espressif ESP8266 SDK, is also true for Arduino on ESP8266. And I did not understand why I need to add the macro to ISRs.

    First question: Why do I need to add the ICACHE_RAM_ATTR macro to ISRs and all functions called from there?

    Next question is, what happens if I force inlining a function that is called from an ISR:

    inline void doStuff() __attribute__((__always_inline__)) { // <-- necessary to add ICACHE_RAM_ATTR here?
        // no more function calls here
    }
    
    void ICACHE_RAM_ATTR handleInterrupt() {
        doStuff();
    }
    

    Second question: Do I need to add the ICACHE_RAM_ATTR macro to functions that are forced to be inlined?

  • x-ray
    x-ray over 4 years
    Thank you for the explanation! About the inlining: So I guess I should add the ICACHE_RAM_ATTR to all functions that are called from an ISR, even if marked to be inlined, because I cannot be sure if they in fact will be inlined? Does adding ICACHE_RAM_ATTR prevent inlining?
  • x-ray
    x-ray over 4 years
    What else apart from calling a function without the ICACHE_RAM_ATTR attribute could potentially lead to FLASH access?
  • Adriano
    Adriano over 4 years
    Yes, if you call another function inside your ISR, it would be better if you add ICACHE_RAM_ATTR also to this functions. The inline function is just a request to the compiler (en.wikipedia.org/wiki/Inline_function), does not mean that it will be inline at 100% and probably will be ignored if you add ICACHE_RAM_ATTR. You should just never try to read or write parameters or SPIFFS inside your interrupt.
  • Adriano
    Adriano over 4 years
    I can't say what will access the flash to be honest. I had a project where I used the DAC output inside an interrupt and this also crashed, since it access and change some registers (they are inside the flash). If the interrupt is a timer, you can use for example Ticker, this will prevent a crash.
  • Mohammad Kholghi
    Mohammad Kholghi about 2 years
    Pin 16 is not connected to any interrupts.