UART, I2C LCD, NeoPixle and strange serial behaviour

TLDR at bottom

I'm making a game that runs off an Arduino UNO and I'm having quite a few issues around all of the different types of serial devices that I'm using. ChatGPT can only get so far and at this point the projects big enough that neither of us know what's going on! I get the feeling my problem is around interrupts & timing and I'm starting to hit limits of the Arduino but a little bit of guidance would be greatly appreciated.

I have:

  • 9 WS2812 LED's (#include <Adafruit_NeoPixel.h>)
  • an adafruit soundboard controlled via sireal (#include <AltSoftSerial.h>)
  • An I2C LCD (#include <LiquidCrystal_I2C.h>)
  • An ESC (#include <Servo.h>)

Everything was working fine however as soon as I added the LCD the USB serial went funny. see the relevant code below:

// top of file
#include <Adafruit_NeoPixel.h>
#include <Servo.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#include <AltSoftSerial.h>

LiquidCrystal_I2C lcd(0x27, 16, 2) ;
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
AltSoftSerial soundSerial(SOUND_RX_PIN, SOUND_TX_PIN);

void playSuccess(){
  soundSerial.println("#02");
}

void setup() {

  Serial.begin(115200); // for debugging
  soundSerial.begin(9600); 

  // NeoPixel init and colors
  strip.begin();
  strip.show(); // all off

  lcd.init( ) ; // Starts LCD
  lcd.clear(); //C1ears any characters on the LCD display
  lcd.backlight( ) ; // Make sure backlight is on

  lcd.setCursor (4, 0) ;
  lcd.print("Hello!");
}

void loop(){
  Serial.println("Initialization complete");
  Serial.println("Wating for command");
  while (!isGameEnabled){
    // For testing / manual triggers via serial console:
    if (Serial.available()){
      String cmd = Serial.readStringUntil('\n');
      cmd.trim();
      if (cmd.equalsIgnoreCase("init")){
        setupGame();
      } else if (cmd.equalsIgnoreCase("start")){
        startGame();

      } else if (cmd.equalsIgnoreCase("scan")){
        bool p[9]; scanMatrix(p);
        Serial.print("Presence: ");
        for (uint8_t i=0;i<9;i++) Serial.print(p[i] ? "1" : "0");
        Serial.println();

      } else if (cmd.equalsIgnoreCase("light")){
        Serial.println("Starting rrggbb flash");
        flashAll(COLOR_RED, 2, 200, 200);
        delay(500);
        flashAll(COLOR_GREEN, 2, 200, 200);
        delay(500);
        flashAll(COLOR_BLUE, 2, 200, 200);
      } 
      else if (cmd.equalsIgnoreCase("sucsess")){
        playSuccess();
      } 
      else {
        Serial.println("Commands: init | start | scan | light | E_on | E_off | D_on | D_off | pop | sucsess | list");
      }

// and so on

Once everything has been initialised an the Arduino falls into the main loop section, it sits in a while loop and remains there until the game has been fully set up. When in this while loop, it should be listening for commands sent by the connected computer which let you manually trigger certain functions of the game for diagnosing.
If I actually start the game, all the game logic seems to work fine: the display updates, the sound works and the LED's work as expected. The only thing that is broken is this initial while loop. Whenever I send a command from my laptop, it always chooses the first option (which calls the setupGame() function) no matter what command I type.

GPT seems to suggest this is because software serial and NeoPixel interfere with timers and interrupts, so a lot of the serial commands may be getting corrupt. But I still don't understand why this would mean the first if statement evaluates to true and it wouldn't just kick you out to the default case at the bottom.

TLDR: my project has quite a few different types of serial devices and things which rely on timers. When I added an I2C LCD to my project, the USB serial commands stopped working. If I remove 'lcd.init( )' everything returns to normal. Something about adding the I2C LCD is adding too much disruption to the rest of the serial devices.

It's possible I might have missed some important information, there's over 500 lines of code which I've had to try and condense. Thanks for your help let and me know if you need to know anything more about my setup.

Welcome to the forum

Thank you for using code tags when posting your code but where is the rest of it ?

For instance, where are SOUND_RX_PIN and SOUND_TX_PIN declared or #defined ?

You could Serial.println(cmd) immediately below the above code so you can see what cmd it’s processing

just a blank character, nothing there

Full code can be found here: GitHub - tobywhiting10/Arduino-game

This probably won't be kept updated but just so you can see what's going on

I've omitted quite a lot just to keep it brief (that and it's been a bit cobbled together so is quite messy). My issue isn't necessarily with the pin assignments. Everything was working up until I added lcd.init(). see my previous comment for a link to the full code if you're interested

What exactly do you mean by a blank character ? Is it a space, Carriage Return or Linefeed, something else ? What is the length of cmd ?

Print a ">" immediately before cmd and a "<" immediately after it to give you a better idea about what is being received

Where are these defined?

My and so on works. Need to see yours.

// Serial.println("Wating for command");
// while loop

    if (Serial.available()){
      String cmd = Serial.readStringUntil('\n');
      cmd.trim();
      if (cmd.equalsIgnoreCase(("init")){
        Serial.println("This is the problem locaton!");
        Serial.print(">");
        Serial.print(cmd);
        Serial.println("<");
        setupGame();
      } else if (cmd.equalsIgnoreCase("start")){
        startGame();
      } 
//and so on

The Output:

Initialization complete
Wating for command
This is the problem locaton!
><
setupGame: scanning items... (this is from startGame();)

I can post the full code here if you would like, if not see the GitHub link

Make a psedo output of what you expect. (Label it to not be confused with actual)

Yes, please. This is standard.

You have screwed up the braces and parentheses in that code section. This is easier to see if you reformat the code

    if (Serial.available())
    {
        String cmd = Serial.readStringUntil('\n');
        cmd.trim();
      if (cmd.equalsIgnoreCase(("init")){
            Serial.println("This is the problem locaton!");
            Serial.print(">");
            Serial.print(cmd);
            Serial.println("<");
            setupGame();
      } else if (cmd.equalsIgnoreCase("start")){
            startGame();
      }
      //and so on
    }

It looks like it should be

    if (Serial.available())
    {
        String cmd = Serial.readStringUntil('\n');
        cmd.trim();
        if (cmd.equalsIgnoreCase("init"))
        {
            Serial.println("This is the problem locaton!");
            Serial.print(">");
            Serial.print(cmd);
            Serial.println("<");
            setupGame();
        }
        else if (cmd.equalsIgnoreCase("start"))
        {
            startGame();
        }
        //and so on
    }

For the full code see below. For the problem area, refer to line 498

When the Arduino boots, finishes it's set up function and initialises everything, it enters the main loop and is initially captured in a while loop.
This while loop continues indefinitely until 'gameEnabled' becomes true, which happens once all the setup conditions are met.
While the Arduino is in this while loop, it should respond to a series of commands sent by the serial monitor. When there is data in the serial buffer, a series of if else statements are used to match the command and then call the necessary function (flash test pattern on the lights, manually trigger sounds, etc). In all the examples I have given so far, I have been giving garbage commands which should not be recognised, resulting in the if else statement triggering the base case which should print to the console: "Commands: init | start | scan | light...." as a way of saying the command was not recognised.
This is not what actually happens. The very first if statement in this large if else tree is always evaluated to true regardless of the command you enter.

/* Game controller for Arduino Uno
   - 3x3 micro switch matrix (3 outputs pulsed, 3 inputs read)
   - WS2812 (Adafruit_NeoPixel) LEDs (one per item position)
   - Adafruit Sound Board connected over SoftwareSerial
   - Implements setupGame() and startGame() per user spec
*/

#include <Adafruit_NeoPixel.h>
#include <NeoSWSerial.h>
#include <Servo.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>


LiquidCrystal_I2C lcd(0x27, 16, 2) ;
//
// === CONFIGURATION ===
//

// Matrix pins (change to match wiring)
const uint8_t ROW_PINS[3] = {5, 6, 7};     // outputs (pulsed)
const uint8_t COL_PINS[3] = {A0, A1, A2};     // inputs (read)

// NeoPixel
const uint8_t LED_PIN = 4;
const uint16_t NUM_LEDS = 9;
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);

// Sound board (SoftwareSerial)
const uint8_t SOUND_RX_PIN = 8;  // UNO pin reading from sound board TX (unused here)
const uint8_t SOUND_TX_PIN = 9;  // UNO pin sending to sound board RX

NeoSWSerial soundSerial(SOUND_RX_PIN, SOUND_TX_PIN);
// other pins
const uint8_t ESC_PIN = 10;          // local
const uint8_t DISPENCER_PIN = 11;     // local
const uint8_t POWER_PIN = 12;         // local
const uint8_t LID_BTN_PIN = A3;
const uint8_t RESET_BTN_PIN = 3;
const uint8_t ENABLE_PIN = 2;

Servo ESC;
static uint16_t delays[300];
// Timing & animation parameters
const uint16_t SPIN_TOTAL_MS = 5000;     // spin slow-down period (5 seconds)
const uint16_t SPIN_MIN_DELAY = 12;      // minimum per-step delay (fast)
const uint16_t SPIN_MAX_DELAY = 500;     // maximum per-step delay (end)
const uint16_t POST_ALL_IN_PLACE_DELAY = 2000; // 2s after all items placed
const uint16_t SHORT_DELAY_AFTER_REMOVAL = 2000; // short pause before spin starts
const uint8_t MAX_SAFE_REMOVALS = 6; // when 6th removal cause failure behavior

// Colors
uint32_t COLOR_OFF;
uint32_t COLOR_ORANGE;
uint32_t COLOR_GREEN;
uint32_t COLOR_RED;
uint32_t COLOR_BLUE;

// =========================================================================
// State
bool lastPresence[9];         // presence at last scan
bool removedPermanent[9];     // items already removed and set green
uint8_t removedCount = 0;     // how many unique items removed (and turned green)
volatile bool isGameEnabled;
bool gameReady;
bool escPowered;
//
// =========================================================================

void disableSystem(){
  isGameEnabled=false;
}

// Helper: map row, col to index 0..8
inline uint8_t idx(uint8_t row, uint8_t col){
  return row*3 + col;
}

// Scan matrix and return presence[] (true if switch detects item present)
// We pulse each row HIGH briefly and read columns. If column reads HIGH when row is high,
// that switch is closed (item present).
void scanMatrix(bool presence[9]){
  //Serial.println("Scaning matrix");
  // start with everything false
  for (uint8_t i=0;i<9;i++) presence[i] = false;

  for (uint8_t r=0; r<3; r++){
    // pulse row HIGH
    digitalWrite(ROW_PINS[r], HIGH);
    delayMicroseconds(120); // small settle time

    for (uint8_t c=0; c<3; c++){
      //Serial.print("Row: "); Serial.print(r); Serial.print(", Col: "); Serial.println(c);
      int val = digitalRead(COL_PINS[c]);
      // We assume wiring provides HIGH to the column input when switch closed and row HIGH.
      // If your wiring uses pull-ups/active-low, invert the test here.
      if (val){
        presence[idx(r,c)] = true;
      }
      //Serial.print("Val: "); Serial.println(val); Serial.println();
      delayMicroseconds(10);
    }

    // set row back LOW
    digitalWrite(ROW_PINS[r], LOW);
    delayMicroseconds(30);
  }
  //Serial.print("Got: ");
  //for (uint8_t i=0;i<9;i++) {Serial.print(presence[i]); }
  //Serial.println();
}


void setup() {
  Serial.begin(115200); // for debugging
  soundSerial.begin(9600); // default; adapt to your soundboard's baud if needed

  Serial.println("---------------------------------");
  Serial.println("  Starburst Projectile System");
  Serial.println("---------------------------------");
  delay(100);
  Serial.println("Begin init");

  ESC.attach(ESC_PIN);
  ESC.write(0);

  isGameEnabled=false;
  gameReady=false;
  escPowered=false;

  // NeoPixel init and colors
  strip.begin();
  strip.show(); // all off
  COLOR_OFF = strip.Color(0,0,0);
  COLOR_ORANGE = strip.Color(90, 200, 0); // tweak if needed
  COLOR_GREEN = strip.Color(255, 0, 0);
  COLOR_RED = strip.Color(0, 255, 0);
  COLOR_BLUE = strip.Color(0, 0, 255);

  Wire.begin();
  lcd.init( ) ; // Starts LCD
  lcd.clear(); //C1ears any characters on the LCD display
  lcd.backlight( ) ; // Make sure backlight is on

  lcd.setCursor (4, 0) ;
  lcd.print("Starburst");
  lcd.setCursor (0, 1) ;
  lcd.print("Projectile System");

  // Matrix pins
  for (uint8_t i=0;i<3;i++){
    pinMode(ROW_PINS[i], OUTPUT);
    digitalWrite(ROW_PINS[i], LOW); // idle LOW
    pinMode(COL_PINS[i], INPUT);    // we'll read these (pulled by rows when pulsed)
  }

  // initialize states
  for (uint8_t i=0;i<9;i++){
    lastPresence[i] = false;
    removedPermanent[i] = false;
  }

  pinMode(POWER_PIN, OUTPUT);
  pinMode(DISPENCER_PIN, OUTPUT);
  pinMode(RESET_BTN_PIN, INPUT_PULLUP);
  pinMode(LID_BTN_PIN, INPUT_PULLUP);
  pinMode(ENABLE_PIN, INPUT_PULLUP);

  digitalWrite(POWER_PIN, HIGH);
  digitalWrite(DISPENCER_PIN, HIGH);

  attachInterrupt(digitalPinToInterrupt(ENABLE_PIN), disableSystem, RISING);

  Serial.println("Performing initial scan");
  bool presence[9];
  uint8_t countPresent = 0;
  scanMatrix(presence);
  for (uint8_t i=0;i<9;i++){
    if (presence[i]){
      countPresent++;
    } 
  }
  if (countPresent==NUM_LEDS){
    Serial.println("All items detectes at boot, game in ready state");
    gameReady=true;
  }

  // Let the hardware settle
  delay(200);
}

// NeoPixel helpers
void setLedColor(uint8_t i, uint32_t color){
  if (i >= NUM_LEDS) return;
  strip.setPixelColor(i, color);
}

void showAllOff(){ for (uint8_t i=0;i<NUM_LEDS;i++) strip.setPixelColor(i, COLOR_OFF); strip.show(); }
void showAllOrange(){ for (uint8_t i=0;i<NUM_LEDS;i++) strip.setPixelColor(i, COLOR_ORANGE); strip.show(); }
void showAllGreen(){ for (uint8_t i=0;i<NUM_LEDS;i++) strip.setPixelColor(i, COLOR_GREEN); strip.show(); }
void showAllRed(){ for (uint8_t i=0;i<NUM_LEDS;i++) strip.setPixelColor(i, COLOR_RED); strip.show(); }

// Sound senders — adapt to your board's protocol. For many boards you either:
// - send a small text command over serial, or
// - toggle a dedicated trigger pin, or
// - send numeric bytes. Replace bodies below as appropriate for your hardware.
void playPop(){
  // Example: send "POP\n" — replace with the correct command for your board.
  soundSerial.println("#01");
  // If your board requires a single byte ID, use soundSerial.write(byteVal);
}

void playSuccess(){
  soundSerial.println("#02");
}

void playFailure(){
  soundSerial.println("#03");
}

void playTrackNumber(uint8_t track){
  // Generic helper for a numbered track if that is how your board works:
  // Example: sending "T3\n" to play track 3. Replace accordingly.
  soundSerial.print("T");
  soundSerial.println(track);
}

// startMotor stub — user requested that on failure we call start motor after 3s.
// Implement actual motor control in this function.
void startMotor(){
  Serial.println("startMotor(): called (implement motor start here)");
  // Example: digitalWrite(MOTOR_PIN, HIGH);
}

// Helper: flash all LEDs color n times
void flashAll(uint32_t color, uint8_t times=3, uint16_t onMs=160, uint16_t offMs=120){
  for (uint8_t t=0;t<times;t++){
    for (uint8_t i=0;i<NUM_LEDS;i++) setLedColor(i, color);
    strip.show();
    delay(onMs);
    showAllOff();
    delay(offMs);
  }
}

// ========================================================================
// Function 1: setupGame()
// - scan matrix, set LEDs orange for positions that have item
// - when all 9 items are in place, wait 2s, play sound, and turn all LEDs off
void setupGame(){
  Serial.println("setupGame: scanning items...");

  // initialize states
  for (uint8_t i=0;i<9;i++){
    lastPresence[i] = false;
    removedPermanent[i] = false;
  }
  removedCount=0;

  bool presence[9];

  uint8_t countPresent = 0;

  while (!(countPresent>=9)){
    scanMatrix(presence);
    countPresent = 0;
    for (uint8_t i=0;i<9;i++){
      lastPresence[i] = presence[i]; // store baseline
      if (presence[i]){
        setLedColor(i, COLOR_ORANGE);
        countPresent++;
      } else {
        setLedColor(i, COLOR_OFF);
      }
    }
    strip.show();

    Serial.print("Items present: ");
    Serial.println(countPresent);
    delay(200);
  }

  
  Serial.println("All 9 in place.");
  flashAll(COLOR_GREEN, 2, 200, 200);
  playSuccess(); // Use success sound (adapt as needed)
  delay(POST_ALL_IN_PLACE_DELAY);
  showAllOff();
  gameReady=true;

}


// Spin animation that starts fast and slows over SPIN_TOTAL_MS,
// playing pop sound on each step, and ending exactly on targetIndex.
// If failure==true, flash red & play failure; else flash green & play success.
// Returns when finished and updates removedPermanent/removedCount if success.
// Spin animation without storing a 300-element delay table.
// Computes each delay step on demand, saving ~600 bytes of SRAM.
void runSpinAndResolve(uint8_t startIndex, uint8_t targetIndex, bool failure){
    const uint16_t baseCycles = 6;
    uint16_t totalSteps = baseCycles * NUM_LEDS;

    uint8_t offset = (targetIndex + NUM_LEDS - (startIndex % NUM_LEDS)) % NUM_LEDS;
    totalSteps += offset;

    if (totalSteps > 300) totalSteps = 300;

    uint8_t cur = startIndex;

    // Track total time for dynamic scaling
    const float totalMs = SPIN_TOTAL_MS;
    float elapsed = 0;

    for (uint16_t step = 0; step < totalSteps; step++){
        float t = (float)step / (float)totalSteps;       // 0..1
        float ease = 1.0f - (1.0f - t)*(1.0f - t);        // quadratic ease-out
        float d = SPIN_MIN_DELAY + (SPIN_MAX_DELAY - SPIN_MIN_DELAY) * ease;

        // scale delay so entire spin lasts exactly SPIN_TOTAL_MS
        float remainingSteps = (float)(totalSteps - step);
        float remainingTime = totalMs - elapsed;
        float scaledDelay = remainingTime / remainingSteps;
        if (scaledDelay < 1) scaledDelay = 1;

        elapsed += scaledDelay;

        // Step spinner
        cur = (cur + 1) % NUM_LEDS;

        // Draw LEDs (persistent green + spinner orange)
        for (uint8_t i=0; i<NUM_LEDS; i++){
            if (removedPermanent[i]) setLedColor(i, COLOR_GREEN);
            else setLedColor(i, COLOR_OFF);
        }
        setLedColor(cur, COLOR_ORANGE);
        strip.show();

        playPop();
        delay((uint16_t)scaledDelay);
        if (!isGameEnabled) {
          break;
        }
    }

    // END OF SPIN
    if (failure){
        playFailure();
        flashAll(COLOR_RED, 3, 200, 180);
        delay(3000);
        startMotor();
    } else {
        playSuccess();
        flashAll(COLOR_GREEN, 3, 160, 120);
        removedPermanent[targetIndex] = true;
        removedCount++;
    }

    // Redraw final permanent LEDs
    for (uint8_t i=0;i<NUM_LEDS;i++){
        if (removedPermanent[i]) setLedColor(i, COLOR_GREEN);
        else setLedColor(i, COLOR_OFF);
    }
    strip.show();
}



// ========================================================================
// Function 2: startGame()
// - set all LEDs to orange
// - watch for an item removal (presence true->false)
// - when removal observed: all LEDs except that location switch off
// - short delay, then spin animation that slows over 5s and stops on removed location
// - each step plays pop sound
// - after stop flash green, play success sound, mark LED green permanently
// - repeat until 5 items removed. On removal of 6th: behave same but final flash red, play failure and call startMotor() after 3s
// - previous removed LEDs remain green
void startGame(){
  if (gameReady && isGameEnabled){
    Serial.println("startGame: commencing...");
  }
  else{
    Serial.println("The game has not been initialised please set up the game");
    for (uint8_t i=0;i<NUM_LEDS;i++){
      setLedColor(i, COLOR_RED);
    }
    strip.show();
    exit(0);
  }
  gameReady=false;

  // initial capture of presence state
  bool presence[9];
  scanMatrix(presence);
  for (uint8_t i=0;i<9;i++){
    lastPresence[i] = presence[i];
  }

  // set all LEDs to orange initially, even if no item - as per spec
  for (uint8_t i=0;i<NUM_LEDS;i++){
    if (!removedPermanent[i]) setLedColor(i, COLOR_ORANGE);
    else setLedColor(i, COLOR_GREEN); // already removed items stay green
  }
  strip.show();

  // Main loop: keep running until we reach failure or user stops program.
  // We'll poll matrix in a loop and act when a present->absent change occurs
  // NOTE: This is blocking sequence per removal (which is required by spec).
  while (isGameEnabled){
    // scan
    scanMatrix(presence);

    // detect any new removal: previously present and now absent, and not already marked removedPermanent
    int removedIndex = -1;
    for (uint8_t i=0;i<NUM_LEDS;i++){
      if (lastPresence[i] == true && presence[i] == false && !removedPermanent[i]){
        removedIndex = i;
        break;
      }
    }

    // update baseline presence for next detection
    for (uint8_t i=0;i<NUM_LEDS;i++) lastPresence[i] = presence[i];

    if (removedIndex >= 0){
      Serial.print("Detected removal at index ");
      Serial.println(removedIndex);

      // On removal: all LEDs except that location should switch off.
      for (uint8_t i=0;i<NUM_LEDS;i++){
        if (i == removedIndex){
          // keep it orange as the spec says "all LEDs except the one corresponding to that location should switch off"
          setLedColor(i, COLOR_ORANGE);
        } else {
          if (removedPermanent[i]) setLedColor(i, COLOR_GREEN);
          else setLedColor(i, COLOR_OFF);
        }
      }
      strip.show();

      // short delay
      delay(SHORT_DELAY_AFTER_REMOVAL);

      // choose a start index for spin. We'll start from currently lit orange LED (the removedIndex),
      // but spec says the loop cycles to the next LED each time, so we'll start from removedIndex.
      uint8_t startIndex = removedIndex;

      // Determine if this removal is the special (6th) removal
      uint8_t futureRemovalCount = removedCount + 1; // if we accept this removal as new
      bool isFailure = (futureRemovalCount >= MAX_SAFE_REMOVALS); // when equals 6 => failure

      // Run spin, which will end on removedIndex
      runSpinAndResolve(startIndex, removedIndex, isFailure);

      // If it was failure, we should stop the game loop or continue? Spec: on removal of 6th item the animation should play as normal however when it gets to the end all Leds should flash red ... and startMotor() called after 3s.
      // After failure, we break out.
      if (isFailure){
        Serial.println("Failure condition reached (6th removal). Exiting startGame loop.");
        break;
      }

      // If not failure, continue until 5 items removed (spec said repeat until 5 items removed; on removal of 6th do failure).
      if (removedCount >= 5){
        Serial.println("Reached 5 removals. Game cycle continues awaiting 6th removal (which will be failure).");
        // continue loop — the spec says cycle repeats until 5 items have been removed; we'll continue monitoring.
      }
    }

    // tiny delay to avoid hammering CPU
    delay(40);
  } // end while
  if(!isGameEnabled)Serial.println("Game has been disabled, halting midway through!");

  Serial.println("startGame(): finished (exited loop).");
}

void setEscPower(){
  if (escPowered){
      Serial.println("Powering on motor");
      ESC.write(0);
      delay(200);
      digitalWrite(POWER_PIN, LOW);
      delay(2500);
  }
  else{
    Serial.println("Powering off motor");
    ESC.write(0);
    digitalWrite(POWER_PIN, HIGH);
  }

}

// ========================================================================
// Example loop() usage: you can call setupGame() and startGame() from loop or from Serial commands.
// For demo, we'll call nothing in loop — user should call these functions from their higher-level code.
// ========================================================================
void loop(){
  Serial.println("Initialization complete");
  Serial.println("Wating for command");
  while (!isGameEnabled){
    if (!digitalRead(RESET_BTN_PIN)) {
      Serial.println("Reset button detected");
      setupGame();
    }

    if (!digitalRead(ENABLE_PIN) && !escPowered) {
      Serial.println("Game is now enabled");
      escPowered=true;
      setEscPower();
    }
    if (digitalRead(ENABLE_PIN) && escPowered) {
      Serial.println("Game is now disabled");
      escPowered=false;
      setEscPower();
    }
    if (gameReady&&escPowered){isGameEnabled=true; Serial.println("All startup conditions met");}


    // For testing / manual triggers via serial console:
    if (Serial.available()){
      String cmd = Serial.readStringUntil('\n');
      cmd.trim();
      if (cmd.equalsIgnoreCase("init")){
        Serial.println("This is the problem locaton!");
        Serial.print(">");
        Serial.print(cmd);
        Serial.println("<");
        setupGame();
      } else if (cmd.equalsIgnoreCase("start")){
        startGame();

      } else if (cmd.equalsIgnoreCase("scan")){
        bool p[9]; scanMatrix(p);
        Serial.print("Presence: ");
        for (uint8_t i=0;i<9;i++) Serial.print(p[i] ? "1" : "0");
        Serial.println();

      } else if (cmd.equalsIgnoreCase("light")){
        Serial.println("Starting rrggbb flash");
        flashAll(COLOR_RED, 2, 200, 200);
        delay(500);
        flashAll(COLOR_GREEN, 2, 200, 200);
        delay(500);
        flashAll(COLOR_BLUE, 2, 200, 200);
      } 
      else if (cmd.equalsIgnoreCase("D_on")){
        digitalWrite(DISPENCER_PIN,LOW);
        Serial.println("dispenser is on");
      } 
      else if (cmd.equalsIgnoreCase("D_off")){
        digitalWrite(DISPENCER_PIN,HIGH);
        Serial.println(" dispenser has stopped");
      } 
      else if (cmd.equalsIgnoreCase("E_off")){
        digitalWrite(POWER_PIN,HIGH);
        Serial.println("ESC is now off");
      } 
      else if (cmd.equalsIgnoreCase("E_on")){
        digitalWrite(POWER_PIN,LOW);
        Serial.println("ESC is now powered");
      } 
      else if (cmd.equalsIgnoreCase("pop")){
        playPop();
      } 
      else if (cmd.equalsIgnoreCase("sucsess")){
        playSuccess();
      } 
      else if (cmd.equalsIgnoreCase("list")){
        soundSerial.println("L");
      } 
      else if (cmd.equalsIgnoreCase("fail")){
        playFailure();
      } 
      else {
        Serial.println("Commands: init | start | scan | light | E_on | E_off | D_on | D_off | pop | sucsess | list");
      }
      
    }
    // Forward soundboard serial output to the Arduino USB serial monitor
    while (soundSerial.available()) {
        char c = soundSerial.read();
        Serial.print(c);   // writes raw data exactly as received
    }

  }
  
  Serial.println("Setup complete, waiting for lid");
  while(digitalRead(LID_BTN_PIN)){ delay(200);} // wate for close
  Serial.println("Lid closed");
  delay(1000);//debounce

  Serial.println("Waiting for lid to be opened");
  flashAll(COLOR_GREEN, 3, 400, 400);
  while(!digitalRead(LID_BTN_PIN)&&isGameEnabled){ 
    delay(200);
  } //wate for open
  Serial.println("GO! GO! GO!");
  startGame();

  delay(1000);
  escPowered=false;
  setEscPower();

  Serial.println("End of game, waiting for reset");
  while (isGameEnabled && digitalRead(RESET_BTN_PIN)) {delay(200);} // the game has finished wait for it to be reset or disabled

  isGameEnabled=false;
}

my mistake, that came from copying loads of different code segments in and out of the IDE and changing things around to help diagnose the problem. it's not the root cause but I have fixed that now

When you compile the full code, how much ram (dynamic memory) does the compiler show as being used?

92% witch does seem high, I can understand that causing some problems. any way to reduce it?

What exactly have you fixed ?

The original code segment that you posted would certainly resulted in the code behaving differently than intended

Is the code that you posted working now or does it still behave as if the command received is "init" ? If so then please try printing cmd bracketed with > and < and post what you see

That error was never actually uploaded to the Arduino. I changed the logic to use a double equals rather than equalsIgnoreCase to see if that was causing any problems. I mistyped it while changing it back and putting it into the forum.

There's definitely a deeper problem relating to memory or some hardware limitation of the Arduino. I can do programming (ish) , I can do electronics but my knowledge on the in's and outs of arduino's is limited.

I have also just notice that whenever I include lcd.init in my setup function, that breaks the NeoPixel LED's and any attempts to set a colour do nothing.

Enabling the I2C bus definitely seems to affect the hardware timers and interrupts of all of the other libraries (i think).

Are there any other alternative libraries or work arounds?

Need to post the code you are using.

this is the actual code