Counting and Timing a shock sensor

Why doesn’t it like this?

sspbass:
Why doesn’t it like this?

Because tmp hasn’t been declared anywhere. My thinking was to declare it as a global variable, right up front and try to stay away from dynamic memory allocation as much as possible. Thus there needs to be a :

char tmp = “01.23s”;

at the top where all the other declarations are made.

Also TimeConvert() needs to be changed so it doesn’t give back a String type variable.

It doesn’t like that either. Now it says invalid conversion from const char to char.

wish I were more help with this stuff.

sspbass:
It doesn’t like that either. Now it says invalid conversion from const char to char.

wish I were more help with this stuff.

Hmmm not sure about that. Problem is I’m not sure how to change TimeConvert(). I used some String functions in it because it was easy to do. I don’t think those same commands will work on a string of characters. It might be that compiling with the old TimeConvert() confuses the compiler. I’d have thought the declaration above would result in a variable, of type char, to be initialized to 01.123s .If the word const had been used in front of char, it would be declared a constant, never to be changed, of type char.

OK, here’s real guess at a slightly modified TimeConvert(). It still uses String functionality to do it’s job but should return a 6 character char type variable. I spent 30 secs on this so don’t bet the farm.

char TimeConvert(long time)
{
// takes the time as argument and returns the time for that hit as a XX.xxs string
  if (time != 0) // Make sure there's a value...
  {
    String tmp3 = String(int((time + 5)/10)); // round msecs into csecs
    int k = tmp3.length();  // k will be index to last char in padded string
    int km1 = k-1;          // km1 will be index to next to last char
    int km2 = k-2;          // km2 will be position of period
    tmp3 = tmp3 + '0';  // pad the end of the truncated string with a zero
    //now move chars to make space to add a period
    tmp3.setCharAt(k, tmp3.charAt(km1));   // move next-2-last to last position in string
    tmp3.setCharAt(km1, tmp3.charAt(km2)); // move char to next-2-last position
    // now insert period
    tmp3.setCharAt(km2, '.');
    // add a blank or 2 as needed to get 5 digits with time right justified
    if (k == 2)
    {
      tmp3 = " 0" + tmp3;     // must be a sub 1 sec time so add a blank and a leading 0
    }
    else
    {
      if (k == 3)
      {
        tmp3 = ' ' + tmp3;    // must be a sub 10 sec time so add a blank to justify
      }
    {
    // tmp3 now holds rounded time in secs XX.xx format
    tmp3 += 's':       // add s for seconds
    char tmp2 = char(tmp3);
    return tmp2;
  }
  else
  {
    char tmp2 = "      "; // If the time variable is zero pass 6 blanks back as a string...
    return tmp2;
  }
}

So did you change all the other case statements in FormatData() to no loonger use Strings and mimic what I did for the case = 1 and = 2 ?

Just the single shooter mode ones.

It’s still hanging me up with the same errors we were just dealing with.

sspbass:
Just the single shooter mode ones.

It’s still hanging me up with the same errors we were just dealing with.

OK, doh. It’s a string (not a String, note the difference). So the declaration should be

char tmp = “01.23s”;

note the . This tells the compiler it’s a string of characters and by-the-way, figure out how many chars from the stuff between the " ".

http://arduino.cc/en/Reference/String

sspbass:
Just the single shooter mode ones.

Since I don’t think the timer can get to the other ones, I don’t think they’ll cause a problem but the compiler might complain about the variable Line not being declared (unless you left that declaration in, a good thing for now)

Along the lines of the preceeding post re: char declarations … I’m not sure if the prior changed version of TimeConvert() will compile or not. If it doesn’t here’s another version than might fix a mistake re: char declaration and conversion. Basically the same correction as above, I’m just not sure if it’s needed. In any case when you get a chance you can try both (as/if needed).

char TimeConvert(long time)
{
// takes the time as argument and returns the time for that hit as a XX.xxs string
  if (time != 0) // Make sure there's a value...
  {
    String tmp3 = String(int((time + 5)/10)); // round msecs into csecs
    int k = tmp3.length();  // k will be index to last char in padded string
    int km1 = k-1;          // km1 will be index to next to last char
    int km2 = k-2;          // km2 will be position of period
    tmp3 = tmp3 + '0';  // pad the end of the truncated string with a zero
    //now move chars to make space to add a period
    tmp3.setCharAt(k, tmp3.charAt(km1));   // move next-2-last to last position in string
    tmp3.setCharAt(km1, tmp3.charAt(km2)); // move char to next-2-last position
    // now insert period
    tmp3.setCharAt(km2, '.');
    // add a blank or 2 as needed to get 5 digits with time right justified
    if (k == 2)
    {
      tmp3 = " 0" + tmp3;     // must be a sub 1 sec time so add a blank and a leading 0
    }
    else
    {
      if (k == 3)
      {
        tmp3 = ' ' + tmp3;    // must be a sub 10 sec time so add a blank to justify
      }
    {
    // tmp3 now holds rounded time in secs XX.xx format
    tmp3 += 's';       // add s for seconds
    char tmp2 [ ] = char(tmp3);
    return tmp2;
  }
  else
  {
    char tmp2 [ ] = "      "; // If the time variable is zero pass 6 blanks back as a string...
    return tmp2;
  }
}

EDIT : I found a typo in the above but odd thing is, it’s not somewhere I’ve touched recently. It’s been there since the rev that added the “s” at the tail and still worked ??!!?? In any case the above is fixed.

tmp3 += ‘s’: should have been, and now is, tmp3 += ‘s’;

Mee_n_Mac:

sspbass:
Just the single shooter mode ones.

It’s still hanging me up with the same errors we were just dealing with.

OK, doh. It’s a string (not a String, note the difference). So the declaration should be

char tmp = “01.23s”;

note the . This tells the compiler it’s a string of characters and by-the-way, figure out how many chars from the stuff between the " ".

http://arduino.cc/en/Reference/String

Is it possible that when we are setting tmp = “what we want to display” its trying to set one location, in this case tmp [7], in the string (an array of characters?) to the time?

sspbass:
Is it possible that when we are setting tmp = “what we want to display” its trying to set one location, in this case tmp [7], in the string (an array of characters?) to the time?

I think you are correct. I’ll guess the proper syntax is tmp = “blah blah blah”.

Another possibility is that the conversion of a String to a char array in TimeConvert() is not being done correctly. Usually you convert a number to a char array using the char() command. It may be this is not the proper way to do it for a String. Read this …

http://stackoverflow.com/questions/7383 … on-arduino

http://arduino.cc/en/Reference/StringToCharArray

So in TimeConvert() it might be better to do this.

char TimeConvert(long time)
{
// takes the time as argument and returns the time for that hit as a XX.xxs string
  if (time != 0) // Make sure there's a value...
  {
    String tmp3 = String(int((time + 5)/10)); // round msecs into csecs
    int k = tmp3.length();  // k will be index to last char in padded string
    int km1 = k-1;          // km1 will be index to next to last char
    int km2 = k-2;          // km2 will be position of period
    tmp3 = tmp3 + '0';  // pad the end of the truncated string with a zero
    //now move chars to make space to add a period
    tmp3.setCharAt(k, tmp3.charAt(km1));   // move next-2-last to last position in string
    tmp3.setCharAt(km1, tmp3.charAt(km2)); // move char to next-2-last position
    // now insert period
    tmp3.setCharAt(km2, '.');
    // add a blank or 2 as needed to get 5 digits with time right justified
    if (k == 2)
    {
      tmp3 = " 0" + tmp3;     // must be a sub 1 sec time so add a blank and a leading 0
    }
    else
    {
      if (k == 3)
      {
        tmp3 = ' ' + tmp3;    // must be a sub 10 sec time so add a blank to justify
      }
    {
    // tmp3 now holds rounded time in secs XX.xx format
    tmp3 += 's';       // add s for seconds
    char tmp2 [7];
    tmp3.toCharArray(tmp2, 7);
    return tmp2;
  }
  else
  {
    char tmp2 [ ] = "      "; // If the time variable is zero pass 6 blanks back as a string...
    return tmp2;
  }
}

The question I have is whether the char tmp2 [7] declaration will place a null character at the end automagically. The string should be 6 chars long, Xx.xxs, so 7 place holders should always be enough. Searching around I find all manner of people having problems using the String class but little in the way of solutions. In the end I think the right thing will be to rewite TImeConvert() to not use String class objects but since it’s worked in the past and w/o the timer going all wiggy, I hate to make the jump. I’d like to see the code get back to some semblance of where it was Fri night before 1 lil change sent it down the rabbit-hole. I still have a hard time believing that the little dynamic memory allocation the code was doing was somehow leaving no contiguous memory locations spaning long enough to do what the code needed to do. There should have been >1 kB left easy. :think:

If I can’t find a replacement mobo for the old notebook, I’ll have to spring for a cheapie do-for in the next day or so and then I can try 10 million things out to see which one works. This is painful, especially after the quick progress earlier on. :shifty:

Check this out

http://www.wimp.com/nerddating/

The code is at a link in the description.

What if we were to do something like this and write the elapsedSeconds and fractionalSecs integers to two arrays when the ISR is triggered.

Then we can just write something like this

lcd.print (elapsedSeconds _":" fractionalSecs *"s")*_
_*```*_
_*ETA: oops wrong link. I'll leave that one up though since it's pretty damn funny.*_
_*Here is the correct link.*_
_*[http://www.youtube.com/watch?v=xUwRoOCnve4](http://www.youtube.com/watch?v=xUwRoOCnve4)*_

I’ll have a look at it later should I get some free time but don’t let that stop you from giving it a try !

(I assume nothing above worked ?)

No luck, same issue. I’m going to go for it. If I get anything working I’ll post it here immediately.

sspbass:
No luck, same issue. I’m going to go for it.

Crap ! I know it's some stupid syntax mistake. AARRRRRRRRRRRRRGGGGGGGGGGGHHHHHHHHHHHH !!!!!!!!!

sspbass:
If I get anything working I’ll post it here immediately.

Good ! Vid or it didn't happen. :twisted:

Here it is.

me being a noob I was only able to get the main screen but at least the concept works.

Not so sure about the rounding though.

Check the vid. http://www.youtube.com/watch?v=7VsHQH0o … e=youtu.be

and the code

#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 = 20;                 // 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 A_splitsSecs[MaxHits];     // Array to hold seconds portion of splits
unsigned long A_splitsCentiSecs[MaxHits]; // Array to hold centiseconds portion of splits
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
char tmp[] = "01.23s";                    // sets tmp to be used for fomatting the data to send to the lcd
long elapsedTime;                         // Elapsed time between hits
int fractional;                          // Used to store fractional part of frames
int fractionalSecs;                      // Used to store fractional part of seconds
int elapsedFrames;
int elapsedSeconds;
int elapsedMinutes;
int elapsedCentiSeconds;
char buf[10];                            // string buffer for itoa fuction
unsigned long A_Secs[MaxHits];            // Array to hold seconds portion of time
unsigned long A_CentiSecs[MaxHits];      // Array to hld centiseconds portion of time
int frameRate = 100;                     // Frames per second at which the timer runs
long interval = (1000/frameRate);        // Blink interval



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       ");
}


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;
    elapsedTime = millis() - StartTime;
    elapsedSeconds = (elapsedTime / 1000L);
    //elapsedMinutes = (elapsedTime / 60000L);
    elapsedFrames = (elapsedTime / interval);
    fractional = (int)(elapsedFrames % frameRate);
    fractionalSecs = (int)(elapsedSeconds % 60L);
    //fractionalMins = (int)(elapsedMinutes % 60L);
    A_Secs[A_count] = fractionalSecs;
    A_CentiSecs[A_count] = fractional;
    // 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];
      A_splitsSecs[i] = A_Secs[i] - A_Secs[i-1];
      A_splitsCentiSecs[i] = A_CentiSecs[i] - A_CentiSecs[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:
    lcd.clear();
     

      //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 = ");
      
     
      if (fractionalSecs < 10) {
        lcd.print ("0");
      }
        lcd.print(itoa(fractionalSecs, buf, 10));
          lcd.print(":");
          
       lcd.print(itoa(fractional, buf, 10));  
          
      //tmp = TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      //lcd.print tmp;
      lcd.setCursor(0, 2);
      lcd.print("Split time = ");
      lcd.print (itoa(A_splitsSecs[A_count - 1], buf, 10));
      lcd.print (":");
      lcd.print (itoa(A_splitsCentiSecs[A_count - 1], buf, 10));
      
      //tmp = TimeConvert(A_splits[A_count-1]); // convert split time
      //lcd.print tmp;
     
      break;
    case 2:
    /*
      // this is A review mode hits 1-8
      lcd.clear();
      for (int i=0; i < 4 ; i++)    // do 4 lines of LCD display
      {
        lcd.print("A ");              // show it's the A shooter
        lcd.print (i+1);              // hit number
        lcd.print(":");               // delimiter
        tmp = TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print tmp;                // send time to LCD
        lcd.print("  ");                // spacer
        lcd.print (i+5);               // next hit number to be displayed
        lcd.print(":");                 // delimiter
        tmp = TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
        lcd.print tmp;                 // send time to LCD
        lcd.setCursor(0, (i+1));    // go to next LCD line
      }
      */
      break;
    case 3:
    /*
      // this is A review mode hits 9-16
      lcd.clear();
      for (int i=0; i < 4 ; i++)    // do 4 lines of LCD display
      {
        lcd.print("A ");              // show it's the A shooter
        lcd.print (i+1);              // hit number
        lcd.print(":");               // delimiter
        tmp = TimeConvert(A_Times[i+8]); // convert hit time to XX.xx format
        lcd.print tmp;                // send time to LCD
        lcd.print("  ");                // spacer
        lcd.print (i+5);               // next hit number to be displayed
        lcd.print(":");                 // delimiter
        tmp = TimeConvert(A_Times[i+12]); // convert hit time to XX.xx format
        lcd.print tmp;                 // send time to LCD
        lcd.setCursor(0, (i+1));    // go to next LCD line
      }
      */
      break;
    case 4:
   
    /*
      // this is A review mode hits 17-20
      lcd.clear();
      for (int i=0; i < 4 ; i++)    // do 4 lines of LCD display
      {
        lcd.print("A ");              // show it's the A shooter
        lcd.print (i+1);              // hit number
        lcd.print(":");               // delimiter
        tmp = TimeConvert(A_Times[i+16]); // convert hit time to XX.xx format
        lcd.print tmp;                // send time to LCD
        lcd.print("  ");                // spacer
        lcd.print (i+5);               // next hit number to be displayed
        lcd.print(":");                 // delimiter
        tmp = TimeConvert(A_Times[i+20]); // convert hit time to XX.xx format
        lcd.print tmp;                 // send time to LCD
        lcd.setCursor(0, (i+1));    // go to next LCD line
      }
      */
    
      break;

  }
}
      /*
    case 5:{
      //this is for AvsB shooter mode
      // display number of hits so far
      String tmp = "A: # hits = ";
      String Line = tmp + A_count;
      lcd.clear();
      lcd.print(Line);
      // now display the time of last hit in secs out to hundreths of secs
      tmp = TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      Line = "A Hit time = " + tmp;
      lcd.setCursor(0, 1);
      lcd.print(Line);
      tmp = "B: # hits = ";
      Line = tmp + B_count;
      lcd.setCursor(0, 2);
      lcd.print(Line);
      // now display the time of last hit in secs out to hundreths of secs
      tmp = TimeConvert(B_Times[B_count-1]); // convert hit time to XX.xx format
      Line = "B Hit time = " + tmp;
      lcd.setCursor(0, 3);
      lcd.print(Line);
      break;}
    case 6:{
      // this is AvsB review mode hits 1-4
      String tmp = TimeConvert(A_Times[0]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(B_Times[0]); // convert hit time to XX.xx format
      String Line = "A 1:" + tmp + "B 1:" + tmp2;
      lcd.clear();
      lcd.print(Line);
      tmp = TimeConvert(A_Times[1]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[1]); // convert hit time to XX.xx format
      Line = "A 2:" + tmp + "B 2:" + tmp2;
      lcd.setCursor(0, 1);
      lcd.print(Line);
      tmp = TimeConvert(A_Times[2]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[2]); // convert hit time to XX.xx format
      Line = "A 3:" + tmp + "B 3:" + tmp2;
      lcd.setCursor(0, 2);
      lcd.print(Line);
      tmp = TimeConvert(A_Times[3]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[3]); // convert hit time to XX.xx format
      Line = "A 4:" + tmp + "B 4:" + tmp2;
      lcd.setCursor(0, 3);
      lcd.print(Line);
      break;}
    case 7:{
      // this is A review mode hits 5-8
      String tmp = TimeConvert(A_Times[4]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(B_Times[4]); // convert hit time to XX.xx format
      String Line = "A 5:" + tmp + "B 5:" + tmp2;
      lcd.clear();
      lcd.print(Line);
      tmp = TimeConvert(A_Times[5]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[5]); // convert hit time to XX.xx format
      Line = "A 6:" + tmp + "B 6:" + tmp2;
      lcd.setCursor(0, 1);
      lcd.print(Line);
      tmp = TimeConvert(A_Times[6]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[6]); // convert hit time to XX.xx format
      Line = "A 7:" + tmp + "B 7:" + tmp2;
      lcd.setCursor(0, 2);
      lcd.print(Line);
      tmp = TimeConvert(A_Times[7]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[7]); // convert hit time to XX.xx format
      Line = "A 8:" + tmp + "B 8:" + tmp2;
      lcd.setCursor(0, 3);
      lcd.print(Line);
      break;}
    case 8:{
      // this is A review mode hits 9-12
      String tmp = TimeConvert(A_Times[8]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(B_Times[8]); // convert hit time to XX.xx format
      String Line = "A 9:" + tmp + "B 9:" + tmp2;
      lcd.clear();
      lcd.print(Line);
      tmp = TimeConvert(A_Times[9]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[9]); // convert hit time to XX.xx format
      Line = "A10:" + tmp + "B10:" + tmp2;
      lcd.setCursor(0, 1);
      lcd.print(Line);
      tmp = TimeConvert(A_Times[10]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[10]); // convert hit time to XX.xx format
      Line = "A11:" + tmp + "B11:" + tmp2;
      lcd.setCursor(0, 2);
      lcd.print(Line);
      tmp = TimeConvert(A_Times[11]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[11]); // convert hit time to XX.xx format
      Line = "A12:" + tmp + "B12:" + tmp2;
      lcd.setCursor(0, 3);
      lcd.print(Line);
      break;}
    case 9:{
      // this is A review mode hits 13-16
      String tmp = TimeConvert(A_Times[12]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(B_Times[12]); // convert hit time to XX.xx format
      String Line = "A13:" + tmp + "B13:" + tmp2;
      lcd.clear();
      lcd.print(Line);
      tmp = TimeConvert(A_Times[13]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[13]); // convert hit time to XX.xx format
      Line = "A14:" + tmp + "B14:" + tmp2;
      lcd.setCursor(0, 1);
      lcd.print(Line);
      tmp = TimeConvert(A_Times[14]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[14]); // convert hit time to XX.xx format
      Line = "A15:" + tmp + "B15:" + tmp2;
      lcd.setCursor(0, 2);
      lcd.print(Line);
      tmp = TimeConvert(A_Times[15]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[15]); // convert hit time to XX.xx format
      Line = "A16:" + tmp + "B16:" + tmp2;
      lcd.setCursor(0, 3);
      lcd.print(Line);
      break;}
    case 10:{
      // this is A review mode hits 17-20
      String tmp = TimeConvert(A_Times[16]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(B_Times[16]); // convert hit time to XX.xx format
      String Line = "A17:" + tmp + "B17:" + tmp2;
      lcd.clear();
      lcd.print(Line);
      tmp = TimeConvert(A_Times[17]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[17]); // convert hit time to XX.xx format
      Line = "A18:" + tmp + "B18:" + tmp2;
      lcd.setCursor(0, 1);
      lcd.print(Line);
      tmp = TimeConvert(A_Times[18]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[18]); // convert hit time to XX.xx format
      Line = "A19:" + tmp + "B19:" + tmp2;
      lcd.setCursor(0, 2);
      lcd.print(Line);
      tmp = TimeConvert(A_Times[19]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[19]); // convert hit time to XX.xx format
      Line = "A20:" + tmp + "B20:" + tmp2;
      lcd.setCursor(0, 3);
      lcd.print(Line);
      break;}
    default:{ 
      // do the default = 0
      // 4 string objects
      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--     ");
  }
  }
}

*/
/*
char TimeConvert(long time);

// takes the time as argument and returns the time for that hit as a XX.xxs string
  if (time != 0) // Make sure there's a value...
  {
    String tmp3 = String(int((time + 5)/10)); // round msecs into csecs
    int k = tmp3.length();  // k will be index to last char in padded string
    int km1 = k-1;          // km1 will be index to next to last char
    int km2 = k-2;          // km2 will be position of period
    tmp3 = tmp3 + '0';  // pad the end of the truncated string with a zero
    //now move chars to make space to add a period
    tmp3.setCharAt(k, tmp3.charAt(km1));   // move next-2-last to last position in string
    tmp3.setCharAt(km1, tmp3.charAt(km2)); // move char to next-2-last position
    // now insert period
    tmp3.setCharAt(km2, '.');
    // add a blank or 2 as needed to get 5 digits with time right justified
    if (k == 2)
    {
      tmp3 = " 0" + tmp3;     // must be a sub 1 sec time so add a blank and a leading 0
    }
    else
    {
      if (k == 3)
      {
        tmp3 = ' ' + tmp3;    // must be a sub 10 sec time so add a blank to justify
      }
    {
    // tmp3 now holds rounded time in secs XX.xx format
    tmp3 += 's';       // add s for seconds
    char tmp2 [7];
    tmp3.toCharArray(tmp2, 7);
    return tmp2;
  }
  else
  {
    char tmp2 [ ] = "      "; // If the time variable is zero pass 6 blanks back as a string...
    return tmp2;
  }
    }
  

}
*/

Fantastic ! I’ll try to look at it when I get back home tomorrow. So perhaps it’s back to where it was Fri, trying to get the display all tarted up and puurty, but mostly functional. At least no completely odd wacky crap. To that end I think it pays to run an extended test, turn the timer on and do a lot of starts and stops w/no reset in between. This way if the memory is still being improperly allocated and freed, the problem should (hopefully) show up, even if it’s just a little memory leak each run. That way we don’t have a WTF moment later on due to a lingering, but smaller, problem. If it doesn’t show up then there’s a good chance it’s been solved.

I looked at what was done and adapted the TimeConvert() function to do a similar method. I think (short of some typos) the following will work. I am counting on using the PString function noted some posts ago. Using this and the new TimeConvert() function will save a ton of typing. The only thing I’m not sure about is if I have to declare the time string variable used by PString. The examples shown for PString did not do that so I’m thinking the initial PString call does that. As I understand it, PString sends “stuff” to a variable, similar to what print() does. So TimeConvert() builds this variable up just like the rest of the code does when printing to the LCD, char and integer by char and integer. Then the variable, time, is printed to the LCD. Seems easy. :shock:

If this does work then the review display for hit #9 is “ugly” but can be fixed. The AvsB reviews can be improved as well (IMO).

Give it a try.

#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)
    {
      time.print(" 0");       // for sub 1 sec times pad with space plus zero
    }
    else
    {
      if (secs < 10)
      {
        time.print(' ');             // for sub 10 sec times pad with space
      }
    }
    time.print(secs);                // now add whole secs 
    time.print('.');                 // now add period

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

See if what I did makes sense. I couldn’t find a good way to pass a string (char array) out of a function so I made a few new global variables and have the TimeConVert() function modify those when it’s called.

http://arduiniana.org/libraries/PString/

I see you are using a variable called “input” in the TimeConvert function but I dont see it declared anywhere in the code. Is this correct?