LED-ring animation and rfid scan functionality in one project

Hey all, I need a little bit of help with my project:
I want to have a smooth gradient animation on a 16 pixel LED ring and when an RFID card is read it should blink green 3 times and then go back to the animation.
My setup is the following: Arduino Nano ESP32, 16px WS2812B ring, NFC Module (PN532 V3).

I've built the led ring animation and RFID scan functionality on it's own and then tried to combine the both, but the animation is not that smooth anymore (because of checking the RFID-Scanner).
The issue lies in line 55 if (!cardDetected && currentTime - cardCheckTime > (800 - random(100, 400))) {
Is there a better way to go about this? Can someone please advice and explain how I could solve this?

Code pastebin here: arduino LED + RFID - Pastebin.com

Thanks!

Welcome to the forum

Please post your full sketch, using code tags when you do

In my experience the easiest way to tidy up the code and add the code tags is as follows
Start by tidying up your code by using Tools/Auto Format in the IDE to make it easier to read. Then use Edit/Copy for Forum and paste what was copied in a new reply. Code tags will have been added to the code to make it easy to read in the forum thus making it easier to provide help

What animation?

Upload your formatted code here, using the < CODE >.

Sorry, I though it's fine via pastebin.
Here is the code:

#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <PN532_I2C.h>
#include <PN532.h>
#include <NfcAdapter.h>

#define LED_RING_PIN D2
#define NUMPIXELS 16

Adafruit_NeoPixel pixels(NUMPIXELS, LED_RING_PIN, NEO_GRB + NEO_KHZ800);
PN532_I2C pn532i2c(Wire);
PN532 nfc(pn532i2c);

uint32_t greenColor = pixels.Color(0, 255, 0);
uint32_t color1 = pixels.Color(255, 69, 0);
uint32_t color2 = pixels.Color(75, 0, 130);
uint32_t color3 = pixels.Color(0, 255, 255);

const int MIN_BRIGHTNESS = 5;
const float MAX_BRIGHTNESS_SCALE = 0.8;
const int pixelMapping[NUMPIXELS] = {0, 1, 2, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10, 9, 8};

int flashCount = 3;
int ledPin = 13;
unsigned long lastAnimationTime = 0;
const int animationInterval = 20;

bool cardDetected = false;
unsigned long cardCheckTime = 0;
unsigned long cardDetectedTime = 0;
const int blinkDuration = 500;
int blinkCount = 0;
bool blinkState = false;

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

  nfc.begin();
  if (!nfc.getFirmwareVersion()) {
    Serial.println("Didn't find PN53x board");
    while (1);
  }

  Serial.println("Waiting for an ISO14443A card");
  nfc.SAMConfig();

  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
}

void loop() {
  unsigned long currentTime = millis();

  if (!cardDetected && currentTime - cardCheckTime > (800 - random(100, 400))) {
    // Serial.println("Checking .......");
    checkRFID();
    cardCheckTime = currentTime;
  }

  if (cardDetected) {
    // Serial.println("handle");
    handleCardDetected(currentTime);
  }

  // Serial.println("done...");

  if (!cardDetected && currentTime - lastAnimationTime >= animationInterval) {
    // Serial.print("x");
    runGradientAnimation();
    lastAnimationTime = currentTime;
  }
}

void checkRFID() {
  uint8_t uid[7];
  uint8_t uidLength;

  if (nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 1)) {
    Serial.println("Found a card!");
    Serial.print("UID Length: "); Serial.print(uidLength, DEC); Serial.println(" bytes");
    Serial.print("UID Value: ");
    for (uint8_t i = 0; i < uidLength; i++) {
      Serial.print(" 0x"); Serial.print(uid[i], HEX);
    }
    Serial.println("");

    cardDetected = true;
    cardDetectedTime = millis();
    blinkCount = 0;
    blinkState = true;
    setRingColor(greenColor);
  }
}

void handleCardDetected(unsigned long currentTime) {
  if (currentTime - cardDetectedTime >= blinkDuration) {
    cardDetectedTime = currentTime;
    blinkState = !blinkState;
    if (blinkState) {
      setRingColor(greenColor);
    } else {
      setRingColor(0);
      blinkCount++;
    }

    if (blinkCount >= flashCount) {
      Serial.println("reset");
      cardDetected = false;
      delay(500);
    }
  }
}

void setRingColor(uint32_t color) {
  for (int i = 0; i < NUMPIXELS; i++) {
    pixels.setPixelColor(i, color);
  }
  pixels.show();
}

void runGradientAnimation() {
  const int cycleTime = 3000;
  unsigned long currentTime = millis();
  float wavePosition = (currentTime % cycleTime) / (float)cycleTime;

  for (int i = 0; i < NUMPIXELS; i++) {
    float pixelFraction = (float)pixelMapping[i] / (NUMPIXELS - 1);
    uint32_t color;

    if (pixelFraction < 0.5) {
      color = interpolateColor(color1, color2, pixelFraction * 2);
    } else {
      color = interpolateColor(color2, color3, (pixelFraction - 0.5) * 2);
    }

    float angle = 2.0 * PI * (wavePosition + (float)i / NUMPIXELS);
    float brightnessFraction = (sin(angle) + 1) / 2;
    brightnessFraction = pow(brightnessFraction, 2.5);
    float brightness = brightnessFraction * MAX_BRIGHTNESS_SCALE;
    brightness = MIN_BRIGHTNESS / 255.0 + brightness * (1.0 - MIN_BRIGHTNESS / 255.0);

    uint32_t adjustedColor = adjustBrightness(color, brightness);
    pixels.setPixelColor(i, adjustedColor);
  }
  pixels.show();
}

uint32_t interpolateColor(uint32_t color1, uint32_t color2, float fraction) {
  uint8_t r = ((color1 >> 16) & 0xFF) + fraction * (((color2 >> 16) & 0xFF) - ((color1 >> 16) & 0xFF));
  uint8_t g = ((color1 >> 8) & 0xFF) + fraction * (((color2 >> 8) & 0xFF) - ((color1 >> 8) & 0xFF));
  uint8_t b = (color1 & 0xFF) + fraction * ((color2 & 0xFF) - (color1 & 0xFF));
  return pixels.Color(r, g, b);
}

uint32_t adjustBrightness(uint32_t color, float brightnessFactor) {
  uint8_t r = ((color >> 16) & 0xFF) * brightnessFactor;
  uint8_t g = ((color >> 8) & 0xFF) * brightnessFactor;
  uint8_t b = (color & 0xFF) * brightnessFactor;
  return pixels.Color(r, g, b);
}

Excellent.

Just letting you know, nobody here likes chasing pastebin links (any link to unknown info).

Something has to be interrupted to let the other do its job. There is no simultaneous anything.

Make your main animation non-blocking (the way you are using millis()), running as the main code. Monitor your card reader to determine detected/pass/fail, and call non-blocking animations.

[edit] If your card reader changes the level of the DIO pin when activity is detected, maybe try using the card reader on (Uno/Nano) pin 2 or pin 3 so you can use a hardware interrupt to show activity on the card reader, and then go to the RFID reading function.

#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <PN532_I2C.h>
#include <PN532.h>
#include <NfcAdapter.h>

#define LED_RING_PIN D2
const int NUMPIXELS = 16;

Adafruit_NeoPixel pixels(NUMPIXELS, LED_RING_PIN, NEO_GRB + NEO_KHZ800);
PN532_I2C pn532i2c(Wire);
PN532 nfc(pn532i2c);

const uint32_t greenColor = pixels.Color(0, 255, 0);

const int flashCount = 3;
const int animationInterval = 20;
const int blinkDuration = 500;

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

  nfc.begin();
  if (!nfc.getFirmwareVersion()) {
    Serial.println("Didn't find PN53x board");
    while (1);
  }

  Serial.println("Waiting for an ISO14443A card");
  nfc.SAMConfig();
}

void loop() {
  static int Gradient = 0;
  checkRFID();
  for (int i = 0; i < NUMPIXELS; i++) {
    pixels.setPixelColor(i, pixels.ColorHSV(uint8_t(i * 255 / NUMPIXELS + Gradient)));
  }
  Gradient++;
  Gradient %= 256;
  delay(animationInterval );
}

void checkRFID() {
  uint8_t uid[7];
  uint8_t uidLength;

  if (nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 1)) {
    Serial.println("Found a card!");
    Serial.print("UID Length: "); Serial.print(uidLength, DEC); Serial.println(" bytes");
    Serial.print("UID Value: ");
    for (uint8_t i = 0; i < uidLength; i++) {
      Serial.print(" 0x"); Serial.print(uid[i], HEX);
    }
    Serial.println();

    handleCardDetected();
  }
}

void handleCardDetected() {
  for (int c = 0; c < flashCount; c++) {
    for (int i = 0; i < NUMPIXELS; i++) {
      pixels.setPixelColor(i, greenColor);
    }
    pixels.show();
    delay(blinkDuration);
    pixels.clear();
    pixels.show();
    delay(blinkDuration);
  }
}

Thanks for the suggestion, but I don't really follow.

Can you please elaborate what you mean with:

the way you are using millis()), running as the main code. Monitor your card reader to determine detected/pass/fail, and call non-blocking animations.

Is there a source I can read up on using the level change of the DIO pin (do you mean the SDA data pin?) as interrupt? The Module communicates via i2c.

Your code is using millis() as a timer, and when the timer reaches a value, your code does something. That is a form of "non-blocking" code. As an example of "blocking" code, a "for()" loop or "delay()" function are blocking. That is to say, nothing will happen until the function/loop is finished. In non-blocking, other events can happen while the timer counts up to its value.

As far as the interrupts, search "arduino uno interrupt" and the first link should do the trick.