Checking IR Input During LED Animation Help

Hello everyone!

So I'm working on my first big project and I've come across an issue that I'm hoping there is a solution to. Essentially, I'm creating a large grid of WS2812B LED's attached to an Arduino Uno R3, and using IR input to select what the LED's are doing. Right now it's all built to only control 9 LED's for testing purposes.

Currently I've got the program setup to use the Up/Down arrows on the IR remote to change brightness, Left/Right arrows to change the color of all the lights, and then the Play/Pause button on the remote to change the lights between on and off. This is all working without issue utilizing switch case.

The next thing I want to setup is to use the number keys on the remote to select pre-built light effects. In doing so, I ran into an issue that I wasn't too surprised to come across, in which once an animated light effect begins the Arduino can no longer listen for new IR codes to change the lights again. I've tried several things to remedy this such as adding a new instance of irrecv.resume() during the animation (and many other places) as well as looking into Attach Interrupt. So far I've not been successful with either of these solutions or anything else I've tinkered with.

In all the research I've read online I seem to be getting conflicting statements regarding whether or not what I'm trying to do is even possible with an Arduino as it can't perform multiple tasks at the same time. The part that really confuses me is I got the idea to do all of this from a FastLED tutorial in which they were switching between animated light effects using a button, and that worked for them, but I can't get it to work with my IR setup. This is the tutorial where he's using a button to switch animated effects.

So far my code will change to the animated effect, Pride2015, when I press the number 2, but like I said, once it starts that effect I can no longer do anything else until I restart the program. My sketch is split into two separate files, the main file being LivingWall_v6.ino and then the animated effect in Pride2015.h. Here is the code for those two files.

LivingWall_v6.ino

#include <Arduino.h>
#include <FastLED.h>
#include <IRremote.h>

const int LED_PIN = 8;
const int IR_PIN = 6;

uint8_t currentHue;
uint8_t previousHue;
uint8_t heldHue;

uint8_t currentBrightness;
uint8_t previousBrightness;
uint8_t heldBrightness;

#define NUM_LEDS 9
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB

// #define kMatrixHeight 3         Still need to setup the matrix properties
// #define kMatrixWidth 3

CRGB leds[NUM_LEDS];

IRrecv irrecv(IR_PIN);

decode_results results;

bool isRunning = false;

#include "Pride2015.h"

void setup(){

  FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
  FastLED.setBrightness(currentBrightness);

  Serial.begin(9600);
  
  irrecv.enableIRIn();
  irrecv.blink13(true);
}



void loop(){

previousBrightness = currentBrightness;
previousHue = currentHue;
  if (irrecv.decode(&results)){
    Serial.println(results.value, HEX);
    irrecv.resume();
    switch(results.value){
      
      
      case 0xFD807F: // Play/Pause Button
      if (currentBrightness != 0){
          previousBrightness = currentBrightness;
          heldBrightness = currentBrightness;
          FastLED.setBrightness(0);
          currentBrightness = 0;
      }
        else if (currentBrightness == 0){
          FastLED.setBrightness(heldBrightness);
          currentBrightness = heldBrightness;
        }  
      FastLED.show();
      break;


      case 0xFD50AF: // Right Arrow
      previousHue = currentHue;
      currentHue = currentHue + 10;
      fill_solid(leds, NUM_LEDS, CHSV(currentHue, 255, 255));
      FastLED.show();
      break;

      
      case 0xFD10EF: // Left Arrow
      previousHue = currentHue;
      currentHue = currentHue - 10;
      fill_solid(leds, NUM_LEDS, CHSV(currentHue, 255, 255));
      FastLED.show();
      break;


      case 0xFDA05F: // Up Arrow
      previousBrightness = currentBrightness;
      currentBrightness = currentBrightness + 10;
      FastLED.setBrightness(currentBrightness);
      FastLED.show();
      break;


      case 0xFDB04F: // Down Arrow
      previousBrightness = currentBrightness;
      currentBrightness = currentBrightness - 10;
      FastLED.setBrightness(currentBrightness);
      FastLED.show();
      break;


      case 0xFD08F7: // Number 1
      fill_rainbow(leds, NUM_LEDS, currentHue, NUM_LEDS);
      FastLED.show();
      break;


      case 0xFD8877: // Number 2
      isRunning = true;
      Pride2015 pride2015 = Pride2015();
      while(isRunning) pride2015.runPattern();
      break;
      
    }
    
   }

  }

Pride2015.h

#include "Arduino.h"
#include <IRremote.h>
class Pride2015 {
  public:
    Pride2015(){};
    void runPattern();
  
  private:
    void prideLoop();
    
    uint16_t sPseudotime = 0;
    uint16_t sLastMillis = 0;
    uint16_t sHue16 = 0;
};

void Pride2015::runPattern() {
  irrecv.resume();
  prideLoop();
  FastLED.show();
  
}

void Pride2015::prideLoop(){
  uint8_t sat8 = beatsin88( 87, 220, 250);
  uint8_t brightdepth = beatsin88( 341, 96, 224);
  uint16_t brightnessthetainc16 = beatsin88( 203, (25 * 256), (40 * 256));
  uint8_t msmultiplier = beatsin88(147, 23, 60);

  uint16_t hue16 = sHue16;//gHue * 256;
  uint16_t hueinc16 = beatsin88(113, 1, 3000);
  
  uint16_t ms = millis();
  uint16_t deltams = ms - sLastMillis ;
  sLastMillis  = ms;
  sPseudotime += deltams * msmultiplier;
  sHue16 += deltams * beatsin88( 400, 5,9);
  uint16_t brightnesstheta16 = sPseudotime;
  
  for( uint16_t i = 0 ; i < NUM_LEDS; i++) {
    hue16 += hueinc16;
    uint8_t hue8 = hue16 / 256;

    brightnesstheta16  += brightnessthetainc16;
    uint16_t b16 = sin16( brightnesstheta16  ) + 32768;

    uint16_t bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536;
    uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536;
    bri8 += (255 - brightdepth);
    
    CRGB newcolor = CHSV( hue8, sat8, bri8);
    
    uint16_t pixelnumber = i;
    pixelnumber = (NUM_LEDS-1) - pixelnumber;
    
    nblend( leds[pixelnumber], newcolor, 64);

  }
}

Is there any way to set this up so that the Arduino can loop that animated effect while also checking for new IR inputs? Hopefully I'm just not privy to something that could make this work and I don't have to rethink the entire project. It wouldn't be the end of the day if animations weren't possible, but animations are definitely supposed to be the highlight of the project.

Thanks in advance for any assistance provided.

In order for your animation to remain responsive, you have to break up you annimation into small chunks and only do a single chunk each time through loop(). See this tutorial: several things at the same time

Unfortunately, this most likely will not solve your problem because FastLED turns off interrupts while it is clocking out the data and the IR library relies on interrupts to capture the incoming signals.

Do you think there would be a way to utilize something like millis() to achieve what I'm trying or is it a lost cause?

You can write your code so the IR message causes an interrupt.

Example, the IR xcvr is attached to UNO pin 2 or 3 (interrupt pins).

During the animation interrupts are turned off, however, an input on pin 2/3 will set the interrupt action. When the animations are finished, interrupts are enabled briefly, at which point an ISR routine you write can take over.

In the ISR you can stop further animations until a valid IR receive code is received.

When you decode a subsequent (2nd) IR message, do what needs to be done with the message and return to animations as needed.

So you will need to send the message twice, one time to get the sketches attention and a second time to tell the sketch to do something.

The IR RX pin can be shared with a second pin like D06 to do actual decoding of the IR signal.

Yes, of course, and you are on the right track. IR input is handled by interrupts, you only have to provide checks for new input in your code.

Replace the irrecv.resume() by irrecv.decode() as in your first sketch and handle the result as required.

So I tried a few different permutations of this as stated and am not getting the desired result. I tried just removing irrecv.resume(); and replacing it with irrecv.decode(&results);, I tried irrecv.decode();, and I also tried replacing it with the if statement if (irrecv.decode(&results)){ Serial.println(results.value, HEX); irrecv.resume(); but none of these are doing anything that I can tell.

In the main file the if statement is checking for any IR results, then if IR is received selecting a switch case. Is there a way to tell the Pride2015.h file to check for results from the IR and if there are results, pass them into the switch at the main file, LivingWall_v6.ino? Or am I missing something obvious with your suggestion? (I probably am....)

Instead of println() stop this loop

by setting isRunning=false. Then you can check and process results.value in the main loop.

Eventually prevent multiple irrecv.decode() by a new global variable command by changing this:

into

  if (irrecv.decode(&results)){
    command = results.value;
    irrecv.resume();
  }
  if (command) {
    Serial.println(results.value, HEX);
    switch(command){
    ...
    command = 0;
  }

Really appreciate all of the help. I'm still not getting it, but I'm thinking you've probably set me on the right path. I just don't want to take up more of your time trying to talk me through what is probably pretty basic. Thanks so much.

1 Like

Don't worry, simply ask if you're stuck with your experiments.

So that means that you have to press a button on the IR twice? And I suspect that the ISR that one writes will conflict with the existing ISR; am I missing something (except for rewriting the IR library)?

No. That's only required if the first code is not detected properly. Your code is not influenced at all. If you receive the same code multiple times then it should subsequently do the same thing, nothing different.

Why would you want to write an ISR? For which interrupt, for which purpose?

I'm trying to understand @LarryD's approach.

Quick question.

While looking into the issue I'm having I came across the FastLED github wiki page that talks about interrupts, and I was wondering if I could use the example he's providing at the end to solve my problem.

Let's say you don't want a back and forth like this however. What if instead you wanted to have something just continually sending data? (E.g. boblight/ambilight/adalight). Then what you can do is make sure that every "batch" of data begins with a known string of bytes called a header. Let's say that every batch of led data will begin with four bytes - 0xDEADBEEF. Now, what we do is we read serial data until we see those four bytes in a row, then we read our full frame of data. The extra trick is that after we're done calling show, we flush the serial read buffer entirely to make sure we don't have a partial frame of data. For example:


void loop() {
  // we're going to read led data directly from serial, after we get our header
  while(true) {
    while(Serial.available() == 0){} // wait for new serial data
    uint8_t b = Serial.read();
    bool looksLikeHeader = false;
    if(b == header[0]) {
      looksLikeHeader = true;
      for(int i = 1; looksLikeHeader && (i < sizeof(header)); i++) {
        while(Serial.available() == 0){} // wait for new serial data
        b = Serial.read();
        if(b != header[i]) {
          // whoops, not a match, this no longer looks like a header.
          looksLikeHeader = false;
        }
      }
    }

    if(looksLikeHeader) {
      // hey, we read all the header bytes!  Yay!  Now read the frame data
      int bytesRead = 0;
      while(bytesRead < (NUM_LEDS *3)) {
        bytesRead += Serial.readBytes(((uint8_t*)leds) + bytesRead, (NUM_LEDS*3)-bytesRead);
      }
      // all bytes are read into led buffer. Now we can break out of while loop
      break;
    }
  }

  // now show the led data
  FastLED.show();

  // finally, flush out any data in the serial buffer, as it may have been interrupted oddly by writing out led data:
  while(Serial.available() > 0) { Serial.read(); }

} 

Here is the page I'm seeing this at. Interrupt Problems

You don't have a problem of missed IR data. Look at the buffered input after each run through a pattern, using irrecv.decode(), and handle what you get. If you don't take the chance and time to look for the input then also interrupts cannot help you out.

Ah, got ya, that makes sense.

The first push sets up the interrupt which triggers when the Pixel code enables interrupts, at which time the ISR then executes, we then set a Flag. Note the first push only sets a flag since decoding did not happen. We now need a 2nd push on the remote.

Normal code in loop() checks this Flag; if set, animations no longer are allowed to happen.
We sit in a state waiting for a 2nd IR push. A TIMER runs at this time, if it times out we go back to running animations as before.
However, if we get the second IR message (within the TIMER timeout period) decoding happens normally.
We decode the 2nd push and then do some action.
If the decode says to change to a new animation or do something else; we clear the Flag and proceed with normal animation processing i.e. with the new Pixel animation.

For visual feed back:

What I’ve done in the past is turn on all the LEDs to yellow, this is feedback to me that says code is waiting for the second push.

If the TIMER expires, the LEDs go back to their animation pattern.

As a safe solution I'd split the project and use one controller for the display control and another one for everything else. This will permanently fix the problem, else the disabled interrupts will bite you with every future update.

Using a second device is definitely not out of the question, I have no issue learning to do that. Curious though, are there devices that can process multiple tasks at once? Just doing a bit of quick searching it seems like maybe a dual-core ESP32 could? And if so, are there any that can be programmed through the Arduino IDE so that I wouldn't need to use different libraries for the LED's and IR stuff? I have no problem buying a new controller and then using this one for something else down the line.