Enabling WiFi conflicts with button interrupts?? [RESOLVED]

I'm not entirely sure what is going wrong here.

Hardware

ESP32 Microcontroller. Attached to 8 neopixels, and a button with pull-up resistors going through a schmitt trigger (for learning).

Software/libraries
I'm using FastLED and the ESP32 Wifi libraries.

My code is below.

What I expected to happen.

I have multiple 'color presets' I want to cycle through when I push the button. I want debouncing on the button. When I push the button, it should change the presets ONCE.

What actually happens

If I do NOT enable WiFi. Meaning I comment out the 'setup_wifi()' line in setup(). Then everything works correctly. I push the button once and hold it down, I get ONE change to the next preset light cycle. This uses my 100msec debounce in the ISR routine.

But, if I uncomment setup_wifi() to turn it on. And then press and hold the button, it continuously cycles through each of my preset light configurations. Without stopping and without really much of a delay..

Then, if I leave wifi enabled. But I change the 100msec delay to like ~225 or above (maybe 250? and above). And then I push and hold the button, it goes back to working. One press and hold gets ONE change of lights. And then I release and press again and it works.

Thoughts

If I have to, I could leave it at a debounce of 250. But I tend to push fast, and would really like a debounce of 100 or even less (I was hoping a schmitt trigger would let me get to like 50-75, but that's a story for a different topic).

But at the very least I'd like to understand what is happening here. Because my code seems to work okay on its own. Just not with the WiFi enabled.

I'm assuming WiFi does something with the interrupts or some other sort of conflict causing my interrupts to be called oddly. But I honestly have no real idea.

So can someone explain to me what is happening? And is there a fix to allow wifi to be enabled, have a short debounce of <=100, and only cycle one preset light at a time?

Code

This code reproduces the problem on my setup. The long (*) sections point to where I change things to make it work/not work.

#include <FastLED.h>
#include <WiFi.h>

// WIFI
char ssid[]     = "##";
const char* password = "##";

// Static IP Stuff
IPAddress local_IP(##);
IPAddress gateway(10, 23, 24, 1);
IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(8, 8, 8, 8);
IPAddress secondaryDNS(8, 8, 4, 4);


// The definitions to control the actual LEDs through the FastLED library
#define LED_PIN     23
#define NUM_LEDS    8
#define BRIGHTNESS  15 // Up to 255
#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];

// Set up some pallete variables to be used later
CRGBPalette16 currentPalette;
TBlendType currentBlending;

// Set up the buttons(as struct) for controlling the lights
struct Button {
    const uint8_t PIN; // Pin the button is attached to
    uint32_t station;  // station we are 'at'.  0 = white, 1 = rainbow, etc
    bool pressed;     // last pressed value (true/false)
};
Button btn01 = {39, 0, false};


/*
 * Intterrupt for button push
 */
void IRAM_ATTR btnISR() {
    static unsigned long last_interrupt_time = 0;
    unsigned long interrupt_time = millis();
  
    // ************************************************
    // If I make this 100 changed to 250, i have no issue
    // ***************************************************
    if (interrupt_time - last_interrupt_time > 100)
    {
        // Do stuff here
        btn01.pressed = true;
        btn01.station += 1;
        if (btn01.station >= 4) {
            btn01.station = 0;
        }

    }
    last_interrupt_time = interrupt_time;
}

/*
 * setup_wifi()
 */
void setup_wifi() {
    // This calls the config to load in the static stuff
    if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
        Serial.println("STA Failed to configure");
    }

    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid);

    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("");
    Serial.println("WiFi Connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());

} // end setup_wifi

/*
 * fillLEDsFromPalette()
 */
void fillLEDsFromPalette( uint8_t colorIndex ) {
    uint8_t brightness = 35;
    for (int i = 0; i < NUM_LEDS; i++) {
        leds[i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending );
        colorIndex += 3;
    } 
}

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

    // *********************************************
    // Or If I comment out this, and don't initialize wifi, everything works as expected
    // **********************************************
    //setup_wifi();
    
    pinMode(btn01.PIN, INPUT);
    attachInterrupt(digitalPinToInterrupt(btn01.PIN), btnISR, RISING);

    delay( 1500 ); // power-up safety delay
    FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
    FastLED.setBrightness(  BRIGHTNESS );
    
    currentBlending = LINEARBLEND;

} // end setup()

void loop() { 

    static uint8_t startIndex = 0;
    startIndex = startIndex + 1;

    if (btn01.station == 0 and btn01.pressed) {
        Serial.println("White");
        for( int i = 0; i < NUM_LEDS; i++) {
            leds[i] = CRGB::White;
        }
        btn01.pressed = false;
    }
    if (btn01.station == 1 and btn01.pressed) {
        Serial.println("Red");
        for( int i = 0; i < NUM_LEDS; i++) {
            leds[i] = CRGB::Red;
        }
        btn01.pressed = false;
    }
    if (btn01.station == 2 and btn01.pressed) {
        Serial.println("Green");
        for( int i = 0; i < NUM_LEDS; i++) {
            leds[i] = CRGB::Green;
        }
        btn01.pressed = false;
    }
    if (btn01.station == 3) {
        Serial.println("Rainbow");
        currentPalette = RainbowColors_p;
        fillLEDsFromPalette( startIndex );
        btn01.pressed = false;
    }

    FastLED.show();
    FastLED.delay(50);
  

} // end void loop()
1 Like

Hello and welcome.

++Karma; // For posting your code correctly on your first post.

/*
 * Intterrupt for button push
 */

Interrupts are the wrong tool for button pushes, that's why you are having problems. Poll the buttons instead. Search on here for related topics, this comes up almost every day.

Interesting. I'll have to try it with a polling solution.

I swear I read somewhere that with the FastLED library interrupts were preferred do to tight timings of that library. But I can't quickly find where I read that, so maybe I got some wires crossed in my brain about it.

But I'll definitely try to re-code from that angle and see how it goes.

That being said, for my own edification. I still don't understand why wifi breaks(?) the interrupt code that works without wifi enable.

Let me clarify, I was only talking about using interrupts for detecting button pushes, not about the rest of your code. Interrupts for button pushes is not the right thing to do. I make no comment on FastLED or other parts of your code. Hopefully someone else will help you with that.

Well then let me quickly take an aside and ask you a quick question about that.

In the end, I plan to have about 10 push buttons.

Looking at polling, you'd have to loop through all 10 push buttons each loop() to look for changes.

In that case is polling still the way to go? That seems resource intensive to me to check every loop for all 10 buttons. When an ISR just gets called infrequently (hours) when buttons are pushed.

In that case is polling still the way to go? That seems resource intensive to me to check every loop for all 10 buttons. When an ISR just gets called infrequently (hours) when buttons are pushed.

Yes, it's the correct thing to do. As for being resource intensive, no, it's not. Testing a button to see if it's pressed or not is utterly trivial for a microcontoller, in any case, what else would it be doing if not testing buttons? The thing you have to get right is the rest of the code must be non-blocking so it gets back to loop() often enough and doesn't get stuck somewhere. That means no delay() and very careful use of for() and while() loops, preferably avoiding them altogether.

Wifi uses interrupts to maintain your connection (either in STA or AP mode or both) While you are in an interrupt, interrupts are disabled. This is probably the reason for the abnormal behavior. But i may as well mention this now, FastLED.show() turns off interrupts while it is sending the signal to the LED-strip, timing is crucial for that. I have heard that for ESP's FastLed also has a DMA mode (as Makuna/NeoPixelBus does), but i am not sure how set that or if it uses that by default on an ESP. And this :FastLED.delay(50);Is one of the dumbest ideas i have ever seen. Rather than using delay(), which on an ESP at least calls yield() until the time has elapsed, FastLED.delay() basically calls .show until then, sending the same info over and over to chips that have that info already and really don't need to be refreshed with the same data again. Of course really you should use millis() for timing, but if you are going to use delay(), use delay() !

nertskull_arduino:
That seems resource intensive to me to check every loop for all 10 buttons. When an ISR just gets called infrequently (hours) when buttons are pushed

Do you realize how fast the processor is? You're throwing around buzzwords like "resource intensive" without investigating the facts. A processor like the AVR could check 1000 buttons in the time it takes to move your finger a few millimeters toward the button. Maybe more.

OH yes and

Button btn01 = {39, 0, false};

should be declared volatile

and there is something about calling millis() from within an ISR, but i am not sure about the details

You can call millis() from within an ISR. It is a very useful feature, however you must understand that the value that millis() returns, does not change during the execution of the ISR.

Deva_Rishi:
Wifi uses interrupts to maintain your connection (either in STA or AP mode or both) While you are in an interrupt, interrupts are disabled. This is probably the reason for the abnormal behavior.

This makes sense, and kind of what I assumed was happening.

Deva_Rishi:
But i may as well mention this now, FastLED.show() turns off interrupts while it is sending the signal to the LED-strip, timing is crucial for that. I have heard that for ESP's FastLed also has a DMA mode (as Makuna/NeoPixelBus does), but i am not sure how set that or if it uses that by default on an ESP.

I didn't know about this, I'll look into it more. Thanks.

Deva_Rishi:
And this :

FastLED.delay(50);

Is one of the dumbest ideas i have ever seen. Rather than using delay(), which on an ESP at least calls yield() until the time has elapsed, FastLED.delay() basically calls .show until then, sending the same info over and over to chips that have that info already and really don't need to be refreshed with the same data again. Of course really you should use millis() for timing, but if you are going to use delay(), use delay() !

Yeah I guess I don't fully understand the FastLED.delay() thing. I know I see it in pretty much all of the examples out there for the library, so I blindly included it. I'm going to read up more on that and see if I can get away without it.

aarg:
Do you realize how fast the processor is? You're throwing around buzzwords like "resource intensive" without investigating the facts. A processor like the AVR could check 1000 buttons in the time it takes to move your finger a few millimeters toward the button. Maybe more.

Not having a coding background, I guess I was thinking about this different. I thought that the processor being so fast means it goes through the loop even faster. So you come around and hit the button polling more frequently per every unit time. A processor that makes 1 loop a second polls once a second. One that polls 10,000/sec spends a bunch of real-life time polling buttons you aren't pushing.

I even played with this a bit early on when I first setup interrupts. I used micros() to time how long my loop() was taking which was 3.473. And it was taking 26 us to poll my buttons. That's 0.7%. I felt taking nearly 1% of my processing time just for polling buttons was "resource intensive". Especially since the buttons only get pushed a few times a day. In the course of 1 day, my program loop() will loop almost 25 million times. Which, at 26us poll time, ends up being over 10 minutes of "wasted" compute time/day. Which I know sounds silly, but initially that seemed like just a waste to have it sitting around checking for something for that much time, when I could just 'interrupt' it once or twice a day with a button push for the cost of just 26 us / push.

Anyway, I guess my point is, looking at it from your viewpoint, yeah that's not very resource intensive. Either way the program is looping, so whether its looping to check the button presses or not, doesn't matter as long as the job gets done.

I've set up a polling solution now in lieu of an interrupt solution. And thus far, everything seems to be working as expected. So clearly polling seems to be the winning answer here. Hopefully I don't run into more issues as I flush out the rest of my code.

Thanks everyone!

Thanks everyone!

You're welcome!

One last thing; please go to your original post and edit the title to include something like [RESOLVED] at the end.

Yeah I guess I don't fully understand the FastLED.delay() thing. I know I see it in pretty much all of the examples out there for the library, so I blindly included it. I'm going to read up more on that and see if I can get away without it.

Oh you can get away without it, i suggest you take a look inside FastLED.cpp (i think it's in there) and see what the function does, and keep in mind that the ws2811 chips do not need to be triggered at all. Once they've received data they keep their RGB levels. Like when you disconnect the data line but leave the power on the leds. Well i'm sure the CPU can do something more useful than that. Also since it turns off interrupts. You could polling buttons or something. About that, consider that it is probably easily sufficient to poll all button 10x per second, and how much processor time % does that take up ? Great it has been resolved.