Friday, May 6, 2022

Interrupts: WTF happened

For an index to all my stories click this text.

Some of you pointed out that there was a problem with my code in the "More buttons on fewer pins" story. Now I must admid that I wrote that story some time ago, published it and forgot about it.
When some readers however mailed me that there was a problem with the software I had to dive into this again. And indeed WTF happened.

Well what happened was that a new version of the Arduino software had some alterations in the interrupt routine. That made the ESP series (ESP8266 and ESP32) microcontrollers run berserk.

What's behind it.

Sometimes programs are loaded from flash memory when run. That works fine for a lot of programs but poses a problem when fast processing is involved.
The Arduino IDE therefore changed the way it handles interrupts. It wants the interrupt handling routine to run in RAM so it is always available when needed, and needs not to get loaded first during execution of a program.

Next to that it is recommended that no delay() command is used in the interrupt routine. As a matter of fact that makes sense. Interrupts generally need to be handled as fast as possible because it may happen that when the interrupt routine is being handled at the same time a new interrupt is initiated.

However in my program that handles a button press speed is not the issue. The interrupt routine is processed a lot faster then you would press another button. But my endeavours in my little corner of the universe is of course of no concern to the developers of the Arduino IDE.

It made however an impact on some of my programs and of course the chapter on interrupts in my book ESP32 Simplified. You can find details on my book here: http://lucstechblog.blogspot.com/2020/06/esp32-simplified-my-book-in-english-now.html

The solution

Luckily the alterations to get things working again are small.

attachInterrupt(digitalPinToInterrupt(D5), buttonpress, FALLING);


In the setup() routine the interrupts are initiated with this line. And that stays the same. The interrupt is attached to pin D5 and when that button is pressed it calls the "buttonpress" routine.

void buttonpress()
{
  // button 1 ==> 011
  if (digitalRead(D5)== LOW && digitalRead(D6)== HIGH && digitalRead(D7)== HIGH)
  {
    strip.setPixelColor(0, 0,255,0);
    strip.setPixelColor(1, 0,0,0);
    strip.setPixelColor(2, 0,0,0);
    strip.setPixelColor(3, 0,0,0);
    strip.setPixelColor(4, 0,0,0);
    strip.setPixelColor(5, 0,0,0);
    strip.setPixelColor(6, 0,0,0);
    strip.show();
    delay(500);
  }


This is part of the "buttonpress" interrupt routine. This is where we need to make the changes. First we need to alter void buttonpress() in ICACHE_RAM_ATTR void buttonpress() So a command is put in front of void buttonpress(). That command is ICACHE_RAM_ATTR and that makes sure that this part of the program is loaded into RAM before the program starts.

Next the delay(500) line must be left out.

So the new first part of the program is:

ICACHE_RAM_ATTR void buttonpress()
{
  // button 1 ==> 011
  if (digitalRead(D5)== LOW && digitalRead(D6)== HIGH && digitalRead(D7)== HIGH)
  {
    strip.setPixelColor(0, 0,255,0);
    strip.setPixelColor(1, 0,0,0);
    strip.setPixelColor(2, 0,0,0);
    strip.setPixelColor(3, 0,0,0);
    strip.setPixelColor(4, 0,0,0);
    strip.setPixelColor(5, 0,0,0);
    strip.setPixelColor(6, 0,0,0);
    strip.show();
  }


Famous last words.

So to get your programs that use an interrupt routine working again put ICACHE_RAM_ATTR in front of the line that defines the start of the routine and remove all delay() statements from your interrupt routines.

PHEW !!!
Easier as I thought.

Till next time.

Luc Volders