Counting and Timing a shock sensor

sspbass:
And doesn’t the analog buttons need to use two different resistors in order for the voltage to be different when one is shorted?

Just to answer this question while I’m pondering why the new function to change times → strings ain’t working.

The voltage divider is different when S1 is pushed and different again when S2 is pushed. Each shorts out a different resistor, changing the ratio of the “bottom” resistors and the “top” resistors to the whole resistance. If you wanted to add an S3 and S4 to short out the topmost and bottommost Rs, then those would have to be different … perhaps 2.2K ohms instead of 5K.

Doesn’t the squiggly line next to pin 6 denote a pwm/analog pin?

It wasn’t in use previously.

In a few minutes I’ll add a link to the latest video with some commentary to explain a few things.

Had a friend come over so I didn’t get much time to work with this tonight.

I’ll have to try the debugging code more tomorrow.

Although I do have some comments regarding the debugging code in the video once I get it uploaded.

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

Mee_n_Mac:
There’s obviously something wrong with the following function (something probably obvious to a real Arduino person) or how it’s being called/used.

String TimeConvert(long time)

{
// takes the time as argument and returns the time for that hit as a XX.xx string
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, ‘.’);
// tmp3 now holds rounded time in secs XX.xx format
return tmp3;
}




And here's 2 snippets of it's usage.


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


String tmp = TimeConvert(A_Times[0]); // convert hit time to XX.xx format


I note from the LCD that the 1'st usage seems not to work at all (or the other data sent to the display is AFU) and the second usage does seem to return a string but it's the same string every time, even with the input argument (supposedly) changed.

Function looks and works fine. I called the function both ways and they returned the expected values. What about the values in the arrays? Are you sure they’re different?

sspbass:
Doesn’t the squiggly line next to pin 6 denote a pwm/analog pin?

It wasn’t in use previously.

In a word … no. PWM is one way to make a fast switching digital output resemble a true analog output. But those PWM pins are really digital inputs and outputs (I/O). The A0-A5 pins are ones that can go to the A/D converter. I think you want A0 to be the input pin for the circuit above.

Digital pin 6 was going to be used by one of the input buttons but it became unused when pin 7 became the new start pin because pin 4 was used by the LCD. We could use pin 6 as a digital input but then you’d have only 1 input, a scroll up but no down. Not too big a deal now with only 20 A targets but later …

So now I understand why the buttons did nothing.

sspbass:
Although I do have some comments regarding the debugging code in the video once I get it uploaded.

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

OK, that was interesting. I see the FormatData() and TimeConvert() routines now seem to work, at least in part. I’ll fix the declaration of the LCD as 16x2. I’m less sure why the start button is wierd.

EDIT : OK I can see that the timer is in the “RUN” mode even before you’ve pushed the start button. It initializes for it’s 5 secs and then goes to ready to running immediately (no button push). My guess is the start button wierdness is due to that. I note the stop button seems to work OK. So fixing the immediate transistion to RUN might fix the start button as well.

ps - the debug loop() won’t transition to the review screen because that part was commented out. So that’s expected. Seems like there’s 2 separate oddities : the RUN state immediately and the same times in review mode, That the hit times now show up as a result of the LCD fix is great ! Good find there. That’s one reason I hate software, one little oversight and strange things happen that don’t seemingly have any relationship to the root cause.

I fixed the 16x2 to be 20x4.

I also did domething with the timer state to perhaps fix that, but the more I look into it the less sure I am that the fix will “take”.

Since you have a button wired to digital pin6, I changed the code to use that as a single scroll button input. So wire that button the same way you’ve wired the start/stop button … except of course it’ll go to pin6.

I’ve done something to perhaps fix the review display(s). I’m not sure why there were stuck displaying the 1’st and 5’th hit times but I changed something I thought was questionalble. I guess we’ll see. There’s still some odd behavior that I wouldn’t have predicted to happen even given the bad coding I’ve seen. There’s probably still some bugs to squash.

#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


// 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 DisplayState = 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
long A_splits[MaxHits];
long B_splits[MaxHits];
long AB_splits[MaxHits];
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

// setup and intialize strings for the display each 20 chars long
String Line1 = "    Shot Timer v1   ";
String Line2 = "      is alive      ";
String Line3 = "                    ";
String Line4 = "                    ";

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
    // check to see if this is first pass since start push
    // this is goofy and will change when modes are coded
    if (DisplayState == 0)
    {
      DisplayState = 1; // display will show count and time w 1st hit
    }
    
    // send data to display if there have been any hits
    if (AupDtFlag == 1 || BupDtFlag == 1)
    {
      CalcTimes();
      FormatData();
      LCDdisplay();
    }
    
    // timer is running so now check for any stop conditions
    if ((digitalRead(StartPin) == LOW) || ((millis() - StartTime) > MaxTime) || A_count >= MaxHits || B_count >= MaxHits)
    {
      StopTimer();
      tone(BuzzerPin, FreqLo, BuzzTime10);
      // update display just in case last msec hit came in
      if (AupDtFlag == 1 || BupDtFlag == 1)
      {
        CalcTimes();
        FormatData();
        LCDdisplay();
      }
      
      // just for the moment send times to PC for debug
      SendTimes();
      
      // delay enough to debounce stop button
      delay(DB_delay);
      
      // Change to review display, later this will be mode depandant
      DisplayState = 2;
      FormatData();
      LCDdisplay();
    }
  }
  else
  {
  // timer is stopped look for start button or review button pushes
    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();
      DisplayState = 0;
      FormatData();
      LCDdisplay();
      
      // 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);
    }
    // check for scroll button pushes, change display state as reqd
    if (digitalRead(ScrollPin) == LOW)
    {
      int ds = int(A_count/8) + 1; // find the displaystate max
      if (A_count % 8)
      {
        ds = int(A_count/8) + 2;   // find the displaystate max 
      }
      DisplayState++;             // increment displaystate upon push
      if (DisplayState > ds)
      {
        DisplayState = 2;        // wrap around of scrolling action
      }
      FormatData();
      LCDdisplay();
      delay(DB_delay);
    }
  }
}


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 < A_count ; 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 (DisplayState)
  {
    case 1:{
      //this is for single A shooter mode
      // display number of hits so far
      String tmp = "A: # hits = ";
      Line1 = tmp + A_count;
      // now display the time of last hit in secs out to hundreths of secs
      String tmp2 = TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      Line2 = "Hit time= " + tmp2 + " secs";
      Line3 = "                    ";
      Line4 = "                    ";
      break;}
    case 2:{
      // this is A review mode hits 1-8
      tmp = TimeConvert(A_Times[0]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[4]); // convert hit time to XX.xx format
      Line1 = "A 1:" + tmp + "s  5:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[1]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[5]); // convert hit time to XX.xx format
      Line2 = "A 2:" + tmp + "s  6:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[2]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[6]); // convert hit time to XX.xx format
      Line3 = "A 3:" + tmp + "s  7:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[3]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[7]); // convert hit time to XX.xx format
      Line4 = "A 4:" + tmp + "s  8:" + tmp2 + "s";
      break;}
    case 3:{
      // this is A review mode hits 9-16
      tmp = TimeConvert(A_Times[8]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[12]); // convert hit time to XX.xx format
      Line1 = "A 9:" + tmp + "s 13:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[9]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[13]); // convert hit time to XX.xx format
      Line2 = "A 10:" + tmp + "s14:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[10]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[14]); // convert hit time to XX.xx format
      Line3 = "A 11:" + tmp + "s15:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[11]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[15]); // convert hit time to XX.xx format
      Line4 = "A 12:" + tmp + "s16:" + tmp2 + "s";
      break;}
    case 4:{
      // this is A review mode hits 17-20
      tmp = TimeConvert(A_Times[16]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[18]); // convert hit time to XX.xx format
      Line1 = "A 17:" + tmp + "s19:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[17]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[19]); // convert hit time to XX.xx format
      Line2 = "A 18:" + tmp + "s20:" + tmp2 + "s";
      Line3 = "                    ";
      Line4 = "                    ";
      break;}
    default: 
      // do the default = 0
      // 4 string objects
      Line1 = "    Shot Timer v1   ";
      Line2 = "     is running     ";
      Line3 = " Countdown to start ";
      Line4 = "    in progress     ";
  }
}

String TimeConvert(long time)
{
// takes the time as argument and returns the time for that hit as a XX.xx string
  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, '.');
  // tmp3 now holds rounded time in secs XX.xx format
  return tmp3;
}


void LCDdisplay()
{
  // reset display update flags
  AupDtFlag = 0;
  BupDtFlag = 0;
  // sends 4 lines to LCD using library functions
  lcd.clear();
  lcd.print(Line1);
  lcd.setCursor(0, 1);
  lcd.print(Line2);
  lcd.setCursor(0, 2);
  lcd.print(Line3);
  lcd.setCursor(0, 3);
  lcd.print(Line4);
}

So see if the start/stop work now and the scroll button too. See if the display transistions from “init” to “ready” and then, when started, to “countdown” as it should. See that the LED and buzzer and messages to the PC all work as they used to. When the buzzer buzzes try to put 1 whack/sec into the system so we can roughtly see the times as they are displayed and that they are about right. If you can get more than 10 so hits the display scrollling can be wrung out. Perhaps what’s needed are 2 or more test runs: one with 10 hits and a stop button … and one with 20+ hits → and autostop … and one with a 30sec timeout and any number of hits less than 20. Vid of the 1’st run including time from reset to start and then stop and then scroll button action. You can just note the proper, or improper, action(s) of the other 2 test runs.

HA ! I watched the vid and listened one more time and I think I’ve got it. What was happening is the timer was stopped (not running as I thought above) but continually looking for the start button push and the (analog) scroll button push. Since there was no analog input, the Arduino might have sensed anything in that regard. So say it sensed a down scroll (low) voltage. The display was already at it’s bottom display so nothing apparently happens with the sensed repeated down button push. In fact the (only) way it got to that (review) display was via sensing a down scroll button, there was (now there is) no code to go to that review display short of a scroll button push after the timer is stopped. The looong push to get things started is a consequence of the debounce delay put into the scoll button reads. Every time after any button gets pressed there’s presently a 1 sec delay built in before any other action can happen. That means with a false analog read of a non-existent low voltage/down scroll button push, the Arduino was in a loop waiting for 1 sec and then doing a quick scan of the buttons and then waiting again. Thus the start button wouldn’t get recognized unless held for a second+ and that’s why it worked normally when you ran the debug loop() code. That analog read stuff was commented out in the debug code.

So the timer state “fixes” I put in aren’t needed but they don’t hurt so leave 'em in I say. The fix for the start oddness is the new digi read of pin6 for the scroll button. It may or may not scroll but the start should now work ! FWIW I thought the auto transistion to a review display was a good thing, so I explicitly put code in to do that … 1 sec after the stop occurs. So the last shot display should persist for that 1 sec after the stop buzz … and then transistion.

As for the repeated times in the review display … I dunno. Either tmp and tmp2 aren’t getting changed each time the TimeConvert() function is called or those new values aren’t being stuffed into the LineX Strings. If the above code rev doesn’t fix it then perhaps they need to be declared as “volatile String” vs just “String”. Or it’s something else all-together …

One more note: I had toe change the tmp1 tmp2 that were re-used so each one in a “loop” had a unique number.

It wouldn’t compile when they were “re-declared”

sspbass:
One more note: I had toe change the tmp1 tmp2 that were re-used so each one in a “loop” had a unique number.

It wouldn’t compile when they were “re-declared”

So something like this for each of the display case conditions in FormatData() ?

    case 2:{
      // this is A review mode hits 1-8
      String tmp = TimeConvert(A_Times[0]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[4]); // convert hit time to XX.xx format
      Line1 = "A 1:" + tmp + "s  5:" + tmp2 + "s";
      String tmp3 = TimeConvert(A_Times[1]); // convert hit time to XX.xx format
      String tmp4 = TimeConvert(A_Times[5]); // convert hit time to XX.xx format
      Line2 = "A 2:" + tmp3 + "s  6:" + tmp4 + "s";
      String tmp5 = TimeConvert(A_Times[2]); // convert hit time to XX.xx format
      String tmp6 = TimeConvert(A_Times[6]); // convert hit time to XX.xx format
      Line3 = "A 3:" + tmp5 + "s  7:" + tmp6 + "s";
      String tmp7 = TimeConvert(A_Times[3]); // convert hit time to XX.xx format
      String tmp8 = TimeConvert(A_Times[7]); // convert hit time to XX.xx format
      Line4 = "A 4:" + tmp7 + "s  8:" + tmp8 + "s";
      break;}

I’m still not sure why the original code using just tmp and tmp2 doesn’t work. I’m not surprised that the last bit that excluded the String on each line didn’t work. I was counting on the declaration made in the Case = 1 to “hold” but I guess the different case statements are treated like different functions, variables declared in each are local to each. If the above is what you did to get it to work … that’s great and not to bad a RAM hog. Better than my very first way of doing it.

This ?

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 (DisplayState)
  {
    case 1:{
      //this is for single A shooter mode
      // display number of hits so far
      String tmp = "A: # hits = ";
      Line1 = tmp + A_count;
      // now display the time of last hit in secs out to hundreths of secs
      String tmp2 = TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      Line2 = "Hit time= " + tmp2 + " secs";
      Line3 = "                    ";
      Line4 = "                    ";
      break;}
    case 2:{
      // this is A review mode hits 1-8
      String tmp = TimeConvert(A_Times[0]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[4]); // convert hit time to XX.xx format
      Line1 = "A 1:" + tmp + "s  5:" + tmp2 + "s";
      String tmp3 = TimeConvert(A_Times[1]); // convert hit time to XX.xx format
      String tmp4 = TimeConvert(A_Times[5]); // convert hit time to XX.xx format
      Line2 = "A 2:" + tmp3 + "s  6:" + tmp4 + "s";
      String tmp5 = TimeConvert(A_Times[2]); // convert hit time to XX.xx format
      String tmp6 = TimeConvert(A_Times[6]); // convert hit time to XX.xx format
      Line3 = "A 3:" + tmp5 + "s  7:" + tmp6 + "s";
      String tmp7 = TimeConvert(A_Times[3]); // convert hit time to XX.xx format
      String tmp8 = TimeConvert(A_Times[7]); // convert hit time to XX.xx format
      Line4 = "A 4:" + tmp7 + "s  8:" + tmp8 + "s";
      break;}
    case 3:{
      // this is A review mode hits 9-16
      String tmp = TimeConvert(A_Times[8]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[12]); // convert hit time to XX.xx format
      Line1 = "A 9:" + tmp + "s 13:" + tmp2 + "s";
      String tmp3 = TimeConvert(A_Times[9]); // convert hit time to XX.xx format
      String tmp4 = TimeConvert(A_Times[13]); // convert hit time to XX.xx format
      Line2 = "A 10:" + tmp3 + "s14:" + tmp4 + "s";
      String tmp5 = TimeConvert(A_Times[10]); // convert hit time to XX.xx format
      String tmp6 = TimeConvert(A_Times[14]); // convert hit time to XX.xx format
      Line3 = "A 11:" + tmp5 + "s15:" + tmp6 + "s";
      String tmp7 = TimeConvert(A_Times[11]); // convert hit time to XX.xx format
      String tmp8 = TimeConvert(A_Times[15]); // convert hit time to XX.xx format
      Line4 = "A 12:" + tmp7 + "s16:" + tmp8 + "s";
      break;}
    case 4:{
      // this is A review mode hits 17-20
      String tmp = TimeConvert(A_Times[16]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[18]); // convert hit time to XX.xx format
      Line1 = "A 17:" + tmp + "s19:" + tmp2 + "s";
      String tmp3 = TimeConvert(A_Times[17]); // convert hit time to XX.xx format
      String tmp4 = TimeConvert(A_Times[19]); // convert hit time to XX.xx format
      Line2 = "A 18:" + tmp3 + "s20:" + tmp4 + "s";
      Line3 = "                    ";
      Line4 = "                    ";
      break;}
    default: 
      // do the default = 0
      // 4 string objects
      Line1 = "    Shot Timer v1   ";
      Line2 = "     is running     ";
      Line3 = " Countdown to start ";
      Line4 = "    in progress     ";
  }
}

DirtyD:
Function looks and works fine. I called the function both ways and they returned the expected values. What about the values in the arrays? Are you sure they’re different?

Thanks for the look ! Sorry I just noticed your post now. Yup, once the display difficulties were worked out I could see the function was doing what it’s supposed to. The problem is that the temporary variables, tmp and tmp2, aren’t carrying that output to the Strings that inturn get output to the LCD. I still don’t know the reason for this. I’ll post a snippet of that old code below if you care to unveil the mystery. I do think the new code, above this post, will work, at the expense of more RAM used. Probably not an concern but you never know.

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 (DisplayState)
  {
    case 1:{
      //this is for single A shooter mode
      // display number of hits so far
      String tmp = "A: # hits = ";
      Line1 = tmp + A_count;
      // now display the time of last hit in secs out to hundreths of secs
      String tmp2 = TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      Line2 = "Hit time = " + tmp2 + "secs";
      Line3 = "                    ";
      Line4 = "                    ";
      break;}
    case 2:{
      // this is A review mode hits 1-8
      String tmp = TimeConvert(A_Times[0]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[4]); // convert hit time to XX.xx format
      Line1 = "A 1:" + tmp + "s  5:" + tmp2 + "s";
      String tmp = TimeConvert(A_Times[1]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[5]); // convert hit time to XX.xx format
      Line2 = "A 2:" + tmp + "s  6:" + tmp2 + "s";
      String tmp = TimeConvert(A_Times[2]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[6]); // convert hit time to XX.xx format
      Line3 = "A 3:" + tmp + "s  7:" + tmp2 + "s";
      String tmp = TimeConvert(A_Times[3]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[7]); // convert hit time to XX.xx format
      Line4 = "A 4:" + tmp + "s  8:" + tmp2 + "s";
      break;}
    case 3:{
      // this is A review mode hits 9-16
      String tmp = TimeConvert(A_Times[8]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[12]); // convert hit time to XX.xx format
      Line1 = "A 9:" + tmp + "s 13:" + tmp2 + "s";
      String tmp = TimeConvert(A_Times[9]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[13]); // convert hit time to XX.xx format
      Line2 = "A 10:" + tmp + "s14:" + tmp2 + "s";
      String tmp = TimeConvert(A_Times[10]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[14]); // convert hit time to XX.xx format
      Line3 = "A 11:" + tmp + "s15:" + tmp2 + "s";
      String tmp = TimeConvert(A_Times[11]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[15]); // convert hit time to XX.xx format
      Line4 = "A 12:" + tmp + "s16:" + tmp2 + "s";
      break;}
    case 4:{
      // this is A review mode hits 17-20
      String tmp = TimeConvert(A_Times[16]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[18]); // convert hit time to XX.xx format
      Line1 = "A 17:" + tmp + "s19:" + tmp2 + "s";
      String tmp = TimeConvert(A_Times[17]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[19]); // convert hit time to XX.xx format
      Line2 = "A 18:" + tmp + "s20:" + tmp2 + "s";
      Line3 = "                    ";
      Line4 = "                    ";
      break;}
    default: 
      // do the default = 0
      // 4 string objects
      Line1 = "    Shot Timer v1   ";
      Line2 = "     is running     ";
      Line3 = " Countdown to start ";
      Line4 = "    in progress     ";
  }
}

It was the case = 2 above that was not correct. The “times” stored have been working in the past and if you look at the hit by hit display, they are being stored and displayed properly as they happen. It’s the later recall and reformatting and display that wasn’t working.

Mee_n_Mac:
I fixed the 16x2 to be 20x4.

I also did domething with the timer state to perhaps fix that, but the more I look into it the less sure I am that the fix will “take”.

Since you have a button wired to digital pin6, I changed the code to use that as a single scroll button input. So wire that button the same way you’ve wired the start/stop button … except of course it’ll go to pin6.

I’ve done something to perhaps fix the review display(s). I’m not sure why there were stuck displaying the 1’st and 5’th hit times but I changed something I thought was questionalble. I guess we’ll see. There’s still some odd behavior that I wouldn’t have predicted to happen even given the bad coding I’ve seen. There’s probably still some bugs to squash.

#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

// 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 DisplayState = 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
long A_splits[MaxHits];
long B_splits[MaxHits];
long AB_splits[MaxHits];
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

// setup and intialize strings for the display each 20 chars long
String Line1 = " Shot Timer v1 ";
String Line2 = " is alive ";
String Line3 = " ";
String Line4 = " ";

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
// check to see if this is first pass since start push
// this is goofy and will change when modes are coded
if (DisplayState == 0)
{
DisplayState = 1; // display will show count and time w 1st hit
}

// send data to display if there have been any hits
if (AupDtFlag == 1 || BupDtFlag == 1)
{
  CalcTimes();
  FormatData();
  LCDdisplay();
}

// timer is running so now check for any stop conditions
if ((digitalRead(StartPin) == LOW) || ((millis() - StartTime) > MaxTime) || A_count >= MaxHits || B_count >= MaxHits)
{
  StopTimer();
  tone(BuzzerPin, FreqLo, BuzzTime10);
  // update display just in case last msec hit came in
  if (AupDtFlag == 1 || BupDtFlag == 1)
  {
    CalcTimes();
    FormatData();
    LCDdisplay();
  }
  
  // just for the moment send times to PC for debug
  SendTimes();
  
  // delay enough to debounce stop button
  delay(DB_delay);
  
  // Change to review display, later this will be mode depandant
  DisplayState = 2;
  FormatData();
  LCDdisplay();
}

}
else
{
// timer is stopped look for start button or review button pushes
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();
  DisplayState = 0;
  FormatData();
  LCDdisplay();
  
  // 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);
}
// check for scroll button pushes, change display state as reqd
if (digitalRead(ScrollPin) == LOW)
{
  int ds = int(A_count/8) + 1; // find the displaystate max
  if (A_count % 8)
  {
    ds = int(A_count/8) + 2;   // find the displaystate max 
  }
  DisplayState++;             // increment displaystate upon push
  if (DisplayState > ds)
  {
    DisplayState = 2;        // wrap around of scrolling action
  }
  FormatData();
  LCDdisplay();
  delay(DB_delay);
}

}
}

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 < A_count ; 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 (DisplayState)
{
case 1:{
//this is for single A shooter mode
// display number of hits so far
String tmp = "A: # hits = ";
Line1 = tmp + A_count;
// now display the time of last hit in secs out to hundreths of secs
String tmp2 = TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
Line2 = “Hit time= " + tmp2 + " secs”;
Line3 = " ";
Line4 = " ";
break;}
case 2:{
// this is A review mode hits 1-8
tmp = TimeConvert(A_Times[0]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[4]); // convert hit time to XX.xx format
Line1 = “A 1:” + tmp + “s 5:” + tmp2 + “s”;
tmp = TimeConvert(A_Times[1]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[5]); // convert hit time to XX.xx format
Line2 = “A 2:” + tmp + “s 6:” + tmp2 + “s”;
tmp = TimeConvert(A_Times[2]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[6]); // convert hit time to XX.xx format
Line3 = “A 3:” + tmp + “s 7:” + tmp2 + “s”;
tmp = TimeConvert(A_Times[3]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[7]); // convert hit time to XX.xx format
Line4 = “A 4:” + tmp + “s 8:” + tmp2 + “s”;
break;}
case 3:{
// this is A review mode hits 9-16
tmp = TimeConvert(A_Times[8]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[12]); // convert hit time to XX.xx format
Line1 = “A 9:” + tmp + “s 13:” + tmp2 + “s”;
tmp = TimeConvert(A_Times[9]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[13]); // convert hit time to XX.xx format
Line2 = “A 10:” + tmp + “s14:” + tmp2 + “s”;
tmp = TimeConvert(A_Times[10]); // convert hit time to XX.xx format
String tmp2 = TimeConvert(A_Times[14]); // convert hit time to XX.xx format
Line3 = “A 11:” + tmp + “s15:” + tmp2 + “s”;
tmp = TimeConvert(A_Times[11]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[15]); // convert hit time to XX.xx format
Line4 = “A 12:” + tmp + “s16:” + tmp2 + “s”;
break;}
case 4:{
// this is A review mode hits 17-20
tmp = TimeConvert(A_Times[16]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[18]); // convert hit time to XX.xx format
Line1 = “A 17:” + tmp + “s19:” + tmp2 + “s”;
tmp = TimeConvert(A_Times[17]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[19]); // convert hit time to XX.xx format
Line2 = “A 18:” + tmp + “s20:” + tmp2 + “s”;
Line3 = " ";
Line4 = " ";
break;}
default:
// do the default = 0
// 4 string objects
Line1 = " Shot Timer v1 ";
Line2 = " is running ";
Line3 = " Countdown to start ";
Line4 = " in progress ";
}
}

String TimeConvert(long time)
{
// takes the time as argument and returns the time for that hit as a XX.xx string
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, ‘.’);
// tmp3 now holds rounded time in secs XX.xx format
return tmp3;
}

void LCDdisplay()
{
// reset display update flags
AupDtFlag = 0;
BupDtFlag = 0;
// sends 4 lines to LCD using library functions
lcd.clear();
lcd.print(Line1);
lcd.setCursor(0, 1);
lcd.print(Line2);
lcd.setCursor(0, 2);
lcd.print(Line3);
lcd.setCursor(0, 3);
lcd.print(Line4);
}




So see if the start/stop work now and the scroll button too. See if the display transistions from "init" to "ready" and then, *when started*, to "countdown" as it should. See that the LED and buzzer and messages to the PC all work as they used to. When the buzzer buzzes try to put 1 whack/sec into the system so we can roughtly see the times as they are displayed and that they are about right. If you can get more than 10 so hits the display scrollling can be wrung out. Perhaps what's needed are 2 or more test runs: one with 10 hits and a stop button ... and one with 20+ hits -> and autostop ... and one with a 30sec timeout and any number of hits less than 20. Vid of the 1'st run including time from reset to start and then stop and then scroll button action. You can just note the proper, or improper, action(s) of the other 2 test runs.

Just ran this code. The Display works correctly (messages at the beginning and the times for each hit are correct now) and so does the start/stop button. I wired a push button to pin 6 and that doesn’t do anything during review.

This is the corrected code that compiled and ran. I’m still going through the posts you made yesterday and today trying to catch up so this may not be as up to date as you are.

#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


// 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 DisplayState = 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
long A_splits[MaxHits];
long B_splits[MaxHits];
long AB_splits[MaxHits];
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

// setup and intialize strings for the display each 20 chars long
String Line1 = "    Shot Timer v1   ";
String Line2 = "      is alive      ";
String Line3 = "                    ";
String Line4 = "                    ";

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
    // check to see if this is first pass since start push
    // this is goofy and will change when modes are coded
    if (DisplayState == 0)
    {
      DisplayState = 1; // display will show count and time w 1st hit
    }
    
    // send data to display if there have been any hits
    if (AupDtFlag == 1 || BupDtFlag == 1)
    {
      CalcTimes();
      FormatData();
      LCDdisplay();
    }
    
    // timer is running so now check for any stop conditions
    if ((digitalRead(StartPin) == LOW) || ((millis() - StartTime) > MaxTime) || A_count >= MaxHits || B_count >= MaxHits)
    {
      StopTimer();
      tone(BuzzerPin, FreqLo, BuzzTime10);
      // update display just in case last msec hit came in
      if (AupDtFlag == 1 || BupDtFlag == 1)
      {
        CalcTimes();
        FormatData();
        LCDdisplay();
      }
      
      // just for the moment send times to PC for debug
      SendTimes();
      
      // delay enough to debounce stop button
      delay(DB_delay);
      
      // Change to review display, later this will be mode depandant
      DisplayState = 2;
      FormatData();
      LCDdisplay();
    }
  }
  else
  {
  // timer is stopped look for start button or review button pushes
    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();
      DisplayState = 0;
      FormatData();
      LCDdisplay();
      
      // 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);
    }
    // check for scroll button pushes, change display state as reqd
    if (digitalRead(ScrollPin) == LOW)
    {
      int ds = int(A_count/8) + 1; // find the displaystate max
      if (A_count % 8)
      {
        ds = int(A_count/8) + 2;   // find the displaystate max 
      }
      DisplayState++;             // increment displaystate upon push
      if (DisplayState > ds)
      {
        DisplayState = 2;        // wrap around of scrolling action
      }
      FormatData();
      LCDdisplay();
      delay(DB_delay);
    }
  }
}


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 < A_count ; 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 (DisplayState)
  {
    case 1:{
      //this is for single A shooter mode
      // display number of hits so far
      String tmp = "A: # hits = ";
      Line1 = tmp + A_count;
      // now display the time of last hit in secs out to hundreths of secs
      String tmp2 = TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      Line2 = "Hit time= " + tmp2 + " secs";
      Line3 = "                    ";
      Line4 = "                    ";
      break;}
    case 2:{
      // this is A review mode hits 1-8
      String tmp = TimeConvert(A_Times[0]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[4]); // convert hit time to XX.xx format
      Line1 = "A 1:" + tmp + "s  5:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[1]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[5]); // convert hit time to XX.xx format
      Line2 = "A 2:" + tmp + "s  6:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[2]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[6]); // convert hit time to XX.xx format
      Line3 = "A 3:" + tmp + "s  7:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[3]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[7]); // convert hit time to XX.xx format
      Line4 = "A 4:" + tmp + "s  8:" + tmp2 + "s";
      break;}
    case 3:{
      // this is A review mode hits 9-16
      String tmp = TimeConvert(A_Times[8]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[12]); // convert hit time to XX.xx format
      Line1 = "A 9:" + tmp + "s 13:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[9]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[13]); // convert hit time to XX.xx format
      Line2 = "A 10:" + tmp + "s14:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[10]); // convert hit time to XX.xx format
      String tmp3 = TimeConvert(A_Times[14]); // convert hit time to XX.xx format
      Line3 = "A 11:" + tmp + "s15:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[11]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[15]); // convert hit time to XX.xx format
      Line4 = "A 12:" + tmp + "s16:" + tmp2 + "s";
      break;}
    case 4:{
      // this is A review mode hits 17-20
      String tmp = TimeConvert(A_Times[16]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[18]); // convert hit time to XX.xx format
      Line1 = "A 17:" + tmp + "s19:" + tmp2 + "s";
      String tmp3 = TimeConvert(A_Times[17]); // convert hit time to XX.xx format
      String tmp4 = TimeConvert(A_Times[19]); // convert hit time to XX.xx format
      Line2 = "A 18:" + tmp + "s20:" + tmp2 + "s";
      Line3 = "                    ";
      Line4 = "                    ";
      break;}
    default: 
      // do the default = 0
      // 4 string objects
      Line1 = "    Shot Timer v1   ";
      Line2 = "     is running     ";
      Line3 = " Countdown to start ";
      Line4 = "    in progress     ";
  }
}

String TimeConvert(long time)
{
// takes the time as argument and returns the time for that hit as a XX.xx string
  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, '.');
  // tmp3 now holds rounded time in secs XX.xx format
  return tmp3;
}


void LCDdisplay()
{
  // reset display update flags
  AupDtFlag = 0;
  BupDtFlag = 0;
  // sends 4 lines to LCD using library functions
  lcd.clear();
  lcd.print(Line1);
  lcd.setCursor(0, 1);
  lcd.print(Line2);
  lcd.setCursor(0, 2);
  lcd.print(Line3);
  lcd.setCursor(0, 3);
  lcd.print(Line4);
}

Mee_n_Mac:

DirtyD:
Function looks and works fine. I called the function both ways and they returned the expected values. What about the values in the arrays? Are you sure they’re different?

Thanks for the look ! Sorry I just noticed your post now. Yup, once the display difficulties were worked out I could see the function was doing what it’s supposed to. The problem is that the temporary variables, tmp and tmp2, aren’t carrying that output to the Strings that inturn get output to the LCD. I still don’t know the reason for this. I’ll post a snippet of that old code below if you care to unveil the mystery. I do think the new code, above this post, will work, at the expense of more RAM used. Probably not an concern but you never know.

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 (DisplayState)
{
case 1:{
//this is for single A shooter mode
// display number of hits so far
String tmp = "A: # hits = ";
Line1 = tmp + A_count;
// now display the time of last hit in secs out to hundreths of secs
String tmp2 = TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
Line2 = "Hit time = " + tmp2 + “secs”;
Line3 = " ";
Line4 = " ";
break;}
case 2:{
// this is A review mode hits 1-8
String tmp = TimeConvert(A_Times[0]); // convert hit time to XX.xx format
String tmp2 = TimeConvert(A_Times[4]); // convert hit time to XX.xx format
Line1 = “A 1:” + tmp + “s 5:” + tmp2 + “s”;
String tmp = TimeConvert(A_Times[1]); // convert hit time to XX.xx format
String tmp2 = TimeConvert(A_Times[5]); // convert hit time to XX.xx format
Line2 = “A 2:” + tmp + “s 6:” + tmp2 + “s”;
String tmp = TimeConvert(A_Times[2]); // convert hit time to XX.xx format
String tmp2 = TimeConvert(A_Times[6]); // convert hit time to XX.xx format
Line3 = “A 3:” + tmp + “s 7:” + tmp2 + “s”;
String tmp = TimeConvert(A_Times[3]); // convert hit time to XX.xx format
String tmp2 = TimeConvert(A_Times[7]); // convert hit time to XX.xx format
Line4 = “A 4:” + tmp + “s 8:” + tmp2 + “s”;
break;}
case 3:{
// this is A review mode hits 9-16
String tmp = TimeConvert(A_Times[8]); // convert hit time to XX.xx format
String tmp2 = TimeConvert(A_Times[12]); // convert hit time to XX.xx format
Line1 = “A 9:” + tmp + “s 13:” + tmp2 + “s”;
String tmp = TimeConvert(A_Times[9]); // convert hit time to XX.xx format
String tmp2 = TimeConvert(A_Times[13]); // convert hit time to XX.xx format
Line2 = “A 10:” + tmp + “s14:” + tmp2 + “s”;
String tmp = TimeConvert(A_Times[10]); // convert hit time to XX.xx format
String tmp2 = TimeConvert(A_Times[14]); // convert hit time to XX.xx format
Line3 = “A 11:” + tmp + “s15:” + tmp2 + “s”;
String tmp = TimeConvert(A_Times[11]); // convert hit time to XX.xx format
String tmp2 = TimeConvert(A_Times[15]); // convert hit time to XX.xx format
Line4 = “A 12:” + tmp + “s16:” + tmp2 + “s”;
break;}
case 4:{
// this is A review mode hits 17-20
String tmp = TimeConvert(A_Times[16]); // convert hit time to XX.xx format
String tmp2 = TimeConvert(A_Times[18]); // convert hit time to XX.xx format
Line1 = “A 17:” + tmp + “s19:” + tmp2 + “s”;
String tmp = TimeConvert(A_Times[17]); // convert hit time to XX.xx format
String tmp2 = TimeConvert(A_Times[19]); // convert hit time to XX.xx format
Line2 = “A 18:” + tmp + “s20:” + tmp2 + “s”;
Line3 = " ";
Line4 = " ";
break;}
default:
// do the default = 0
// 4 string objects
Line1 = " Shot Timer v1 ";
Line2 = " is running ";
Line3 = " Countdown to start ";
Line4 = " in progress ";
}
}




It was the case = 2 above that was not correct. The "times" stored have been working in the past and if you look at the hit by hit display, they are being stored and displayed properly as they happen. It's the later recall and reformatting and display that wasn't working.

This did not work. The LCD only display 6 hit/times the first attempt and the second attempt filled the screen but the times were all the same like it was before. I will revert it to the post above this. Keep in mind I still had to renumber all of the String tmp’s to a unique number within each case to get it to compile.

sspbass:
This did not work. The LCD only display 6 hit/times the first attempt and the second attempt filled the screen but the times were all the same like it was before. I will revert it to the post above this. Keep in mind I still had to renumber all of the String tmp’s to a unique number within each case to get it to compile.

Yup, that was the old, known non-working code. I posted it just in case DirtyD felt some desire to investigate why it didn’t work. :mrgreen:

I’ll have a look at your code that did work and see how it compares to what I thought you had done to rename the tmp’s and what I had posted as a possible fix.

Mee_n_Mac:

sspbass:
This did not work. The LCD only display 6 hit/times the first attempt and the second attempt filled the screen but the times were all the same like it was before. I will revert it to the post above this. Keep in mind I still had to renumber all of the String tmp’s to a unique number within each case to get it to compile.

Yup, that was the old, known non-working code. I posted it just in case DirtyD felt some desire to investigate why it didn’t work. :mrgreen:

I’ll have a look at your code that did work and see how it compares to what I thought you had done to rename the tmp’s and what I had posted as a possible fix.

haha, well thats good news then.

sspbass:
Just ran this code. The Display works correctly (messages at the beginning and the times for each hit are correct now) and so does the start/stop button. I wired a push button to pin 6 and that doesn’t do anything during review.

This is the corrected code that compiled and ran. I’m still going through the posts you made yesterday and today trying to catch up so this may not be as up to date as you are.

#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

// 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 DisplayState = 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
long A_splits[MaxHits];
long B_splits[MaxHits];
long AB_splits[MaxHits];
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

// setup and intialize strings for the display each 20 chars long
String Line1 = " Shot Timer v1 ";
String Line2 = " is alive ";
String Line3 = " ";
String Line4 = " ";

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
// check to see if this is first pass since start push
// this is goofy and will change when modes are coded
if (DisplayState == 0)
{
DisplayState = 1; // display will show count and time w 1st hit
}

// send data to display if there have been any hits
if (AupDtFlag == 1 || BupDtFlag == 1)
{
  CalcTimes();
  FormatData();
  LCDdisplay();
}

// timer is running so now check for any stop conditions
if ((digitalRead(StartPin) == LOW) || ((millis() - StartTime) > MaxTime) || A_count >= MaxHits || B_count >= MaxHits)
{
  StopTimer();
  tone(BuzzerPin, FreqLo, BuzzTime10);
  // update display just in case last msec hit came in
  if (AupDtFlag == 1 || BupDtFlag == 1)
  {
    CalcTimes();
    FormatData();
    LCDdisplay();
  }
  
  // just for the moment send times to PC for debug
  SendTimes();
  
  // delay enough to debounce stop button
  delay(DB_delay);
  
  // Change to review display, later this will be mode depandant
  DisplayState = 2;
  FormatData();
  LCDdisplay();
}

}
else
{
// timer is stopped look for start button or review button pushes
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();
  DisplayState = 0;
  FormatData();
  LCDdisplay();
  
  // 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);
}
// check for scroll button pushes, change display state as reqd
if (digitalRead(ScrollPin) == LOW)
{
  int ds = int(A_count/8) + 1; // find the displaystate max
  if (A_count % 8)
  {
    ds = int(A_count/8) + 2;   // find the displaystate max 
  }
  DisplayState++;             // increment displaystate upon push
  if (DisplayState > ds)
  {
    DisplayState = 2;        // wrap around of scrolling action
  }
  FormatData();
  LCDdisplay();
  delay(DB_delay);
}

}
}

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 < A_count ; 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 (DisplayState)
{
case 1:{
//this is for single A shooter mode
// display number of hits so far
String tmp = "A: # hits = ";
Line1 = tmp + A_count;
// now display the time of last hit in secs out to hundreths of secs
String tmp2 = TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
Line2 = “Hit time= " + tmp2 + " secs”;
Line3 = " ";
Line4 = " ";
break;}
case 2:{
// this is A review mode hits 1-8
String tmp = TimeConvert(A_Times[0]); // convert hit time to XX.xx format
String tmp2 = TimeConvert(A_Times[4]); // convert hit time to XX.xx format
Line1 = “A 1:” + tmp + “s 5:” + tmp2 + “s”;
tmp = TimeConvert(A_Times[1]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[5]); // convert hit time to XX.xx format
Line2 = “A 2:” + tmp + “s 6:” + tmp2 + “s”;
tmp = TimeConvert(A_Times[2]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[6]); // convert hit time to XX.xx format
Line3 = “A 3:” + tmp + “s 7:” + tmp2 + “s”;
tmp = TimeConvert(A_Times[3]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[7]); // convert hit time to XX.xx format
Line4 = “A 4:” + tmp + “s 8:” + tmp2 + “s”;
break;}
case 3:{
// this is A review mode hits 9-16
String tmp = TimeConvert(A_Times[8]); // convert hit time to XX.xx format
String tmp2 = TimeConvert(A_Times[12]); // convert hit time to XX.xx format
Line1 = “A 9:” + tmp + “s 13:” + tmp2 + “s”;
tmp = TimeConvert(A_Times[9]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[13]); // convert hit time to XX.xx format
Line2 = “A 10:” + tmp + “s14:” + tmp2 + “s”;
tmp = TimeConvert(A_Times[10]); // convert hit time to XX.xx format
String tmp3 = TimeConvert(A_Times[14]); // convert hit time to XX.xx format
Line3 = “A 11:” + tmp + “s15:” + tmp2 + “s”;
tmp = TimeConvert(A_Times[11]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[15]); // convert hit time to XX.xx format
Line4 = “A 12:” + tmp + “s16:” + tmp2 + “s”;
break;}
case 4:{
// this is A review mode hits 17-20
String tmp = TimeConvert(A_Times[16]); // convert hit time to XX.xx format
String tmp2 = TimeConvert(A_Times[18]); // convert hit time to XX.xx format
Line1 = “A 17:” + tmp + “s19:” + tmp2 + “s”;
String tmp3 = TimeConvert(A_Times[17]); // convert hit time to XX.xx format
String tmp4 = TimeConvert(A_Times[19]); // convert hit time to XX.xx format
Line2 = “A 18:” + tmp + “s20:” + tmp2 + “s”;
Line3 = " ";
Line4 = " ";
break;}
default:
// do the default = 0
// 4 string objects
Line1 = " Shot Timer v1 ";
Line2 = " is running ";
Line3 = " Countdown to start ";
Line4 = " in progress ";
}
}

String TimeConvert(long time)
{
// takes the time as argument and returns the time for that hit as a XX.xx string
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, ‘.’);
// tmp3 now holds rounded time in secs XX.xx format
return tmp3;
}

void LCDdisplay()
{
// reset display update flags
AupDtFlag = 0;
BupDtFlag = 0;
// sends 4 lines to LCD using library functions
lcd.clear();
lcd.print(Line1);
lcd.setCursor(0, 1);
lcd.print(Line2);
lcd.setCursor(0, 2);
lcd.print(Line3);
lcd.setCursor(0, 3);
lcd.print(Line4);
}

OK, great !! I like your version better than the other 2 I posted previously ! You can ignore those versions. It makes sense to me that the tmp and tmp2 variables can be re-used and that they only need to be declared once in each “case” statements. This way you only have 2 Strings that get re-used vs 8 Strings. It’s the way I thought it should work. I guess declaring them as Strings every use is a no-no, once at the beginning is enough. That’s, I guess, why the 1’st version didn’t work. :?:

So the version of FormatData() that works is included in the above, and explicitly and alone, below. Let’s use this as the working baseline from here on out.

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 (DisplayState)
  {
    case 1:{
      //this is for single A shooter mode
      // display number of hits so far
      String tmp = "A: # hits = ";
      Line1 = tmp + A_count;
      // now display the time of last hit in secs out to hundreths of secs
      String tmp2 = TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      Line2 = "Hit time= " + tmp2 + " secs";
      Line3 = "                    ";
      Line4 = "                    ";
      break;}
    case 2:{
      // this is A review mode hits 1-8
      String tmp = TimeConvert(A_Times[0]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[4]); // convert hit time to XX.xx format
      Line1 = "A 1:" + tmp + "s  5:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[1]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[5]); // convert hit time to XX.xx format
      Line2 = "A 2:" + tmp + "s  6:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[2]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[6]); // convert hit time to XX.xx format
      Line3 = "A 3:" + tmp + "s  7:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[3]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[7]); // convert hit time to XX.xx format
      Line4 = "A 4:" + tmp + "s  8:" + tmp2 + "s";
      break;}
    case 3:{
      // this is A review mode hits 9-16
      String tmp = TimeConvert(A_Times[8]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[12]); // convert hit time to XX.xx format
      Line1 = "A 9:" + tmp + "s 13:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[9]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[13]); // convert hit time to XX.xx format
      Line2 = "A 10:" + tmp + "s14:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[10]); // convert hit time to XX.xx format
      String tmp3 = TimeConvert(A_Times[14]); // convert hit time to XX.xx format
      Line3 = "A 11:" + tmp + "s15:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[11]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[15]); // convert hit time to XX.xx format
      Line4 = "A 12:" + tmp + "s16:" + tmp2 + "s";
      break;}
    case 4:{
      // this is A review mode hits 17-20
      String tmp = TimeConvert(A_Times[16]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[18]); // convert hit time to XX.xx format
      Line1 = "A 17:" + tmp + "s19:" + tmp2 + "s";
      String tmp3 = TimeConvert(A_Times[17]); // convert hit time to XX.xx format
      String tmp4 = TimeConvert(A_Times[19]); // convert hit time to XX.xx format
      Line2 = "A 18:" + tmp + "s20:" + tmp2 + "s";
      Line3 = "                    ";
      Line4 = "                    ";
      break;}
    default: 
      // do the default = 0
      // 4 string objects
      Line1 = "    Shot Timer v1   ";
      Line2 = "     is running     ";
      Line3 = " Countdown to start ";
      Line4 = "    in progress     ";
  }
}

One more note: I had toe change the tmp1 tmp2 that were re-used so each one in a “loop” had a unique number.

It wouldn’t compile when they were “re-declared”

So when you said the above I thought what you did was ...
    case 2:{
      // this is A review mode hits 1-8
      String tmp = TimeConvert(A_Times[0]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[4]); // convert hit time to XX.xx format
      Line1 = "A 1:" + tmp + "s  5:" + tmp2 + "s";
      String tmp3 = TimeConvert(A_Times[1]); // convert hit time to XX.xx format
      String tmp4 = TimeConvert(A_Times[5]); // convert hit time to XX.xx format

Note how all the "tmp"s have a different name tmp, tmp2, tmp3, tmp4, etc, etc. This is wasteful of RAM if not needed. Which is appears it’s not ! That’s what I did in the 2 versions I posted previously for you to try. No need to given the above works.

So now we have to figure out why the scroll button on pin6 isn’t doing it’s job. I guess the 1st thing to do is verify the wiring. If you have a voltmeter you should be abke to see a high voltage (5v) at pin 6 with the button not pushed and a low (= ground, 0v) with it pushed. I did a quick look at the code and it looked the same as works for the start/stop button so I’ll look again but it’s probably like proof reading your own writing. If you don’t see the mistake the 1’st time, you’ll never see it.

Here’s debug flavor of your last loop(). It has a println command in it to send a message to the PC when the scroll button is pushed and to print the display state (which should tell us which LCD display is being commanded). If the pin6 voltages measure out OK, then give this a whirl and let’s see what we see.

void loop()
{
  if (TimerState) // is timer running
  {
    // timer is running
    // check to see if this is first pass since start push
    // this is goofy and will change when modes are coded
    if (DisplayState == 0)
    {
      DisplayState = 1; // display will show count and time w 1st hit
    }
    
    // send data to display if there have been any hits
    if (AupDtFlag == 1 || BupDtFlag == 1)
    {
      CalcTimes();
      FormatData();
      LCDdisplay();
    }
    
    // timer is running so now check for any stop conditions
    if ((digitalRead(StartPin) == LOW) || ((millis() - StartTime) > MaxTime) || A_count >= MaxHits || B_count >= MaxHits)
    {
      StopTimer();
      tone(BuzzerPin, FreqLo, BuzzTime10);
      // update display just in case last msec hit came in
      if (AupDtFlag == 1 || BupDtFlag == 1)
      {
        CalcTimes();
        FormatData();
        LCDdisplay();
      }
      
      // just for the moment send times to PC for debug
      SendTimes();
      
      // delay enough to debounce stop button
      delay(DB_delay);
      
      // Change to review display, later this will be mode depandant
      DisplayState = 2;
      FormatData();
      LCDdisplay();
    }
  }
  else
  {
  // timer is stopped look for start button or review button pushes
    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();
      DisplayState = 0;
      FormatData();
      LCDdisplay();
      
      // 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);
    }
    // check for scroll button pushes, change display state as reqd
    if (digitalRead(ScrollPin) == LOW)
    {
      int ds = int(A_count/8) + 1; // find the displaystate max
      if (A_count % 8)
      {
        ds = int(A_count/8) + 2;   // find the displaystate max 
      }
      DisplayState++;             // increment displaystate upon push
      if (DisplayState > ds)
      {
        DisplayState = 2;        // wrap around of scrolling action
      }

      // debug statement to see if pin6 is being read
      Serial.println("Scoll baby scroll");
      Serial.println(DisplayState);

      FormatData();
      LCDdisplay();
      delay(DB_delay);
    }
  }
}

sspbass:
Keep in mind I still had to renumber all of the String tmp’s to a unique number within each case to get it to compile.

Hmmmm, I’m back to being confused. In what I thought was a working FormatData() I did not see unique numbers on all the tmp’s. That’s what I had done in my last guess at a working version.

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

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 (DisplayState)
  {
    case 1:{
      //this is for single A shooter mode
      // display number of hits so far
      String tmp = "A: # hits = ";
      Line1 = tmp + A_count;
      // now display the time of last hit in secs out to hundreths of secs
      String tmp2 = TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      Line2 = "Hit time= " + tmp2 + " secs";
      Line3 = "                    ";
      Line4 = "                    ";
      break;}
    case 2:{
      // this is A review mode hits 1-8
      String tmp = TimeConvert(A_Times[0]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[4]); // convert hit time to XX.xx format
      Line1 = "A 1:" + tmp + "s  5:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[1]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[5]); // convert hit time to XX.xx format
      Line2 = "A 2:" + tmp + "s  6:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[2]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[6]); // convert hit time to XX.xx format
      Line3 = "A 3:" + tmp + "s  7:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[3]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[7]); // convert hit time to XX.xx format
      Line4 = "A 4:" + tmp + "s  8:" + tmp2 + "s";
      break;}
    case 3:{
      // this is A review mode hits 9-16
      String tmp = TimeConvert(A_Times[8]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[12]); // convert hit time to XX.xx format
      Line1 = "A 9:" + tmp + "s 13:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[9]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[13]); // convert hit time to XX.xx format
      Line2 = "A 10:" + tmp + "s14:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[10]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[14]); // convert hit time to XX.xx format
      Line3 = "A 11:" + tmp + "s15:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[11]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[15]); // convert hit time to XX.xx format
      Line4 = "A 12:" + tmp + "s16:" + tmp2 + "s";
      break;}
    case 4:{
      // this is A review mode hits 17-20
      String tmp = TimeConvert(A_Times[16]); // convert hit time to XX.xx format
      String tmp2 = TimeConvert(A_Times[18]); // convert hit time to XX.xx format
      Line1 = "A 17:" + tmp + "s19:" + tmp2 + "s";
      tmp = TimeConvert(A_Times[17]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[19]); // convert hit time to XX.xx format
      Line2 = "A 18:" + tmp + "s20:" + tmp2 + "s";
      Line3 = "                    ";
      Line4 = "                    ";
      break;}
    default:
      // do the default = 0
      // 4 string objects
      Line1 = "    Shot Timer v1   ";
      Line2 = "     is running     ";
      Line3 = " Countdown to start ";
      Line4 = "    in progress     ";
  }
}

This should fix hit 15 from displaying wrong. Looks like there were a few variable declarations that weren’t used (Both in case = 3 and 4). Scratch that they were used to hold the return value from the TimerConvert function but didn’t get used when concatenating the strings for the display. :smiley: