Arduino UNO project: 7 segment module (3 inch, 3 digit) behavior problem

I have an Arduino project (with UNO clone) about an arcade game where the player pushes a doll to collect score with 3 chances and gain tickets based on the total score, the schematic is shown in the following picture:

And here's the sketch (long):

#include <Arduino.h>
#include "MD_YX5300.h"

//Coin input
#define coin 10 //Grey jumper wire + Blue long wire

//DC Motor
const int ENA = 5; //PWM pin, blue jumper wire
const int IN1 = 6; //White jumper wire, IN2 pull-down with R 10K
const int stop = 7; //Green jumper wire, limit switch

//Sensor IR
const int activate = 8; //Detects if object is thrown to activate other IR objects, orange jumper wire
const int ir_low = 4; //Score 25, black jumper wire + Blue long wire
const int ir_mid = 3; //Score 50, grey jumper wire + Blue long wire
const int ir_high = 2; //Score 100, white jumper wire + Blue long wire

//7 Segment Score
//CLK = Blue long wire
//LATCH = Green long wire
//DATA = White long wire
//Purple jumper wire (mid, score)
#define CLK1 12
#define LATCH1 11
#define DATA1 13
//Green jumper wire (bottom, timer)
#define CLK2 15
#define LATCH2 14
#define DATA2 16
//Orange jumper wire (top, credit)
#define CLK3 18
#define LATCH3 17
#define DATA3 19
unsigned int time;
unsigned int credit;
unsigned int insert;
unsigned int score;
unsigned int score1;
unsigned int score2;
unsigned int score3;
unsigned int total;

const byte binary[11] = {
  B11010111, //0
  B10000001, //1
  B11001110, //2
  B11001011, //3
  B10011001, //4
  B01011011, //5
  B01011111, //6
  B11000001, //7
  B11011111, //8
  B11011011, //9
};

//Ticket Dispenser
const int motor = 9; //Brown jumper wire + Blue long wire
unsigned int ticket1;
unsigned int ticket2;
unsigned int ticket3;
unsigned int ticket;
unsigned int reward;
unsigned int mercy;

//Sounds
MD_YX5300 sound(Serial);

//Chance millis
int chance;
unsigned long halt_chance = 250;
unsigned long start_chance = 0;

//IR Sensor millis
unsigned long halt = 2500; //IR sensor got delayed for 2,5s per throw
unsigned long start_low = 0;
unsigned long start_mid = 0;
unsigned long start_high = 0;

//Limit millis
unsigned long limit = 1000; //for 30s time limit
unsigned long start_limit = 0;

//Sound millis
unsigned long halt_coin = 200;
unsigned long start_coin = 0;
unsigned long start_down = 0;

//Refresh millis
unsigned long refresh = 0;

//Start from upper or bottom
bool above = false;

void setup() {
  Serial.begin(9600);
  //Coin input
  pinMode(coin, INPUT_PULLUP); //+External pullup
  //DC Motor
  pinMode(ENA, OUTPUT);
  pinMode(IN1, OUTPUT);
  pinMode(stop, INPUT);
  //Sensor IR
  pinMode(activate, INPUT); //+External pullup
  pinMode(ir_low, INPUT);
  pinMode(ir_mid, INPUT);
  pinMode(ir_high, INPUT);
  //7 Segment
  pinMode(LATCH1, OUTPUT);
  pinMode(CLK1, OUTPUT);
  pinMode(DATA1, OUTPUT);
  pinMode(LATCH2, OUTPUT);
  pinMode(CLK2, OUTPUT);
  pinMode(DATA2, OUTPUT);
  pinMode(LATCH3, OUTPUT);
  pinMode(CLK3, OUTPUT);
  pinMode(DATA3, OUTPUT);
  //Ticket Dispenser
  pinMode(motor, OUTPUT);
  digitalWrite(motor, HIGH);
  //Sound
  sound.begin();
}

void loop() {
  digitalWrite(motor, HIGH);
  //Reset
  chance=0;
  score=0;
  score1=0;
  score2=0;
  score3=0;
  total=0;
  ticket1=0;
  ticket2=0;
  ticket3=0;
  ticket=0;
  reward=0;
  mercy=0;
  time=0;
  above=false;
  Result(3,score);
  Timer(3,time);
  Play(3,credit);
  //DC motor reset
  do{
    DCUnlock();
  } while(digitalRead(stop)==LOW);
  if(digitalRead(stop)==HIGH){
    DCStop();
  }
  //Standby sound
  sound.playTrackRepeat(8);
  //Coin input
  while(true){
    unsigned long current = millis();
    if(current-refresh>=50){
      Result(3,score);
      Timer(3,time);
      Play(3,credit);
      refresh=current;
    }
    if(digitalRead(coin)==LOW){
      Credit();
      time=3;
      Timer(3,time);
      //Coin sound
      sound.playTrack(9);
      delay(1000);
    }
    if(insert>0){
      insert--;
      time=3;
      Timer(3,time);
      break;
    }
  }
  //Ready sound
  sound.playTrack(1);
  while(true){
    //Coin input
    unsigned long current = millis();
    if(current-start_coin>=halt_coin){
      if(digitalRead(coin)==LOW){
        Credit();
        start_coin=current;
      }
    }
    //Doll release
    if((digitalRead(ir_low)==LOW) || (digitalRead(ir_mid)==LOW) || (digitalRead(ir_high)==LOW)){
      above=true;
      DCUnlock();
    }
    //DC Stop
    if((digitalRead(ir_low)==HIGH) && (digitalRead(ir_mid)==HIGH) && (digitalRead(ir_high)==HIGH) && (digitalRead(stop)==HIGH)){
      DCStop();
    }
    //Countdown
    if((current-start_limit>=limit) && (time>0)){
      time--;
      Timer(3,time);
      start_limit=current;
    }
    //Sensor IR (Activator)
    if(current-start_chance>=halt_chance){
      //Countermeasure
      if((digitalRead(activate)==LOW) && (time>0) && (above==false)){
        start_chance=current;
        above=true;
      }
      //Start from above
      else if((above==true) && (digitalRead(activate)==LOW)){
        start_chance=current;
        break;
      }
      //Start from below
      else if((above==false) && (time==0)){
        break;
      }
    }
  }
  //DC Stop
  if((digitalRead(ir_low)==HIGH) && (digitalRead(ir_mid)==HIGH) && (digitalRead(ir_high)==HIGH) && (digitalRead(stop)==HIGH)){
    DCStop();
  }
  //Game start
  time=30;
  Timer(3,time);
  //Theme music
  sound.playTrackRepeat(2);
  //Scoring system
  while(chance<6){
    unsigned long current = millis();
    ScoreAdd();
    //3 chances for 2 LOW IR sensor activations each throw
    if(current-start_chance>=halt_chance){
      if(digitalRead(activate)==LOW){
        start_chance=current;
        chance++;
        if(chance%2==0){
          sound.playTrackRepeat(2);
        }
        //Slide whistle down sound  
        else if(chance%2!=0){
          sound.playTrack(3);
        }        
      }
    }
    //Timer
    if((current-start_limit>=limit) && (time>0)){
      time--;
      Timer(3,time);
      start_limit=current;
      //Time's up
      if(time==0){
        chance=6;
        //Time up sound
        sound.playTrack(6);
        delay(500);
      }
    }
    //Coin input
    if(current-start_coin>=halt_coin){
      if(digitalRead(coin)==LOW){
        Credit();
        start_coin=current;
      }
    }
  }
  unsigned long current = millis();
  //Final score
  total=score1+score2+score3;
  ticket=ticket1+ticket2+ticket3;
  Result(3,total);
  //3 mercy tickets
  if(total==0){
    while(mercy<3){
      digitalWrite(motor, LOW);
      mercy++;
      delay(300);
      //Mercy sound
      sound.playTrack(10);      
      if(mercy==3){
        digitalWrite(motor, HIGH);
        sound.playPause();
      }
      //Coin input
      if(current-start_coin>=halt_coin){
        if(digitalRead(coin)==LOW){
          Credit();
          start_coin=current;
        }
      }
    }
  }
  //Ticket dispensing
  else{
    while(reward<ticket){
      digitalWrite(motor, LOW);
      delay(300);
      digitalWrite(motor, HIGH);
      delay(10);
      reward++;
      //Award sound
      sound.playTrack(7);
      if(reward==ticket){
        digitalWrite(motor, HIGH);
        sound.playPause();
      }
    }
    //Coin input
    if(current-start_coin>=halt_coin){
      if(digitalRead(coin)==LOW){
        Credit();
        start_coin=current;
      }
    }
  }
  credit--;
}

void Credit(){
  credit++;
  insert++;
  Play(3,credit);
  //Coin sound
  sound.playTrack(9);
}

void DCUnlock(){
  digitalWrite(IN1, HIGH);
  analogWrite(ENA, 255);
}

void DCStop(){
  digitalWrite(IN1, LOW);
}

void Result(long digit, long number){
  digitalWrite(LATCH1, LOW);
  DisplayScore(digit, number);
  digitalWrite(LATCH1, HIGH);
}

void Timer(long digit2, long s){
  digitalWrite(LATCH2, LOW);
  DisplayTimer(digit2, s);
  digitalWrite(LATCH2, HIGH);
}

void Play(long digit3, long acc){
  digitalWrite(LATCH3, LOW);
  DisplayCoin(digit3, acc);
  digitalWrite(LATCH3, HIGH);
}

void DisplayScore(long t_digit, long t_number){
  long temp_number=t_number;
  long temp_N=1;
  for(int i=0; i<t_digit; i=i+1){
    int print = (temp_number/temp_N) % 10;
    temp_N=temp_N*10;
    shiftOut(DATA1, CLK1, LSBFIRST, binary[print]);
  }
}

void DisplayTimer(long t_digit2, long t_s){
  long temp_s=t_s;
  long temp_N2=1;
  for(int j=0; j<t_digit2; j=j+1){
    int print2 = (temp_s/temp_N2) % 10;
    temp_N2=temp_N2*10;
    shiftOut(DATA2, CLK2, LSBFIRST, binary[print2]);
  }
}

void DisplayCoin(long t_digit3, long t_acc){
  long temp_acc=t_acc;
  long temp_N3=1;
  for(int k=0; k<t_digit3; k=k+1){
    int print3 = (temp_acc/temp_N3) % 10;
    temp_N3=temp_N3*10;
    shiftOut(DATA3, CLK3, LSBFIRST, binary[print3]);
  }
}

void ScoreAdd(){
  unsigned long current = millis();
  if(current-start_low>=halt){
    if(digitalRead(ir_low)==LOW){
      //Score sound effect
      sound.playTrack(4);
      score=25;
      if(chance<3){
        score1=25;
        ticket1=2;
      }
      else if((chance>=3) && (chance<5)){
        score2=25;
        ticket2=2;
      }
      else{
        score3=25;
        ticket3=2;
        time=1;
        Timer(3,time);
      }        
      Result(3,score);
      start_low=current;
      start_down=current;
    }
  }
  if(current-start_mid>=halt){
    if(digitalRead(ir_mid)==LOW){
      //Score sound effect
      sound.playTrack(4);
      score=50;
      if(chance<3){
        score1=50;
        ticket1=3;
      }
      else if((chance>=3) && (chance<5)){
        score2=50;
        ticket2=3;
      }
      else{
        score3=50;
        ticket3=3;
      }
      Result(3,score);
      start_mid=current;
      start_down=current;    
    }
  }
  if(current-start_high>=halt){
    if(digitalRead(ir_high)==LOW){
      //Score sound effect
      sound.playTrack(4);
      score=100;
      if(chance<3){
        score1=100;
        ticket1=4;
      }
      else if((chance>=3) && (chance<5)){
        score2=100;
        ticket2=4;
      }
      else{
        score3=100;
        ticket3=4;
        time=2;
        Timer(3,time);         
      }
      Result(3,score);
      start_high=current;
      start_down=current;
    }
  }
  //Doll release
  if(((digitalRead(ir_low)==LOW) || (digitalRead(ir_mid)==LOW) || (digitalRead(ir_high)==LOW)) && (chance<5)){
    DCUnlock();
  }
  //DC Stop
  if((digitalRead(ir_low)==HIGH) && (digitalRead(ir_mid)==HIGH) && (digitalRead(ir_high)==HIGH) && (digitalRead(stop)==HIGH)){
    DCStop();
  }
  //Slide whistle up sound
  if((current-start_down==1000) && (chance<5)){
    sound.playTrack(5);
  }
}

How the sketch/devices work is the following:

  1. Insert coin.
  2. Countdown in 3s.
  3. The DC motor rotates to unlock the doll, sending it rolling to the player.
  4. The game starts either if the doll crosses the "3x chance" IR sensor below (PIN 8) or if the countdown reaches 0 (in case the doll was already unlocked before inserting coin), with 30s timer.
  5. The player pushes the doll to the front to gain score (either gaining 100, 50, and 25, from one of the 3 IR sensors in front).
  6. The game gives 3 chances, each time the doll crosses the chance IR sensor, one chance is used.
  7. In 1st-2nd chance, when the doll is thrown to the front, the DC motor will spin to unlock the doll and send it back to the player, but after 3rd chance, the DC motor will not spin.
  8. Afterwards, the score is counted and the ticket dispenser dispenses tickets based on the score.
  9. Back to insert coin.

The problem is, at first, the 3 7 segment modules all work fine (for years), but recently, their output is now corrupted. Using long wire (for CLK, LATCH, and DATA) is inevitable for them, as they're located in the upper part of the arcade machine with Arduino below. I tested each of the 3 modules individually, and they work fine, but not if mounted in the arcade machine above with long wires connecting them to the Arduino.

I switched to using protected 3 x 1 wire like this for the modules, and using this, there's some improvement, it works fine with 1-2 modules, but it corrupts 2 of the 3 modules if all modules are used. And initially, it runs for a while using 2 modules (only using the score and timer ones), but now, it corrupts the output of 1 module, and the result is the same when I swapped the modules.

Are there any solutions for this? Like, would using shielded twisted wires for each of the modules solve this?

If distance is an issue, then most likely capacitance of the cable is the cause.

What you could do :

  • Change the 3 stranded wire for something thinner like standard led-strip wire (AWG22) to reduce capacitance
  • Slow down the transmission, write your own shiftOut() function but keep a few microseconds in between every statechange of the CLK to ensure that the data line is HIGH or LOW before the clock changes state (as well as making sure that the transmission is correct before you set/reset the latch. A few micro seconds should suffice.

Changing the wires to twisted pair does nothing unless you start using a twisted pair protocol, but wires from a UTP cable are really nice and thin and will have less capacitance that what you are using now.

The motor does not appear to have any noise filtering and it will most likely introduce some on the GND line.

Hi @89jvek !

If all fails a workaround is to install a separate Arduino board like a Nano placed close to the three modules to drive them and to control the Nano from the Uno via SoftwareSerial...

Sometimes eliminating the cause becomes easier if there's a second solution ...

Good luck!

ec2021

Then why do you suspect it is a wiring issue?
Did you recently change the wiring and then notice the odd behavior?

Are you actually powering the LED displays with 12V?
If yes, that is your problem.

Has anything changed recently that might be causing the problem, such as some other equipment located near your game, something else in the building that might be causing spikes on the power line, etc?

As already mentioned above the cable you linked here is not helpful . It is not shielded against EMI as it is designed for electrical connection to mains.

Arcade machine wiring has corrosion. Replace connectors and reflow solder joints. How can I turn all Arduino jumper connections into one unified plug? - #34 by kmin

Would microseconds delay ok and not interrupt the game at all? As usually delay isn't recommended.

No, I didn't change the wiring at all, I let the engine operate without intervention, but suddenly there's an odd behavior.

Yeah. What'd be the solution by this?

No it usually isn't. Ideally one uses hwSPI, but you have various outputs. We are looking at 'a' microsecond, which would be a total of 24us per display i guess, but a few times that in locations and you will not likely exceed 1mz. I doubt it will really be a problem, but improving the hardware is probably something to try first.

My earlier recommendation for going for thinner wire still stands. Though solid wire has significantly less capacitance than multi strand, those cables are quite thick.