IR Beam Break Sensors acting up

We built this interactive voting station from neopixels and have been testing it this week.

The way it works is a coin is dropped through a chute where a beam break sensor picks up the coins signature. There are 24 stations. The sensors are LOW triggered.

Every now and then, one of the stations will randomly start counting up without input, and recently, station 11 doesn’t read anything half of the time. I’m gonna crawl under it with an oscilloscope, but maybe someone has an idea what this could be? The Mega is getting 12V and the sensors are getting 5V from external source, all grounds are tied together.

VIDEO


/*
  6 strips × (8 bars × 24 px) = 6 × 192 = 1152 LEDs total
  24 sensors: one per section (6 strips × 4 sections)
  Each strip’s 4 sections map to bar pairs: (0,1), (2,3), (4,5), (6,7)
  Progress per section: 0..24. Unfilled LEDs are OFF by default.
*/

#include <Adafruit_NeoPixel.h>

// -------------------------- USER CONFIG --------------------------
#define NUM_STRIPS        6
#define NUM_BARS          8
#define PIXELS_PER_BAR    24
#define LEDS_PER_STRIP    (NUM_BARS * PIXELS_PER_BAR)

// NeoPixel type: try NEO_GRB first; if colors are wrong, try NEO_RGB / NEO_BRG / NEO_RBG / NEO_GBR / NEO_BGR
#define NEO_TYPE          (NEO_RGB + NEO_KHZ800)   // Prefer 800 kHz parts. 400 kHz will double blind time.

// Set your 6 strip pins here:
uint8_t STRIP_PINS[NUM_STRIPS] = {2, 3, 4, 5, 6, 7};

// Set your 24 sensor pins here (one per section: strip0 sec0..3, strip1 sec0..3, ...):
uint8_t COIN_PINS[24] = {
  22,23,24,25,   // strip 0 sections 0..3
  26,27,28,29,   // strip 1
  30,31,32,33,   // strip 2
  34,35,36,37,   // strip 3
  38,39,40,41,   // strip 4
  42,43,44,45    // strip 5
};

// LED frame cadence (global). We refresh at most ONE strip per frame.
const uint16_t FRAME_MS = 20;   // ~50 Hz global cadence

// Sensor signal polarity (with INPUT_PULLUP, broken = LOW)
const uint8_t  ACTIVE_LEVEL   = LOW;
const uint16_t DEBOUNCE_MS    = 3;    // short debounce on the digital input WAS 3
const uint16_t MIN_ACTIVE_MS    = 5;   // need >=12ms LOW to count (tune 8..20)
const uint16_t QUIET_BEFORE_MS  = 8;    // require >=8ms stable before a new break

static uint32_t lastStableMs[24];       // last time state became stable
static uint32_t activeStartMs[24];      // when LOW (broken) became stable
static uint32_t lastCountMs[24];        // when we last counted a coin
// ---- PIR motion settings ----
#define NUM_PIRS 3
uint8_t PIR_PINS[NUM_PIRS] = {8, 9, 10};   // <-- set your PIR pins here

// Most PIR modules output HIGH on motion. If yours is inverted, set PIR_ACTIVE_LEVEL = LOW.
const uint8_t PIR_ACTIVE_LEVEL = HIGH;

const uint32_t INACTIVITY_MS = 15UL * 60UL * 1000UL;  // 15 minutes
//const uint32_t INACTIVITY_MS = 15UL * 1000UL;  // 15 seconds

static volatile uint32_t lastMotionMs = 0;
static bool lightsOff = false;

// ------------------------ END USER CONFIG ------------------------

// ------------------------- LED OBJECTS ---------------------------
Adafruit_NeoPixel strips[NUM_STRIPS] = {
  Adafruit_NeoPixel(LEDS_PER_STRIP, STRIP_PINS[0], NEO_TYPE),
  Adafruit_NeoPixel(LEDS_PER_STRIP, STRIP_PINS[1], NEO_TYPE),
  Adafruit_NeoPixel(LEDS_PER_STRIP, STRIP_PINS[2], NEO_TYPE),
  Adafruit_NeoPixel(LEDS_PER_STRIP, STRIP_PINS[3], NEO_TYPE),
  Adafruit_NeoPixel(LEDS_PER_STRIP, STRIP_PINS[4], NEO_TYPE),
  Adafruit_NeoPixel(LEDS_PER_STRIP, STRIP_PINS[5], NEO_TYPE),
};

// ---------------------- GRADIENT / RENDER ------------------------
struct RGB { uint8_t r,g,b; };
struct Gradient { RGB c0, c1; };

static inline RGB lerpRGB(const RGB& a, const RGB& b, float t) {
  RGB o;
  o.r = (uint8_t)(a.r + (b.r - a.r) * t + 0.5f);
  o.g = (uint8_t)(a.g + (b.g - a.g) * t + 0.5f);
  o.b = (uint8_t)(a.b + (b.b - a.b) * t + 0.5f);
  return o;
}

// Per-strip, per-section gradient and direction
Gradient sectionGrad[NUM_STRIPS][4];
bool     sectionLeftToRight[NUM_STRIPS][4];

// Map section -> bar index (paired)
static inline int barFromSection(int section, bool secondInPair) {
  return section * 2 + (secondInPair ? 1 : 0);
}
static inline int ledIndex(int bar, int pixel) {
  return bar * PIXELS_PER_BAR + pixel;
}

// Renders ONE section on ONE strip, mirroring its paired bar
void renderSection(int si, int section, int value) {
  if (value < 0) value = 0;
  if (value > PIXELS_PER_BAR) value = PIXELS_PER_BAR;

  const Gradient& grad = sectionGrad[si][section];
  Adafruit_NeoPixel& strip = strips[si];

  // build gradient buffer for the filled region
  RGB buf[PIXELS_PER_BAR];
  for (int i = 0; i < PIXELS_PER_BAR; ++i) {
    if (i < value) {
      float t = (PIXELS_PER_BAR == 1) ? 0.0f : (float)i / (float)(PIXELS_PER_BAR - 1);
      buf[i] = lerpRGB(grad.c0, grad.c1, t);
    } else {
      buf[i] = {0,0,0}; // unfilled = off
    }
  }

  int barA = barFromSection(section, false);
  int barB = barFromSection(section, true);
  bool ltr = sectionLeftToRight[si][section];

  for (int i = 0; i < PIXELS_PER_BAR; ++i) {
    int pix = ltr ? i : (PIXELS_PER_BAR - 1 - i);
    uint32_t c = strip.Color(buf[pix].r, buf[pix].g, buf[pix].b);
    strip.setPixelColor(ledIndex(barA, i), c);
    strip.setPixelColor(ledIndex(barB, i), c);
  }
}

void renderStrip(int si, const uint8_t sectionMask /*4-bit: which sections to render*/) {
  for (int s = 0; s < 4; ++s) {
    if (sectionMask & (1u << s)) {
      extern int progressVals[NUM_STRIPS][4];
      renderSection(si, s, progressVals[si][s]);
    }
  }
}

// ---------------------- PROGRESS / DIRTY -------------------------
int progressVals[NUM_STRIPS][4];     // current bar fill values 0..24
int counts[24];                      // sensor-driven counts (one per section: 6×4)
static int lastCounts[24];           // for change detection
static uint8_t dirtyStrip[NUM_STRIPS];     // strip needs show
static uint8_t sectionDirty[NUM_STRIPS];   // 4-bit mask per strip: which sections changed

inline void mapCountsAndMarkDirty() {
  for (int si = 0; si < NUM_STRIPS; ++si) {
    for (int s = 0; s < 4; ++s) {
      int i = 4*si + s;
      int v = counts[i];
      if (v != lastCounts[i]) {
        lastCounts[i] = v;
        progressVals[si][s] = v;
        dirtyStrip[si] = 1;
        sectionDirty[si] |= (1u << s);
      }
    }
  }
}

// -------------------------- SENSORS ------------------------------
static uint8_t rawState[24], stableState[24], lastStable[24];
static uint32_t lastChangeMs[24];
static volatile uint8_t brokeLatch[24];  // set on first stable ACTIVE edge

void sensorsBegin() {
  for (int i = 0; i < 24; ++i) {
    pinMode(COIN_PINS[i], INPUT_PULLUP);     // or INPUT if module is push-pull
    uint8_t r = digitalRead(COIN_PINS[i]);
    rawState[i] = stableState[i] = lastStable[i] = r;
    lastChangeMs[i] = lastStableMs[i] = millis();
    activeStartMs[i] = lastCountMs[i] = 0;
  }
}


inline void pollSensors() {
  uint32_t now = millis();
  for (int i = 0; i < 24; ++i) {
    uint8_t r = digitalRead(COIN_PINS[i]);

    // Debounce to a stable value
    if (r != rawState[i]) {
      rawState[i] = r;
      lastChangeMs[i] = now;
      continue;
    }
    if (now - lastChangeMs[i] < DEBOUNCE_MS) continue;

    // Stable; check for transition relative to previous stable
    if (stableState[i] != r) {
      uint32_t prevStableAt = lastStableMs[i]; // <-- capture BEFORE updating
      uint8_t prev = stableState[i];
      stableState[i] = r;
      lastStableMs[i] = now;                   // <-- update AFTER using prevStableAt

      // Transition to ACTIVE (broken)
      if (r == ACTIVE_LEVEL && prev != ACTIVE_LEVEL) {
        // Require some quiet time before we start a candidate break
        if ((now - lastCountMs[i] >= QUIET_BEFORE_MS) &&
            (now - prevStableAt  >= QUIET_BEFORE_MS)) {
          activeStartMs[i] = now;   // candidate break begins
        } else {
          activeStartMs[i] = 0;     // too chatty; ignore
        }
      }
      // Transition to INACTIVE (unbroken): evaluate duration
      else if (r != ACTIVE_LEVEL && prev == ACTIVE_LEVEL) {
        if (activeStartMs[i]) {
          uint32_t dur = now - activeStartMs[i];
          if (dur >= MIN_ACTIVE_MS) {
            // count 0..24 (24 pixels, inclusive)
            counts[i] += 1;
            if (counts[i] > PIXELS_PER_BAR) counts[i] = 0;
            lastCountMs[i] = now;
          }
          activeStartMs[i] = 0;
        }
      }
    }
  }
}

// Consume latches: increment count mod 24
inline void consumeLatches() {
  for (int i = 0; i < 24; ++i) {
    if (brokeLatch[i]) {
      brokeLatch[i] = 0;
      counts[i] = (counts[i] + 1) % 25;
      Serial.println(i);
    }
  }
}

// ---------------------- STAGGERED REFRESH ------------------------
static uint32_t lastFrameMs = 0;
static int nextStrip = 0;
static uint32_t lastAnyShowMs = 0;
const uint32_t KEEPALIVE_MS = 600;   // force a refresh occasionally so you know it's alive

inline void staggeredRefresh() {
  uint32_t now = millis();

  // Keep-alive: nudge the next strip occasionally (in case nothing changes for a long time)
  if (now - lastAnyShowMs >= KEEPALIVE_MS) {
    dirtyStrip[nextStrip] = 1;
  }

  if (now - lastFrameMs < FRAME_MS) return;
  lastFrameMs = now;

  // Find one dirty strip to render + show this frame
  for (int attempts = 0; attempts < NUM_STRIPS; ++attempts) {
    int si = nextStrip;
    nextStrip = (nextStrip + 1) % NUM_STRIPS;

    if (dirtyStrip[si]) {
      uint8_t mask = sectionDirty[si] ? sectionDirty[si] : 0x0F; // if unknown, render all 4
      renderStrip(si, mask);
      sectionDirty[si] = 0;

      // Do the (only) show this frame
      strips[si].show();
      dirtyStrip[si] = 0;
      lastAnyShowMs = now;
      break;
    }
  }
}

// PIR STUFF
inline bool anyMotionNow() {
  for (int i = 0; i < NUM_PIRS; ++i) {
    if (digitalRead(PIR_PINS[i]) == PIR_ACTIVE_LEVEL) return true;
  }
  return false;
}

inline void turnOffAllLights() {
  // Clear all strips immediately (don’t stagger—just shut off)
  for (int si = 0; si < NUM_STRIPS; ++si) {
    for (int p = 0; p < LEDS_PER_STRIP; ++p) {
      strips[si].setPixelColor(p, 0);
    }
    strips[si].show();  // do show per strip here so it goes dark right away
  }
}

inline void wakeDisplay() {
  // Force full repaint on wake
  for (int si = 0; si < NUM_STRIPS; ++si) {
    sectionDirty[si] = 0x0F;
    dirtyStrip[si]   = 1;
  }
}


// --------------------------- SETUP -------------------------------
void setup() {
  Serial.begin(115200);

  // Init LEDs
  for (int si = 0; si < NUM_STRIPS; ++si) {
    strips[si].begin();
    strips[si].setBrightness(255);   // adjust if needed
    strips[si].show();               // clear
    // Default gradients & directions per strip/section
    // sectionGrad[si][0] = { {255,   0,   0}, {0, 0, 100} }; // red -> blue
    // sectionGrad[si][1] = { {  0, 100,   0}, {  0, 100, 50} }; // green -> cyan
    // sectionGrad[si][2] = { {  0,   0, 100}, {255,   0, 100} }; // blue -> magenta
    // sectionGrad[si][3] = { {255, 100, 100}, {0,   100, 100} }; // white -> violet
    for (int s = 0; s < 4; ++s) sectionLeftToRight[si][s] = true;
  }
    sectionGrad[0][0] = { {255,  10,  10}, {255,  10, 10} }; // strawberry
    sectionGrad[0][1] = { {255,   0,   0}, {255,   0,  0} }; // cherry
    sectionGrad[0][2] = { {255,   0,   0}, {255,  10,   0} }; // Tropical Punch
    sectionGrad[0][3] = { {255,  5,   0}, {255,   25,   0} }; // peach mango
    sectionGrad[1][0] = { {255,   10,   0}, {255, 10,   0} }; // orange
    sectionGrad[1][1] = { {255, 5,   0}, {255, 5, 0} }; // tangerine
    sectionGrad[1][2] = { {255, 5,   0}, {255, 5, 0} }; // root beer (brown is physically impossible)
    sectionGrad[1][3] = { {255, 25,   0}, {255,   80, 0} }; // pina pineapple
    sectionGrad[2][0] = { {255, 25,   0}, {255,   80, 0} }; // sunshine punch
    sectionGrad[2][1] = { {255, 80,   0}, {255, 80, 00} }; // lemonade
    sectionGrad[2][2] = { {255, 120,   0}, {255, 120, 0} }; // lemon-lime
    sectionGrad[2][3] = { {0, 100, 0} , {255,  0, 0} }; // strawberry kiwi
    sectionGrad[3][0] = { {0, 100, 0}, {0, 100, 0} }; // jamaica
    sectionGrad[3][1] = { {  0, 100,   10}, {  0, 100, 10} }; // green apple
    sectionGrad[3][2] = { {  0,  100, 100}, {255,   0, 50} }; // sharkleberry fin
    sectionGrad[3][3] = { {0, 100, 100}, {0,   100, 100} }; // great bluedini
    sectionGrad[4][0] = { {0,   0,   100}, {255,80, 0} }; // blue raspberry lemonade
    sectionGrad[4][1] = { {  0, 0,   100}, {  0, 0, 100} }; // blast off blue moon berry
    sectionGrad[4][2] = { {  0,   0, 10}, {0,   0, 100} }; // ghoul aid blackberry
    sectionGrad[4][3] = { {0, 0, 100}, {255,   0, 50} }; // magic twists switchin secret
    sectionGrad[5][0] = { {0,   0, 50}, {255, 0, 30} }; // grape
    sectionGrad[5][1] = { {255, 0,   50}, { 255, 0, 50} }; // purplesaurus rex
    sectionGrad[5][2] = { {255,   0, 80}, {255,   0, 10} }; // fruit ts wildberry tea
    sectionGrad[5][3] = { {255, 0, 40}, {255,   0, 40} }; // raspberry

  // Init counters / dirties
  for (int i = 0; i < 24; ++i) { counts[i] = 0; lastCounts[i] = -1; } // -1 forces initial paint
  for (int si = 0; si < NUM_STRIPS; ++si) { dirtyStrip[si] = 1; sectionDirty[si] = 0x0F; }

  sensorsBegin();

  // Initial full render so you see something immediately
  for (int si = 0; si < NUM_STRIPS; ++si) {
    for (int s = 0; s < 4; ++s) progressVals[si][s] = counts[4*si + s];
    renderStrip(si, 0x0F);
    strips[si].show();
  }
  lastAnyShowMs = millis();
  lastFrameMs = 0; // so first stagger tick can occur soon

    // ---- PIR init ----
  for (int i = 0; i < NUM_PIRS; ++i) {
    pinMode(PIR_PINS[i], INPUT); // Most PIR breakouts are push-pull. If needed, use INPUT_PULLUP and flip PIR_ACTIVE_LEVEL.
  }
  lastMotionMs = millis();

}

// ---------------------------- LOOP -------------------------------
void loop() {
  // 0) Motion handling: update lastMotionMs and handle sleep/wake
  uint32_t now = millis();
  if (anyMotionNow()) {
    lastMotionMs = now;
    Serial.println(lastMotionMs);
    if (lightsOff) {          // wake from sleep
      lightsOff = false;
      wakeDisplay();          // mark everything dirty so it repaints
    }
  } else {
    if (!lightsOff && (now - lastMotionMs >= INACTIVITY_MS)) {
      lightsOff = true;
      turnOffAllLights();     // immediate blackout
    }
  }
  
  // 1) Poll sensors quickly and latch broken edges
  pollSensors();

  // 2) Consume latches -> update counts (mod 24)
  //consumeLatches();

  // 3) Map counts to progress & mark dirty only where changed
  mapCountsAndMarkDirty();

  // 4) Staggered refresh: at most ONE .show() per frame
  //Serial.println(lightsOff);
  if (!lightsOff) {
    staggeredRefresh(); // your one-strip-per-frame updater
  }

  // Optional: small delay to keep loop friendly; not required
  // delay(0);
}

// ---------------------- OPTIONAL HELPERS -------------------------
// Call to tweak a gradient on the fly
void setSectionGradient(int stripIndex, int section, RGB startRGB, RGB endRGB) {
  if (stripIndex < 0 || stripIndex >= NUM_STRIPS) return;
  if (section < 0 || section > 3) return;
  sectionGrad[stripIndex][section].c0 = startRGB;
  sectionGrad[stripIndex][section].c1 = endRGB;
  // Mark dirty for redraw
  sectionDirty[stripIndex] |= (1u << section);
  dirtyStrip[stripIndex] = 1;
}

// Flip direction for wiring/layout quirks
void setSectionDirection(int stripIndex, int section, bool leftToRight) {
  if (stripIndex < 0 || stripIndex >= NUM_STRIPS) return;
  if (section < 0 || section > 3) return;
  sectionLeftToRight[stripIndex][section] = leftToRight;
  sectionDirty[stripIndex] |= (1u << section);
  dirtyStrip[stripIndex] = 1;
}

Thanks!
J

Can you post an annotated schematic and note the length of the wires going to each sensor? This sounds like classic EMI interference. Can you also post a photo of the wiring of the sensors?

1 Like

CLUES
PIR - learn how they really work
Bounce - use a library

What happened when the failing unit was replaced with a known good unit? If you have not done this, it should be done before even asking the forum.

PIR sensors are not the problem. Those are being used for a timeout function that shuts the machine off after a period of inactivity. The beam break IR sensors are the ones we're talking about.

-J

I have more on order and will see what result this test yields.

I ask the forum because I want to diagnose in parallel to speed things up in the event that doesn't fix it. This feels like one of those trouble shooting issues that will be a long journey.

-J

I am working on drawing one up. Total signal wire length is about 12 ft of 26 AWG ribbon cable. All connections are well soldered and heat shrunk. Each sensor and emitter gets power in parallel from a fatter 22 AWG wire. All wires are held inside a plastic loom.

-J

Without you providing much more information, there is not much anyone can do to help. Make it a rule for any future designs you do, to make things easy to repair, easy to report errors and to always have spares to test with.

Very bad design! Ribbon cables always need to terminate with connectors!

1 Like

So tell me without having a schematic how did you wire it up?
Without a schematic how do you know you wired it up correctly, that's you had nothing to compare it to did you?

If you are going to use that technique you need to wire a ground connection on each side of the signal you need to carry.
so that is
ground, signal1, ground, ground signal2 ground, ground, signal 3 .......and so on. If you want to stand any chance of it working. Even then it might not go the distance.

Failing that then the best way to go any large distance is to use twisted wire and buffers like this

This is good for over 60 feet.

1 Like
pinMode(COIN_PINS[i], INPUT_PULLUP);     // or INPUT if module is push-pull

Using long wires with internal pull-up can be sensitive to noise pickup.
Use a low resistance external pull-up or better, an RC filter.

1 Like

There is a DB25 connector on the other end.

We designed the system and it isn't that complicated. Most of the wiring is power distribution and the rest is signal wires going straight from the sensor output to the Arduino pins.

Is the purpose of the ground wires to absorb interference or something? I have heard this suggestion elsewhere and I am curious how this is supposed to work.

Crimped pins or soldered pins? Soldering pins on any DB connector is quite difficult and even harder to check for problems. Did you put heat shrink over the connections?

To be honest you have a real mess on your hands. Who is we and what is this project for. What environment will it reside in? The best you can get from us at this point is a guess from assumptions. Without the annotated schematic you are very close to being on your own.

To protect a projects electronics from electromagnetic interference there are several strategies you can employ. EMI can cause malfunctions or degradation in performance if not properly mitigated. Here are some approaches you can consider:

1. Shielding

  • Shielded cables: Use cables with shielding (like braided metal or foil wraps) to reduce the amount of EMI they emit or pick up.

  • Enclosures: Place sensitive electronics in metal or conductive enclosures (Faraday cages) to block external electromagnetic fields.

2. Grounding

  • Proper grounding: Ensure that all electronic components and their enclosures are properly grounded to prevent stray electromagnetic signals from affecting them. Grounding can direct EMI away from sensitive components.

3. Ferrite Beads

  • Ferrite beads: Install ferrite beads on cables that run to and from sensitive electronics. These components help suppress high-frequency EMI by dissipating it as heat.

4. Twisted Pair Cables

  • Twisted pair wiring: If you're running wires, use twisted pair cables for signal lines. The twisting helps cancel out EMI that would otherwise be picked up by the wires.

5. Capacitors and Filters

  • EMI filters: Install EMI filters (low-pass, high-pass, or band-stop) on power supply lines or signal lines to prevent high-frequency noise from reaching sensitive electronics.

  • Capacitors: Capacitors between power lines and ground can help smooth out high-frequency noise.

6. Distance from EMI Sources

  • Separation: Keep sensitive electronics as far away as possible from sources of EMI, such as motors, ignitions, and power cables.

7. Proper Cable Routing

  • Avoid running signal cables near power lines, ignition systems, or any high-current cables to reduce the possibility of picking up interference.

8. Use EMI-Resistant Components

  • Whenever possible, use components that are specifically designed to be resistant to EMI, such as EMI-hardened sensors, controllers, and processors.

9. Suppress Noise at the Source

  • Suppress ignition noise: Use resistor-type spark plugs or ignition leads with built-in suppression to reduce EMI from the bike’s engine and ignition system.

  • DC-DC converters: For bikes with electric systems, install noise-suppressed DC-DC converters to minimize EMI from power supplies.

10. Suppress Noise at the SourceGood Read: https://www.nutsvolts.com/magazine/article/September2015_HamWorkbench ?

By implementing a combination of these techniques, you can significantly reduce the impact of EMI on your projects electronics. If you need help with a specific part or scenario, feel free to provide more details!

Soldered with heat shrink. Good connection has been confirmed, and if they were not making solid connection this wouldn't cause this issue. I can completely disconnect the cable and nothing happens.

-J

Maybe the wiring should use differential pairs, as has been suggested. You could reduce the wiring complexity by using a lot of little processors and an RS-485 network. But you undoubtedly don’t want to rewire the whole thing!

What is the process by which the Arduino reads the data? I’m asking if it’s just a single test of each incoming line, or if you’re running up a count of how many times in succession each sensor sees an object passing. If you count over some period of time, then you can hope to reduce the effects of noise by looking for a consistent result. Obviously this has to depend on the size of the object and the speed it travels at, but there ought to be some interval which the processor can identify and say “That was a real object” versus “That was probably noise”. You might also count the number of incidents identified as noise, and use it to check the integrity of the system. If there’s a lot of false activation, you really should work to reduce it.

From the video, the project looks nicely constructed.

The code has the appearance of having been refactored several times, probably by AI. For example there are relics like volatile uint8_t brokeLatch[24]; which seem to be unused and suggests that an interrupt source, probably a timer, was used at one time to poll the sensors.

Anyway, if you are getting erratic results from the coin detection sensors, you should write a much simpler program, for test purposes, which just processes the coin sensors and maintains/prints out the detection events. That is all the code to handle the led strip, PIR sensors etc. should be stripped out. Also the debouncing logic on the coin detection sensors could be much less complex.

Also, post details of the actual coin detection sensors in use.

Apart from the electrical glitches which have been suggested, the erratic behaviour could also be due to timing issues interfering with the polling of the sensors. If there is too much activity in the loop() some detections may be missed. Also IR leakage from one sensor could possibly interfere with another sensor.

2 Likes

See post #11 for a solution.