Counting and Timing a shock sensor

Actually it is declared, just not as a stand alone variable. If you look at the function declaration you will see TimeConvert(long input). This variable only has scope within that function. If you see it outside of the function it is invalid.

Dan

ooooohhhhhhh. gotcha!

So if you declare it in that function and don’t set it to anything, how is it ever supposed to not = 0 so it can do the if statement?

It occurs to me that the TimeConvert() function could have “printed” it’s “chars and integers” directly to the LCD (like the code in the clock project) instead of using the PString library function to store time. But I wanted a way to debug things, by printing time, if things semi-worked but needed tweaking. However if using PString proves to be a PITA, it can go. If the compiler complains about “time” or “buffer” that would qualify as PITA.

This would be that alternate version of code. In some ways it’s even simpler and so prefered.

#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 9, 8);


// set pin numbers
const int StartPin = 7;       // the number of the Start pushbutton pin
const int TargetAPin = 2;     // the number of the A targets input pin
const int TargetBPin = 3;     // the number of the B targets input pin
const int BuzzerPin = 10;     // the number of the buzzer output pin
const int LEDPin = 13;        // the number of the LED output pin
const int ScrollPin = 6;       // the number of the scroll button input pin
//const int ButtonInPin = A0;   // the pin used for the analog buttons


// initialize the constants
const unsigned long MaxTime = 30000;     // the max time the timer can run for = 30 secs
const unsigned long WaitTime = 2000;     // wait time btw start and timer running = 2 secs plus debounce
const unsigned long DB_delay = 1000;     // set a debounce wait time of 1000 msecs
const unsigned long BuzzTime5 = 500;     // set the on time for the buzzer, 500 msecs
const unsigned long BuzzTime2 = 200;     // set the on time for the buzzer, 200 msecs
const unsigned long BuzzTime10 = 1000;   // set the on time for the buzzer, 1000 msecs
const unsigned int FreqHi = 2000;        // High frequency of buzzer tone
const unsigned int FreqLo = 1000;        // Low frequency of buzzer tone
const byte MaxHits = 10;                 // Maximum number if hits allowed
const unsigned long LCDtime = 100;       // Set min time btw writes to LCD to 100 msecs


// initialize global variables
volatile byte TimerState = 0;            // variable for state of timer, running or not running
volatile byte AupDtFlag = 0;             // variable indication an A hit has occurred
volatile byte BupDtFlag = 0;             // variable indication a B hit has occurred
byte DisplayIndex = 0;                   // variable for controlling what's displayed
unsigned long StartTime = 0;             // variable to hold the start time
unsigned long BuzzTime = 500;            // variable to hold the buzzer on time
unsigned int Freq = 2000;                // variable for high or low buzzer tone
volatile byte A_count = 0;               // variable to hold the number of A hits
volatile byte B_count = 0;               // variable to hold the number of B hits
volatile long A_Times[MaxHits];          // array to hold up to MaxHits hit times for target A
volatile long B_Times[MaxHits];          // array to hold up to MaxHits hit times for target B
unsigned long A_splits[MaxHits];
unsigned long B_splits[MaxHits];
long AB_splits[MaxHits];
unsigned long LastTime = 0;              // initialize last LCD display time to zero for 1st pass
float A_Hit_Factor = 0;
float B_Hit_Factor = 0;
byte S1 = 0;                             // Switch 1 init to zero
byte S2 = 0;                             // Switch 2 init to zero
unsigned int diMin = 0;                  // Display index mininmim
unsigned int diMax = 99;                 // Display index maximum
unsigned int TimerMode = 0;              // initialize the mode to be single shooter
volatile int secs = 0;
volatile int frac = 0;
// char time[ ] = "      ";
// char buffer[7];                          // save 7 empty spaces



void setup()
{
  // set up the LCD's number of columns and rows: 
  lcd.begin(20, 4);
  lcd.clear();
  lcd.print("    Shot Timer 1    ");
  lcd.setCursor(0, 1);
  lcd.print("    Initializing    ");
  delay(5000);

  // initialize output pins
  pinMode(BuzzerPin, OUTPUT);
  pinMode(LEDPin, OUTPUT);
  digitalWrite(BuzzerPin, LOW);
  digitalWrite(LEDPin, LOW);
   
  // initialize the input pins with internal pullups
  pinMode(StartPin, INPUT);
  // pinMode(UpPin, INPUT);
  //pinMode(DownPin, INPUT);
  pinMode(TargetAPin, INPUT);
  pinMode(TargetBPin, INPUT);
  digitalWrite(StartPin, HIGH);   
  //digitalWrite(UpPin, HIGH);
  //digitalWrite(DownPin, HIGH);
  digitalWrite(TargetAPin, HIGH);
  digitalWrite(TargetBPin, HIGH);

  // for now use pin 6 as button input
  pinMode(ScrollPin, INPUT);
  digitalWrite(ScrollPin, HIGH);

  // opens serial port, sets data rate to 9600 bps
  Serial.begin(9600);

  // setup ext pins as interrupts
  attachInterrupt(0, ISR_A, FALLING);
  attachInterrupt(1, ISR_B, FALLING);

  lcd.setCursor(0, 1);
  lcd.print("      Ready       ");
  // PString time(buffer, sizeof(buffer));
}


void loop()
{
  if (TimerState) // is timer running
  {
    // timer is running
    // set display according to timer mode
    switch (TimerMode)
    {
      case 1:
        //this is for A vs B shooter mode
        DisplayIndex = 5;
        break;
      default: 
        // the default = single A shooter mode
        DisplayIndex = 1;   // display will show count and time of hits
    }
    
    // send data to display if there have been any hits
    if (AupDtFlag == 1 || BupDtFlag == 1)
    {
      CalcTimes();
      FormatData();
    }
    
    // timer is running so now check for any stop conditions
    if ((digitalRead(StartPin) == LOW) || ((millis() - StartTime) > MaxTime) || A_count >= MaxHits || B_count >= MaxHits)
    {
      // stop the timer change the display
      StopTimer();
      tone(BuzzerPin, FreqLo, BuzzTime10);
      // update display just in case last msec hit came in
      if (AupDtFlag == 1 || BupDtFlag == 1)
      {
        CalcTimes();
        FormatData();
      }
      
      // just for the moment send times to PC for debug
      SendTimes();
      
      // delay enough to debounce stop button
      delay(DB_delay);
      
      // Change to review-stopped state display based on timer mode
      switch (TimerMode)
      {
        case 1:
          //this is for A vs B shooter mode
          diMin = 6;
          diMax = int(max(A_count,B_count)/4) + diMin - 1; // find how many whole screens
          if (max(A_count,B_count) % 4)
          {
            diMax++ ;              // add another screen if partial exists
          }
          DisplayIndex = diMax;    // display will show list of hit times
          break;
        default: 
          // the default = single A shooter mode
          diMin = 2;
          diMax = int(A_count/8) + diMin - 1; // find how many whole screens
          if (A_count % 8)
          {
            diMax++ ;              // add another screen if partial exists
          }
          DisplayIndex = diMax;    // display will show list of hit times
      }
    }
  }
  else
  {
  // timer is stopped look for start button or review button pushes

    // check for scroll button pushes, change display state as reqd
    if (digitalRead(ScrollPin) == LOW)
    {
      DisplayIndex++;             // increment DisplayIndex upon push
      if (DisplayIndex > diMax)
      {
        DisplayIndex = diMin;     // wrap around of scrolling action
      }
      // change to new display
      FormatData();
      delay(DB_delay);
    }
    
    // check to see if start button has been pushed
    if (digitalRead(StartPin) == LOW)
    {
      // start button pushed
      // Do the following debug code only to get system running
      // This will send message to PC to show button was pushed
      Serial.println("Timer is running");
      
      // turn on the LED to show timer is running
      digitalWrite(LEDPin, HIGH);
      
      // clear all the prior runs data, reset the display
      ClearData();
      DisplayIndex = 0;
      FormatData();
      
      // delay the Wait Time from start button push
      // this delay is for debounce purposes and should stay
      delay(DB_delay);
      // this delay will change to random later on    
      delay(WaitTime);
       
      // enable the interrupts just in case
      interrupts();
      
      // save the starting time of this run
      StartTime = millis();
      
      // set state of timer to running
      TimerState = 1;
      
      // now buzz the speaker for 0.5 secs
      tone(BuzzerPin, FreqHi, BuzzTime5);
    }
  }
}


void StopTimer()
{
  //noInterrupts();
  TimerState = 0;
  // turn the LED off to show timer is stopped
  digitalWrite(LEDPin, LOW);
}


void ClearData()
{
  A_count = 0; 
  B_count = 0;
  for (int i=0; i < MaxHits ; i++)
  {
    A_Times[i] = 0;
    B_Times[i] = 0;
    A_splits[i] = 0;
    B_splits[i] = 0;
    AB_splits[i] = 0;
   }
  A_Hit_Factor = 0;
  B_Hit_Factor = 0;
}


void SendTimes()
// temp routine to send data to serial monitor
{
  Serial.println("Timer is stopped");
  Serial.println("Here are the times for Shooter A");
  Serial.print("Number of hits : ");
  Serial.print("\t");
  Serial.println(A_count);
  Serial.println("A Hit and split times are : ");
  int i = 0;
  int k = 0;
  for (i=0; i < MaxHits ; i++)
  {
    k = i + 1;
    //Serial.print(A_count[k]);
    Serial.print("\t");
    Serial.print(A_Times[i]);
    Serial.print("\t");
    Serial.println(A_splits[i]);
  }
  Serial.println("Here are the times for Shooter B");
  Serial.print("Number of hits : ");
  Serial.print("\t");
  Serial.println(B_count);
  Serial.println("B Hit and split times are : ");
  for (i=0; i < B_count ; i++)
  {
    k = i + 1;
    //Serial.print(B_count[k]);
    Serial.print("\t");
    Serial.print(B_Times[i]);
    Serial.print("\t");
    Serial.println(B_splits[i]);
  }
}


void ISR_A()
{
  if(TimerState)
  {
    // store the hit time 
    A_Times[A_count] = millis() - StartTime;
    // increment the hit count and array index
    ++A_count;
    // set the Hit flag so other stuff will update
    AupDtFlag = 1;
  }
}


void ISR_B()
{
  if(TimerState)
  {
    // store the hit time 
    B_Times[B_count] = millis() - StartTime;
    // increment the hit count and array index
    ++B_count;
    // set the Hit flag so other stuff will update
    BupDtFlag = 1;
  }
}


void CalcTimes()
{
// routine to calculate all data and declare winner
// not all calcs having meaning for uses of timer
  // calculate A splits and cumlative hit factor
  if (A_count > 1)
  {
    for (int i=1; i < A_count ; i++)
    {
      A_splits[i] = A_Times[i] - A_Times[i-1];
    }
  }
  else
  {
    A_splits[0] = A_Times[0];
  }  
  A_Hit_Factor = A_Times[A_count - 1]/A_count;
  // calculate B splits and cumlative hit factor
  if (B_count > 1)
  {
    for (int i=1; i < B_count ; i++)
    {
      B_splits[i] = B_Times[i] - B_Times[i-1];
    }
  }
  else
  {
    B_splits[0] = B_Times[0];
  }  
  B_Hit_Factor = B_Times[B_count - 1]/B_count;
  // Calculate A - B times just in case
  int Min_count = min(A_count, B_count);
  for (int i=0; i < Min_count ; i++)
    {
      AB_splits[i] = A_Times[i] - B_Times[i];
    }
// add more here for A vs B modes

}


void FormatData()
{
// routine to format lines of data to be sent to LCD
// presently assume 4 lines of 20 characters
// switch formatting done based on display state
  switch (DisplayIndex)
  {
    case 1:
      //this is for single A shooter mode
      // display number of hits so far
      lcd.clear();
      lcd.print("A: # hits = ");
      lcd.print(A_count);
      lcd.setCursor(0, 1);
      lcd.print(" Hit  time = ");
      TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      // lcd.print(time);
      lcd.setCursor(0, 2);
      lcd.print("Split time = ");
      TimeConvert(A_splits[A_count-1]); // convert split time
      // lcd.print(time);
      break;
    case 2:
      // this is A review mode hits 1-8
      lcd.clear();
      int k = 0;
      for (int i=0; i < 4 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("  ");
        lcd.print(i+5);
        lcd.print(":");
        TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
      break;
    case 3:
      // this is A review mode hits 9-16
      lcd.clear();
      int k = 0;
      for (int i=8; i < 12 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");    // this will display oddly for hits 9 and 13
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print(" ");
        lcd.print(i+5);
        lcd.print(":");
        TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
      break;
    case 4:
      // this is A review mode hits 17-20
      lcd.clear();
      int k = 0;
      for (int i=16; i < 20 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        // lcd.print(" ");
        // lcd.print(i+5);
        // lcd.print(":");
        // TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
      break;
    case 5:
      //this is for AvsB shooter mode
      // display number of hits so far
      lcd.clear();
      lcd.print("A: # hits = ");
      lcd.print(A_count);
      lcd.setCursor(0, 1);
      lcd.print("A Hit time = ");
      TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      // lcd.print(time);
      lcd.setCursor(0, 2);
      lcd.print("B: # hits = ");
      lcd.print(B_count);
      lcd.setCursor(0, 3);
      lcd.print("B Hit time = ");
      TimeConvert(B_Times[A_count-1]); // Now for B times
      // lcd.print(time);
      break;
    case 6:
      // this is AvsB review mode hits 1-4
      lcd.clear();
      int k = 0;
      for (int i=0; i < 4 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("B ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
      break;
    case 7:
      // this is AvsB review mode hits 5-8
      lcd.clear();
      int k = 0;
      for (int i=4; i < 8 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("B ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
      break;
    case 8:
      // this is AvsB review mode hits 9-12
      break;
      lcd.clear();
      int k = 0;
      for (int i=8; i < 10 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("B ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
      for (int i=10; i < 12 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("B");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
      break;
    case 9:
      // this is AvsB review mode hits 13-16
      lcd.clear();
      int k = 0;
      for (int i=12; i < 16 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("B");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
      break;
    case 10:
      // this is AvsB review mode hits 17-20
      lcd.clear();
      int k = 0;
      for (int i=16; i < 20 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("B");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
      break;
    default: 
      // do the default = 0
      lcd.clear();
      lcd.print("    Shot Timer v1   ");
      lcd.setCursor(0, 1);
      lcd.print("     is running     ");
      lcd.setCursor(0, 2);      
      lcd.print("      Shooter:      ");
      lcd.setCursor(0, 3);
      lcd.print("    --STANDBY--     ");
  }
}


void TimeConvert(long input)
{
// takes the msecs as argument and sets the time for that hit as a XX.xxs string

  //time.begin();           // reset Pstring

  if (input != 0)         // Make sure there's a value...
  {
    secs = int((input + 5)/1000L);         // round msecs into whole secs
    frac = int(((input + 5) % 1000L)/10L); // find fractional rounded secs

    if (secs < 1)
    {
      lcd.print(" 0");       // for sub 1 sec times pad with space plus zero
    }
    else
    {
      if (secs < 10)
      {
        lcd.print(' ');             // for sub 10 sec times pad with space
      }
    }
    lcd.print(secs);                // now add whole secs 
    lcd.print('.');                 // now add period

    if (frac < 10)
    {
      lcd.print(' ');               // If frac < 10 then pad with a blank
    }
    lcd.print(frac);                // now add fractional secs
    lcd.print('s');                 // add s to tail
  }
  else
  {
    lcd.print = ("      ");         // If the input variable is zero set time to 6 blank spaces
  }
}

sspbass:
ooooohhhhhhh. gotcha!

So if you declare it in that function and don’t set it to anything, how is it ever supposed to not = 0 so it can do the if statement?

When you call TimeConvert you supply the value that the variable input takes on. So if it was TimeConvert(3000) input would be 3000.

Dan

sspbass:
ooooohhhhhhh. gotcha!

So if you declare it in that function and don’t set it to anything, how is it ever supposed to not = 0 so it can do the if statement?

If you’re talking about “input”, it gets set when the function is called. Look at :

TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format

In this case “input” gets set to A_Times, where x is the hit number (decremented because arrays run 0 to whatever).

EDIT : anyone know where I can learn to type faster ? :mrgreen:

Mee_n_Mac:
EDIT : anyone know where I can learn to type faster ? :mrgreen:

Beat by the old guy. You totally deserved that! :lol:

However you answer so many posts I was able to sneak in and ninja that answer.

Dan

dkulinski:
Beat by the old guy.

Don’t bet the farm on that. :twisted:

Now that you’re back from vacation … any comment on the recent woes ? Tried to implement some simple String functions and the whole thing went weird. Cruising the various forums it sounds like some memory overwriting problem, common to the String class, that the 2 versions above try to sidestep. At one point last Fri the whole thing was looking pretty good and then … right into the toilet !

I’ll check it out tonight. I was wondering why you were using different string libraries. I checked out the latest youtube video posted and it seem that the screen is updated too much. I’ve copied the code locally so I can check it out on the bus ride home.

Dan

dkulinski:
I’ll check it out tonight. I was wondering why you were using different string libraries. I checked out the latest youtube video posted and it seem that the screen is updated too much. I’ve copied the code locally so I can check it out on the bus ride home.

Dan

That would be nice, thx ! Just to recap : initially the idea was to format 2, then 4, lines to be sent to the LCD. In trying to keep the formatting and the sending (to the LCD) functions divorced (so the nature of the display could be changed w/o having to redo a lot of code), the formatting function tried to make 4 Strings (note capital S), using the nice features that Strings supposedly provide. That seemed to work at first but while tweaking the display to look just right, the whole timer went south. Not only was the display overwritting itself but eventually it was resetting the Arduino. WTF ?!?! Small code changes produced odd results and from what I could read, Strings were a problem in the pre-Ardiuno 1.0 release. Apparently memory allocation and deallocation and fragmentation were an issue. Since sspbass is using release 1.0, I don’t know why these problems, supposedly fixed, persist but there you go.

So the last 3 or 4 versions have moved away further and further from anything String related. It turns out that rather than using String functionality to build up and hold the display data (in the Line1 - Line4 variables), the LCD itself can be used to “buffer” the data. You just have to be careful to write the data to it in the proper order so it’ll display as you intend. :doh: So the last version (above) formats the data, with each character and integer to be displayed calculated on the fly, and then sent to the LCD in the order needed. There are no variables that store the (final) data to be displayed, just the chars and ints that make up that display. Short of some stupid typo or syntax error by moi, it has to work. {yes, I taunt the code gods !} But of course now, if the display ever changes and the new one doesn’t use the “lcd.print” library … well it’s a lot of code to be changed. ATM I’ll take working and bullet-proof vs future-proof.

It was just getting to the point where it intersects my project and I want to see what can be done.

Well, I guess I’m taking the night off from this project.

I forgot my laptop at work and I can’t get my desktop to communicate with the Arduino.

I tried every USB port I had!

I did get the code to compile so if you feel like playing with it play with this one.

#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 9, 8);


// set pin numbers
const int StartPin = 7;       // the number of the Start pushbutton pin
const int TargetAPin = 2;     // the number of the A targets input pin
const int TargetBPin = 3;     // the number of the B targets input pin
const int BuzzerPin = 10;     // the number of the buzzer output pin
const int LEDPin = 13;        // the number of the LED output pin
const int ScrollPin = 6;       // the number of the scroll button input pin
//const int ButtonInPin = A0;   // the pin used for the analog buttons


// initialize the constants
const unsigned long MaxTime = 30000;     // the max time the timer can run for = 30 secs
const unsigned long WaitTime = 2000;     // wait time btw start and timer running = 2 secs plus debounce
const unsigned long DB_delay = 1000;     // set a debounce wait time of 1000 msecs
const unsigned long BuzzTime5 = 500;     // set the on time for the buzzer, 500 msecs
const unsigned long BuzzTime2 = 200;     // set the on time for the buzzer, 200 msecs
const unsigned long BuzzTime10 = 1000;   // set the on time for the buzzer, 1000 msecs
const unsigned int FreqHi = 2000;        // High frequency of buzzer tone
const unsigned int FreqLo = 1000;        // Low frequency of buzzer tone
const byte MaxHits = 10;                 // Maximum number if hits allowed
const unsigned long LCDtime = 100;       // Set min time btw writes to LCD to 100 msecs


// initialize global variables
volatile byte TimerState = 0;            // variable for state of timer, running or not running
volatile byte AupDtFlag = 0;             // variable indication an A hit has occurred
volatile byte BupDtFlag = 0;             // variable indication a B hit has occurred
byte DisplayIndex = 0;                   // variable for controlling what's displayed
unsigned long StartTime = 0;             // variable to hold the start time
unsigned long BuzzTime = 500;            // variable to hold the buzzer on time
unsigned int Freq = 2000;                // variable for high or low buzzer tone
volatile byte A_count = 0;               // variable to hold the number of A hits
volatile byte B_count = 0;               // variable to hold the number of B hits
volatile long A_Times[MaxHits];          // array to hold up to MaxHits hit times for target A
volatile long B_Times[MaxHits];          // array to hold up to MaxHits hit times for target B
unsigned long A_splits[MaxHits];
unsigned long B_splits[MaxHits];
long AB_splits[MaxHits];
unsigned long LastTime = 0;              // initialize last LCD display time to zero for 1st pass
float A_Hit_Factor = 0;
float B_Hit_Factor = 0;
byte S1 = 0;                             // Switch 1 init to zero
byte S2 = 0;                             // Switch 2 init to zero
unsigned int diMin = 0;                  // Display index mininmim
unsigned int diMax = 99;                 // Display index maximum
unsigned int TimerMode = 0;              // initialize the mode to be single shooter
volatile int secs = 0;
volatile int frac = 0;
// char time[ ] = "      ";
// char buffer[7];                          // save 7 empty spaces



void setup()
{
  // set up the LCD's number of columns and rows: 
  lcd.begin(20, 4);
  lcd.clear();
  lcd.print("    Shot Timer 1    ");
  lcd.setCursor(0, 1);
  lcd.print("    Initializing    ");
  delay(5000);

  // initialize output pins
  pinMode(BuzzerPin, OUTPUT);
  pinMode(LEDPin, OUTPUT);
  digitalWrite(BuzzerPin, LOW);
  digitalWrite(LEDPin, LOW);
   
  // initialize the input pins with internal pullups
  pinMode(StartPin, INPUT);
  // pinMode(UpPin, INPUT);
  //pinMode(DownPin, INPUT);
  pinMode(TargetAPin, INPUT);
  pinMode(TargetBPin, INPUT);
  digitalWrite(StartPin, HIGH);   
  //digitalWrite(UpPin, HIGH);
  //digitalWrite(DownPin, HIGH);
  digitalWrite(TargetAPin, HIGH);
  digitalWrite(TargetBPin, HIGH);

  // for now use pin 6 as button input
  pinMode(ScrollPin, INPUT);
  digitalWrite(ScrollPin, HIGH);

  // opens serial port, sets data rate to 9600 bps
  Serial.begin(9600);

  // setup ext pins as interrupts
  attachInterrupt(0, ISR_A, FALLING);
  attachInterrupt(1, ISR_B, FALLING);

  lcd.setCursor(0, 1);
  lcd.print("      Ready       ");
  // PString time(buffer, sizeof(buffer));
}


void loop()
{
  if (TimerState) // is timer running
  {
    // timer is running
    // set display according to timer mode
    switch (TimerMode)
    {
      case 1:
        //this is for A vs B shooter mode
        DisplayIndex = 5;
        break;
      default: 
        // the default = single A shooter mode
        DisplayIndex = 1;   // display will show count and time of hits
    }
    
    // send data to display if there have been any hits
    if (AupDtFlag == 1 || BupDtFlag == 1)
    {
      CalcTimes();
      FormatData();
    }
    
    // timer is running so now check for any stop conditions
    if ((digitalRead(StartPin) == LOW) || ((millis() - StartTime) > MaxTime) || A_count >= MaxHits || B_count >= MaxHits)
    {
      // stop the timer change the display
      StopTimer();
      tone(BuzzerPin, FreqLo, BuzzTime10);
      // update display just in case last msec hit came in
      if (AupDtFlag == 1 || BupDtFlag == 1)
      {
        CalcTimes();
        FormatData();
      }
      
      // just for the moment send times to PC for debug
      SendTimes();
      
      // delay enough to debounce stop button
      delay(DB_delay);
      
      // Change to review-stopped state display based on timer mode
      switch (TimerMode)
      {
        case 1:
          //this is for A vs B shooter mode
          diMin = 6;
          diMax = int(max(A_count,B_count)/4) + diMin - 1; // find how many whole screens
          if (max(A_count,B_count) % 4)
          {
            diMax++ ;              // add another screen if partial exists
          }
          DisplayIndex = diMax;    // display will show list of hit times
          break;
        default: 
          // the default = single A shooter mode
          diMin = 2;
          diMax = int(A_count/8) + diMin - 1; // find how many whole screens
          if (A_count % 8)
          {
            diMax++ ;              // add another screen if partial exists
          }
          DisplayIndex = diMax;    // display will show list of hit times
      }
    }
  }
  else
  {
  // timer is stopped look for start button or review button pushes

    // check for scroll button pushes, change display state as reqd
    if (digitalRead(ScrollPin) == LOW)
    {
      DisplayIndex++;             // increment DisplayIndex upon push
      if (DisplayIndex > diMax)
      {
        DisplayIndex = diMin;     // wrap around of scrolling action
      }
      // change to new display
      FormatData();
      delay(DB_delay);
    }
    
    // check to see if start button has been pushed
    if (digitalRead(StartPin) == LOW)
    {
      // start button pushed
      // Do the following debug code only to get system running
      // This will send message to PC to show button was pushed
      Serial.println("Timer is running");
      
      // turn on the LED to show timer is running
      digitalWrite(LEDPin, HIGH);
      
      // clear all the prior runs data, reset the display
      ClearData();
      DisplayIndex = 0;
      FormatData();
      
      // delay the Wait Time from start button push
      // this delay is for debounce purposes and should stay
      delay(DB_delay);
      // this delay will change to random later on    
      delay(WaitTime);
       
      // enable the interrupts just in case
      interrupts();
      
      // save the starting time of this run
      StartTime = millis();
      
      // set state of timer to running
      TimerState = 1;
      
      // now buzz the speaker for 0.5 secs
      tone(BuzzerPin, FreqHi, BuzzTime5);
    }
  }
}


void StopTimer()
{
  //noInterrupts();
  TimerState = 0;
  // turn the LED off to show timer is stopped
  digitalWrite(LEDPin, LOW);
}


void ClearData()
{
  A_count = 0; 
  B_count = 0;
  for (int i=0; i < MaxHits ; i++)
  {
    A_Times[i] = 0;
    B_Times[i] = 0;
    A_splits[i] = 0;
    B_splits[i] = 0;
    AB_splits[i] = 0;
   }
  A_Hit_Factor = 0;
  B_Hit_Factor = 0;
}


void SendTimes()
// temp routine to send data to serial monitor
{
  Serial.println("Timer is stopped");
  Serial.println("Here are the times for Shooter A");
  Serial.print("Number of hits : ");
  Serial.print("\t");
  Serial.println(A_count);
  Serial.println("A Hit and split times are : ");
  int i = 0;
  int k = 0;
  for (i=0; i < MaxHits ; i++)
  {
    k = i + 1;
    //Serial.print(A_count[k]);
    Serial.print("\t");
    Serial.print(A_Times[i]);
    Serial.print("\t");
    Serial.println(A_splits[i]);
  }
  Serial.println("Here are the times for Shooter B");
  Serial.print("Number of hits : ");
  Serial.print("\t");
  Serial.println(B_count);
  Serial.println("B Hit and split times are : ");
  for (i=0; i < B_count ; i++)
  {
    k = i + 1;
    //Serial.print(B_count[k]);
    Serial.print("\t");
    Serial.print(B_Times[i]);
    Serial.print("\t");
    Serial.println(B_splits[i]);
  }
}


void ISR_A()
{
  if(TimerState)
  {
    // store the hit time 
    A_Times[A_count] = millis() - StartTime;
    // increment the hit count and array index
    ++A_count;
    // set the Hit flag so other stuff will update
    AupDtFlag = 1;
  }
}


void ISR_B()
{
  if(TimerState)
  {
    // store the hit time 
    B_Times[B_count] = millis() - StartTime;
    // increment the hit count and array index
    ++B_count;
    // set the Hit flag so other stuff will update
    BupDtFlag = 1;
  }
}


void CalcTimes()
{
// routine to calculate all data and declare winner
// not all calcs having meaning for uses of timer
  // calculate A splits and cumlative hit factor
  if (A_count > 1)
  {
    for (int i=1; i < A_count ; i++)
    {
      A_splits[i] = A_Times[i] - A_Times[i-1];
    }
  }
  else
  {
    A_splits[0] = A_Times[0];
  }  
  A_Hit_Factor = A_Times[A_count - 1]/A_count;
  // calculate B splits and cumlative hit factor
  if (B_count > 1)
  {
    for (int i=1; i < B_count ; i++)
    {
      B_splits[i] = B_Times[i] - B_Times[i-1];
    }
  }
  else
  {
    B_splits[0] = B_Times[0];
  }  
  B_Hit_Factor = B_Times[B_count - 1]/B_count;
  // Calculate A - B times just in case
  int Min_count = min(A_count, B_count);
  for (int i=0; i < Min_count ; i++)
    {
      AB_splits[i] = A_Times[i] - B_Times[i];
    }
// add more here for A vs B modes

}


void FormatData()
{
// routine to format lines of data to be sent to LCD
// presently assume 4 lines of 20 characters
// switch formatting done based on display state
  switch (DisplayIndex)
  {
    case 1:
      //this is for single A shooter mode
      // display number of hits so far
      lcd.clear();
      lcd.print("A: # hits = ");
      lcd.print(A_count);
      lcd.setCursor(0, 1);
      lcd.print(" Hit  time = ");
      TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      // lcd.print(time);
      lcd.setCursor(0, 2);
      lcd.print("Split time = ");
      TimeConvert(A_splits[A_count-1]); // convert split time
      // lcd.print(time);
      break;
    case 2:
    {
      // this is A review mode hits 1-8
      lcd.clear();
      int k = 0;
      for (int i=0; i < 4 ; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("  ");
        lcd.print(i+5);
        lcd.print(":");
        TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
    }
      break;
    case 3:
    {
      // this is A review mode hits 9-16
      lcd.clear();
      int k = 0;
      for (int i=8; i < 12 ; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print("A");    // this will display oddly for hits 9 and 13
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print(" ");
        lcd.print(i+5);
        lcd.print(":");
        TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
    }
      break;
    case 4:
    {
      // this is A review mode hits 17-20
      lcd.clear();
      int k = 0;
      for (int i=16; i < 20 ; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        // lcd.print(" ");
        // lcd.print(i+5);
        // lcd.print(":");
        // TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
    }
      break;
    case 5:
      //this is for AvsB shooter mode
      // display number of hits so far
      lcd.clear();
      lcd.print("A: # hits = ");
      lcd.print(A_count);
      lcd.setCursor(0, 1);
      lcd.print("A Hit time = ");
      TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      // lcd.print(time);
      lcd.setCursor(0, 2);
      lcd.print("B: # hits = ");
      lcd.print(B_count);
      lcd.setCursor(0, 3);
      lcd.print("B Hit time = ");
      TimeConvert(B_Times[A_count-1]); // Now for B times
      // lcd.print(time);
      break;
    case 6:
    {
      // this is AvsB review mode hits 1-4
      lcd.clear();
      int k = 0;
      for (int i=0; i < 4 ; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("B ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
    }
      break;
    case 7:
    {
      // this is AvsB review mode hits 5-8
      lcd.clear();
      int k = 0;
      for (int i=4; i < 8 ; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("B ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
    }
      break;
    case 8:
    {
      // this is AvsB review mode hits 9-12
      break;
      lcd.clear();
      int k = 0;
      for (int i=8; i < 10 ; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("B ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
    
      for (int i=10; i < 12 ; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("B");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
    }
      break;
    case 9:
    {
      // this is AvsB review mode hits 13-16
      lcd.clear();
      int k = 0;
      for (int i=12; i < 16 ; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("B");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
    }
      break;
    case 10:
    {
      // this is AvsB review mode hits 17-20
      lcd.clear();
      int k = 0;
      for (int i=16; i < 20 ; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("B");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
    }
      break;
    default: 
      // do the default = 0
      lcd.clear();
      lcd.print("    Shot Timer v1   ");
      lcd.setCursor(0, 1);
      lcd.print("     is running     ");
      lcd.setCursor(0, 2);      
      lcd.print("      Shooter:      ");
      lcd.setCursor(0, 3);
      lcd.print("    --STANDBY--     ");
  }
}


void TimeConvert(long input)
{
// takes the msecs as argument and sets the time for that hit as a XX.xxs string

  //time.begin();           // reset Pstring

  if (input != 0)         // Make sure there's a value...
  {
    secs = int((input + 5)/1000L);         // round msecs into whole secs
    frac = int(((input + 5) % 1000L)/10L); // find fractional rounded secs

    if (secs < 1)
    {
      lcd.print(" 0");       // for sub 1 sec times pad with space plus zero
    }
    else
    {
      if (secs < 10)
      {
        lcd.print(' ');             // for sub 10 sec times pad with space
      }
    }
    lcd.print(secs);                // now add whole secs 
    lcd.print('.');                 // now add period

    if (frac < 10)
    {
      lcd.print(' ');               // If frac < 10 then pad with a blank
    }
    lcd.print(frac);                // now add fractional secs
    lcd.print('s');                 // add s to tail
  }
  else
  {
    lcd.print("      ");         // If the input variable is zero set time to 6 blank spaces
  }
}

Just noticed a copy’n’paste error that will make certain times look odd. The code should still compile and run but 12.011 secs

will display like 12. 1s

and not the 12.01s desired. The fix for that is below.

void TimeConvert(long input)
{
// takes the msecs as argument and sets the time for that hit as a XX.xxs string

  //time.begin();           // reset Pstring

  if (input != 0)         // Make sure there's a value...
  {
    secs = int((input + 5)/1000L);         // round msecs into whole secs
    frac = int(((input + 5) % 1000L)/10L); // find fractional rounded secs

    if (secs < 1)
    {
      lcd.print(" 0");       // for sub 1 sec times pad with space plus zero
    }
    else
    {
      if (secs < 10)
      {
        lcd.print(' ');             // for sub 10 sec times pad with space
      }
    }
    lcd.print(secs);                // now add whole secs 
    lcd.print('.');                 // now add period

    if (frac < 10)
    {
      lcd.print('0');               // If frac < 10 then pad with a zero
    }
    lcd.print(frac);                // now add fractional secs
    lcd.print('s');                 // add s to tail
  }
  else
  {
    lcd.print = ("      ");         // If the input variable is zero set time to 6 blank spaces
  }
}

sspbass:
I did get the code to compile …

I didn’t notice the above until just now. That’s a step forward ! :dance:

If it compiles I feel it has a good chance of running properly and even looking puuurty (w/the above fix). :liar:

Alright, my first jaunt through the code and nothing jumps out screaming that it is wrong. I need to step through the main loop a bit to get the hang of what it is supposed to be doing.

One thing I like to do for state machines (and this fits that pattern) is to use an enum to make it more readable. Something like:

enum state { INIT, IDLE, RUN, REVIEW };

Then in your code you define your enum var:

enum running_state state = INIT;

And in your main loop you can do:

if(running_state == INIT)

I am still not sure why it is refreshing the LCD so much, that is kind of crazy. You could have some preformed strings and do some replacement on certain regions. You would use regular character arrays for this and for me the management is simple. I’ll post examples later. Sure it isn’t as pretty as a String class but it is going to be efficient and you will control memory precisely.

Dan

dkulinski:
I am still not sure why it is refreshing the LCD so much, that is kind of crazy.

Dan

It depends on what rev of code you’re looking at. sspbass did a rev that took the ideas used in the Arduino clock and modified the ISRs to do a lot of what was done in that code. It worked but only for the hit-by-hit display, which is what was seen in the recent video. I looked at the “clock code” and modified the prior TimeConvert() function to do things a new way so as to avoid being “Strung up”. Given that code only updates the display when a hit happens or when a button is pushed, and that used to work before the memory mangling fiasco, you won’t see the last piece of code posted making the display flicker. I didn’t spend much time looking at the version that did make that happen as I figured it would be leapfrogged. Hopefully the last revison of code has done just that.

dkulinski:
One thing I like to do for state machines (and this fits that pattern) is to use an enum to make it more readable. Something like:

enum state { INIT, IDLE, RUN, REVIEW };

Dan

I like it ! I tried to do something similar but when odd crap started happening I pulled anything new out. While I might want someday to figure out why my String code acted so wonky, that day ain’t today. But putting the above in is good practice as a year from now it’ll be much easier to (re)figure out.

dkulinski:
I need to step through the main loop a bit to get the hang of what it is supposed to be doing.

Dan

The basic idea is that the timer is either running or stopped. When running the ISRs are enabled to record hit times. Whenever a hit happens the various calculations are performed and the display updated. Then the code looks for a reason to become stopped. There are several ATM ; a stop button push, a time out or a max number of hits on the A or B channels. These conditions will lend themselves to further game or Course of Fire usage. When going to a stopped state, the display is changed to the proper review display (to happen when the user presses a scroll button). Which displays are used when depends on the timer state and the timer mode.

The timer mode determines what the basic functionality of the timer is. One mode might just be a normal timer, recording hits for a single shooter until stopped. Another mode might be an A shooter vs B shooter with the first to score X hits stopping the timer and being declared a winner. Other “game” modes will be added and each will modify the behaviour of the timer when running and what’s displayed when running or stopped.

When stopped the timer looks for a start button push or a (display) scroll button push. The latter just advances forward ( at present, and will scroll backwards when the button is added) the various “review of data” displays. DisplayIndex is the controlling variable for this and it gets limited so as to only display the displays appropriate for the timer mode of operation and the number of hits (no need to scroll through 50 empty hits when 6 hits constitutes a win). When a start push is detected the timer does a countdown routine, waiting some amount of time (later this will be variable), buzzing the buzzer, lighting a “run” LED and finally putting the timer state to “running”.

Later on another timer mode, and buttons, will need to be added to go to a configuration mode with displays and an interactive user interface. Then the operator will be able to specify which timer mode he/she wants and configure parameters (time out, winning hits, etc, etc) needed for any and all modes. This has yet to be added.

And that’s basically it. For debug purposes only there are calcs done and results sent to the PC serial monitor but when the project is “done”, those can go away (or be keyed to a debug = enabled condition via a config input). At one point the timer was timing and displays were showing hit-by-hit times and even the review displays were scrolling from screen to screen. Then 1 lil addition caused everthing to go right down the toilet … for no good reason that I could see in the code itself. My guess is some inherent problem with the String class usage was just below the hurt threshold and a seemingly minor change broke that barrier (memory overwriting memory already in use). If we can get back to goodness, then sspbass can try out the A vs B mode (the only other one presently implemented). I’m very curious to see how the Arduino will handle 2 simultaneous hits. Everything after that is gravy.

The Good (and so close to pretty) …

http://www.youtube.com/watch?v=7Nf7ELnb … e=youtu.be

The Bad (tried to pretty up the display from the above and this is the thanks I get) …

http://www.youtube.com/watch?v=ZN5uJ0c9 … e=youtu.be

And the Ugly (WTF, self reseting Arduino ?!?) …

http://www.youtube.com/watch?v=AyZ2U62B … e=youtu.be

Well, back in business. Here is the latest code running.

http://www.youtube.com/watch?v=KWOhAZgt … e=youtu.be

Well that’s a Sh^&load better ! The review page with hit #9 on it was a pre-known odd looker due to it being the only place with a single digit (9) mixed with double digits (10+). I think I have a way to make that look better, it just wasn’t a priority. As for the hits not showing up while occurring … I have to GUESS that’s a result of updating the display very very quickly, which isn’t supposed to happen. I wonder if the B channel is somehow active ? Does the serial monitor show any B hits and times ? Other than that … I dunno ATM. Lastly I noticed there was a space00.XX split time. I’ll have to look into that. The revised code is supposed to handle that situation, but apparently it isn’t.

So when you get a chance, change the initialization of TimerMode =1 (per that long ago post) and see if the A vs B mode works in any way. Meanwhile, during the tech’s breaks here at work, I’ll see if I can squash some bugs.

I haven’t yet figured out why the running hits display is so dim (fast updating is my guess) but here’s a few fixes for the formatting problems I saw. I think the new method of doing the math yeilds a “secs” with a zero for sub-1sec times, so I took a guess and changed that part just to see what happens. So give it a last whack less than 1 sec after the next to last whack and let’s see how that display looks. Also recheck the rounding of times vs the serial monitor display. The method/math is a little different from when it last worked so it needs to be verified again.

In addition to the formatting fixes, I added debug printout to the below. If you see a steady stream of “Too quick” on the serial monitor, it means that, somehow, the display is being updated much much quicker than desired or designed.

void FormatData()
{

// debug stuff to figure out dim display
if((millis() - LastTime) < 100)
{
  Serial.println("Too quick");
}
LastTime = millis();

// routine to format lines of data to be sent to LCD
// presently assume 4 lines of 20 characters
// switch formatting done based on display state
  switch (DisplayIndex)
  {
    case 1:
      //this is for single A shooter mode
      // display number of hits so far
      lcd.clear();
      lcd.print(" A: # hits = ");
      lcd.print(A_count);
      lcd.setCursor(0, 1);
      lcd.print(" Hit  time = ");
      TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      // lcd.print(time);
      lcd.setCursor(0, 2);
      lcd.print("Split time = ");
      TimeConvert(A_splits[A_count-1]); // convert split time
      // lcd.print(time);
      break;
    case 2:
      // this is A review mode hits 1-8
      lcd.clear();
      int k = 0;
      for (int i=0; i < 4 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("  ");
        lcd.print(i+5);
        lcd.print(":");
        TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
      break;
    case 3:
      // this is A review mode hits 9-16
      lcd.clear();
      int k = 0;
      for (int i=8; i < 12 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");    // this will display oddly for hits 9 and 13
        if(i == 8)
        {
          lcd.print(" ");
        }
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print(" ");
        lcd.print(i+5);
        lcd.print(":");
        TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
      break;
    case 4:
      // this is A review mode hits 17-20
      lcd.clear();
      int k = 0;
      for (int i=16; i < 20 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        // lcd.print(" ");
        // lcd.print(i+5);
        // lcd.print(":");
        // TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
      break;
    case 5:
      //this is for AvsB shooter mode
      // display number of hits so far
      lcd.clear();
      lcd.print("A: # hits = ");
      lcd.print(A_count);
      lcd.setCursor(0, 1);
      lcd.print("A Hit time = ");
      TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      // lcd.print(time);
      lcd.setCursor(0, 2);
      lcd.print("B: # hits = ");
      lcd.print(B_count);
      lcd.setCursor(0, 3);
      lcd.print("B Hit time = ");
      TimeConvert(B_Times[A_count-1]); // Now for B times
      // lcd.print(time);
      break;
    case 6:
      // this is AvsB review mode hits 1-4
      lcd.clear();
      int k = 0;
      for (int i=0; i < 4 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("B ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
      break;
    case 7:
      // this is AvsB review mode hits 5-8
      lcd.clear();
      int k = 0;
      for (int i=4; i < 8 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("B ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
      break;
    case 8:
      // this is AvsB review mode hits 9-12
      break;
      lcd.clear();
      int k = 0;
      for (int i=8; i < 10 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("B ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
      for (int i=10; i < 12 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("B");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
      break;
    case 9:
      // this is AvsB review mode hits 13-16
      lcd.clear();
      int k = 0;
      for (int i=12; i < 16 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("B");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
      break;
    case 10:
      // this is AvsB review mode hits 17-20
      lcd.clear();
      int k = 0;
      for (int i=16; i < 20 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
        lcd.print("B");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
        // lcd.print(time);
      }
      break;
    default: 
      // do the default = 0
      lcd.clear();
      lcd.print("    Shot Timer v1   ");
      lcd.setCursor(0, 1);
      lcd.print("     is running     ");
      lcd.setCursor(0, 2);      
      lcd.print("      Shooter:      ");
      lcd.setCursor(0, 3);
      lcd.print("    --STANDBY--     ");
  }
}

and …

void TimeConvert(long input)
{
// takes the msecs as argument and sets the time for that hit as a XX.xxs string

  //time.begin();           // reset Pstring

  if (input != 0)         // Make sure there's a value...
  {
    secs = int((input + 5)/1000L);         // round msecs into whole secs
    frac = int(((input + 5) % 1000L)/10L); // find fractional rounded secs

    if (secs < 1)
    {
      lcd.print(' ');       // for sub 1 sec times pad with space plus zero
    }
    else
    {
      if (secs < 10)
      {
        lcd.print(' ');             // for sub 10 sec times pad with space
      }
    }
    lcd.print(secs);                // now add whole secs 
    lcd.print('.');                 // now add period

    if (frac < 10)
    {
      lcd.print('0');               // If frac < 10 then pad with a zero
    }
    lcd.print(frac);                // now add fractional secs
    lcd.print('s');                 // add s to tail
  }
  else
  {
    lcd.print = ("      ");         // If the input variable is zero set time to 6 blank spaces
  }
}

Congrats on getting it to a nearly working state again. Always nice to have that kind of progress.

I went through the code again and it seems there are some math operations that shouldn’t be happening. When you store the times of the hits, don’t subtract the start time. This can be done at display time and at review time.

As for the interrupts, maybe turn them on when the start signal is received and then turn them off when the buzzer is off? This way you can get rid of some control structures in the ISRs.

For the display updates, this I am not quite sure of. It is obviously updating very quickly causing this to look dim. You are also passing many many strings to display which makes the code cluttered but not wrong. The loop doesn’t look wrong but then again this isn’t completely trivial. You are handling it like a state machine which is good strategy. I just finished up some yard work so I am going to drop your code into the IDE and look at a bit more closely now.

Dan