Counting and Timing a shock sensor

Here’s a rev to debug the speed problem. What was different, bad speed vs good, seems to be the addition of the switch case stuff at the top of the timer is running loop. When it was working there was a simple if conditional. So I made something similar (but still allowing AvsB) to the old code and commented out the switch case statements. If the above compiles and at all works in just the old single A shooter mode, give this a try and see if the speed issue goes away. It makes no sense to me that this should be any different but …

void loop()
{
  if (TimerState) // is timer running
  {
    // timer is running
    // set display according to timer mode
    if (TimerMode) 
    {
      DisplayIndex = 11;   // for AvsB mode
    }
    else
    {
      DisplayIndex = 1;    // for single A mode
    }

    //switch (TimerMode)
    //{
      //case 1:{
        //this is for A vs B shooter mode
        //DisplayIndex = 11;
        //break;}
      //case 2:{
        //this is for ??? mode
        // set DisplayIndex = something here
        //break;}
      //case 3:{
        //this is for ???? mode
        // set DisplayIndex = something here
        //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();
      LCDdisplay();
    }
    
    // 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();
        LCDdisplay();
      }
      
      // 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 = 11;
          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;}
        case 2:{
          //this is for ??? mode
          // set DisplayIndex = something here
          break;}
        case 3:{
          //this is for ???? mode
          // set DisplayIndex = something here
          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();
      LCDdisplay();
      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();
      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);
    }
  }
}

If the speed problem still persists with the above, try to note the PC monitor when whacking the sensor like a crack crazed monkey. Does it update with each whack or does it get screwed up too ? Is the print being done immediately following the hit or does it get laggy as the quick hits happen ?

I looked back at the history of what we were doing. There were a lot of cosmetic changes to FormatData() and TimeConvert() to make the display look good. There was, before that even, a restructuring of the code to ask if the timer was running or not first, and then search for switch inputs. The speed issue was not present then as evidenced by vids 6 and 7. Then after I added the switch case to make the display change with mode and some other cosmetic changes that got the display working all puuuurty, the speed issue popped up. There’s no reason why the switch case should have any effect on how the LCD gets updated but who knows, perhaps the Arduino compiler is just plain doing something completely wrong. It seems to work, the review displays change as they should and nothing gets screwy when using the scroll button (which eventually invokes a different set of switch cases) so I don’t get it. What I’m thinking is that there’s some memory usage issue. That the switch case at the top of the loop is somehow overwriting memory used for other stuff, like Line1-4 perhaps. Am I correct in thinking that even with the speed issue happening, the timer can be stopped and the review displays work ? Or does the timer go completely into LaLa Land, never to return to the working world, until reset ?

BTW can you look to see how much RAM and flash the project is using at present ? Maybe the switch case bumped into some limit.

Mee_n_Mac:

sspbass:
Back to this compile error. I thought we (you) took care of this?

Yup. That’s a copy’n’paste error that came in with the new AvsB displays. The following should remedy that. I still don’t know what to think about the speed problem. That came in with what I thought would be a minor change, certainly not something I can see causing the problem we’re seeing. But let’s get the AvsB working whilst that’s stewing. I may offer up a rev that reverts back a step just to pin down the statements that I think are causing that problem.

The fix for the compiler error …

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
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;
tmp2 = TimeConvert(A_splits[A_count-1]); // convert split time
Line3 = “Split time = " + tmp2;
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 + " 5:” + tmp2;
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 + " 6:” + tmp2;
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 + " 7:” + tmp2;
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 + " 8:” + tmp2;
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 + " 13:" + tmp2;
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 = “A10:” + tmp + " 14:" + tmp2;
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 = “A11:” + tmp + " 15:" + tmp2;
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 = “A12:” + tmp + " 16:" + tmp2;
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 = “A17:” + tmp + " 19:" + tmp2;
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 = “A18:” + tmp + " 20:" + tmp2;
Line3 = " ";
Line4 = " ";
break;}
case 10:{
//this is for AvsB 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 = "A Hit time = " + tmp2;
tmp2 = TimeConvert(A_splits[A_count-1]); // convert split time
tmp = "B: # hits = ";
Line3 = tmp + B_count;
// now display the time of last hit in secs out to hundreths of secs
tmp2 = TimeConvert(B_Times[B_count-1]); // convert hit time to XX.xx format
Line4 = "B Hit time = " + tmp2;
break;}
case 11:{
// 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
Line1 = “A 1:” + tmp + “B 1:” + tmp2;
tmp = TimeConvert(A_Times[1]); // convert hit time to XX.xx format
tmp2 = TimeConvert(B_Times[1]); // convert hit time to XX.xx format
Line2 = “A 2:” + tmp + “B 2:” + tmp2;
tmp = TimeConvert(A_Times[2]); // convert hit time to XX.xx format
tmp2 = TimeConvert(B_Times[2]); // convert hit time to XX.xx format
Line3 = “A 3:” + tmp + “B 3:” + tmp2;
tmp = TimeConvert(A_Times[3]); // convert hit time to XX.xx format
tmp2 = TimeConvert(B_Times[3]); // convert hit time to XX.xx format
Line4 = “A 4:” + tmp + “B 4:” + tmp2;
break;}
case 12:{
// 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
Line1 = “A 5:” + tmp + “B 5:” + tmp2;
tmp = TimeConvert(A_Times[5]); // convert hit time to XX.xx format
tmp2 = TimeConvert(B_Times[5]); // convert hit time to XX.xx format
Line2 = “A 6:” + tmp + “B 6:” + tmp2;
tmp = TimeConvert(A_Times[6]); // convert hit time to XX.xx format
tmp2 = TimeConvert(B_Times[6]); // convert hit time to XX.xx format
Line3 = “A 7:” + tmp + “B 7:” + tmp2;
tmp = TimeConvert(A_Times[7]); // convert hit time to XX.xx format
tmp2 = TimeConvert(B_Times[7]); // convert hit time to XX.xx format
Line4 = “A 8:” + tmp + “B 8:” + tmp2;
break;}
case 13:{
// 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
Line1 = “A 9:” + tmp + “B 9:” + tmp2;
tmp = TimeConvert(A_Times[9]); // convert hit time to XX.xx format
tmp2 = TimeConvert(B_Times[9]); // convert hit time to XX.xx format
Line2 = “A10:” + tmp + “B10:” + tmp2;
tmp = TimeConvert(A_Times[10]); // convert hit time to XX.xx format
tmp2 = TimeConvert(B_Times[10]); // convert hit time to XX.xx format
Line3 = “A11:” + tmp + “B11:” + tmp2;
tmp = TimeConvert(A_Times[11]); // convert hit time to XX.xx format
tmp2 = TimeConvert(B_Times[11]); // convert hit time to XX.xx format
Line4 = “A12:” + tmp + “B12:” + tmp2;
break;}
case 14:{
// 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
Line1 = “A13:” + tmp + “B13:” + tmp2;
tmp = TimeConvert(A_Times[13]); // convert hit time to XX.xx format
tmp2 = TimeConvert(B_Times[13]); // convert hit time to XX.xx format
Line2 = “A14:” + tmp + “B14:” + tmp2;
tmp = TimeConvert(A_Times[14]); // convert hit time to XX.xx format
tmp2 = TimeConvert(B_Times[14]); // convert hit time to XX.xx format
Line3 = “A15:” + tmp + “B15:” + tmp2;
tmp = TimeConvert(A_Times[15]); // convert hit time to XX.xx format
tmp2 = TimeConvert(B_Times[15]); // convert hit time to XX.xx format
Line4 = “A16:” + tmp + “B16:” + tmp2;
break;}
case 15:{
// 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
Line1 = “A17:” + tmp + “B17:” + tmp2;
tmp = TimeConvert(A_Times[17]); // convert hit time to XX.xx format
tmp2 = TimeConvert(B_Times[17]); // convert hit time to XX.xx format
Line2 = “A18:” + tmp + “B18:” + tmp2;
tmp = TimeConvert(A_Times[18]); // convert hit time to XX.xx format
tmp2 = TimeConvert(B_Times[18]); // convert hit time to XX.xx format
Line3 = “A19:” + tmp + “B19:” + tmp2;
tmp = TimeConvert(A_Times[19]); // convert hit time to XX.xx format
tmp2 = TimeConvert(B_Times[19]); // convert hit time to XX.xx format
Line4 = “A20:” + tmp + “B20:” + tmp2;
break;}
default:
// do the default = 0
// 4 string objects
Line1 = " Shot Timer v1 ";
Line2 = " is running ";
Line3 = " Shooter: ";
Line4 = " --STANDBY-- ";
}
}

Now as soon as the buzzer goes to start timing the display goes back to the “Shot Timer 1 Initializing” then “Shot Timer 1 Ready” screens.

It continues to function fine other than it keeps starting over when the timer should be starting.

ETA: I tried the debug void loop() and it is doing the same thing.

sspbass:
Now as soon as the buzzer goes to start timing the display goes back to the “Shot Timer 1 Initializing” then “Shot Timer 1 Ready” screens. It continues to function fine other than it keeps starting over when the timer should be starting.

ETA: I tried the debug void loop() and it is doing the same thing.

OK, I have a really good guess as to what’s happening and it’s a result of me trying to take a shortcut using the switch case. Let me assume that by "continues to function fine " you meant that the start and stop works and the times shown on the PC are good, even with “rapid” hits. That is to say what’s AFU’ed is the LCD display ONLY. If so then I think the following will fix the problem.

#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 = 500;       // Set min time btw writes to LCD to 500 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
long A_splits[MaxHits];
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

// 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
    // 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();
      LCDdisplay();
    }
    
    // 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();
        LCDdisplay();
      }
      
      // 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();
      LCDdisplay();
      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();
      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);
    }
  }
}


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
      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;
      tmp2 = TimeConvert(A_splits[A_count-1]); // convert split time
      Line3 = "Split time = " + tmp2;
      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 + "  5:" + tmp2;
      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 + "  6:" + tmp2;
      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 + "  7:" + tmp2;
      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 + "  8:" + tmp2;
      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 + " 13:" + tmp2;
      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 = "A10:" + tmp + " 14:" + tmp2;
      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 = "A11:" + tmp + " 15:" + tmp2;
      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 = "A12:" + tmp + " 16:" + tmp2;
      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 = "A17:" + tmp + " 19:" + tmp2;
      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 = "A18:" + tmp + " 20:" + tmp2;
      Line3 = "                    ";
      Line4 = "                    ";
      break;
    case 5:
      //this is for AvsB 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 = "A Hit time = " + tmp2;
      tmp2 = TimeConvert(A_splits[A_count-1]); // convert split time
      tmp = "B: # hits = ";
      Line3 = tmp + B_count;
      // now display the time of last hit in secs out to hundreths of secs
      tmp2 = TimeConvert(B_Times[B_count-1]); // convert hit time to XX.xx format
      Line4 = "B Hit time = " + tmp2;
      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
      Line1 = "A 1:" + tmp + "B 1:" + tmp2;
      tmp = TimeConvert(A_Times[1]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[1]); // convert hit time to XX.xx format
      Line2 = "A 2:" + tmp + "B 2:" + tmp2;
      tmp = TimeConvert(A_Times[2]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[2]); // convert hit time to XX.xx format
      Line3 = "A 3:" + tmp + "B 3:" + tmp2;
      tmp = TimeConvert(A_Times[3]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[3]); // convert hit time to XX.xx format
      Line4 = "A 4:" + tmp + "B 4:" + tmp2;
      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
      Line1 = "A 5:" + tmp + "B 5:" + tmp2;
      tmp = TimeConvert(A_Times[5]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[5]); // convert hit time to XX.xx format
      Line2 = "A 6:" + tmp + "B 6:" + tmp2;
      tmp = TimeConvert(A_Times[6]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[6]); // convert hit time to XX.xx format
      Line3 = "A 7:" + tmp + "B 7:" + tmp2;
      tmp = TimeConvert(A_Times[7]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[7]); // convert hit time to XX.xx format
      Line4 = "A 8:" + tmp + "B 8:" + tmp2;
      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
      Line1 = "A 9:" + tmp + "B 9:" + tmp2;
      tmp = TimeConvert(A_Times[9]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[9]); // convert hit time to XX.xx format
      Line2 = "A10:" + tmp + "B10:" + tmp2;
      tmp = TimeConvert(A_Times[10]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[10]); // convert hit time to XX.xx format
      Line3 = "A11:" + tmp + "B11:" + tmp2;
      tmp = TimeConvert(A_Times[11]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[11]); // convert hit time to XX.xx format
      Line4 = "A12:" + tmp + "B12:" + tmp2;
      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
      Line1 = "A13:" + tmp + "B13:" + tmp2;
      tmp = TimeConvert(A_Times[13]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[13]); // convert hit time to XX.xx format
      Line2 = "A14:" + tmp + "B14:" + tmp2;
      tmp = TimeConvert(A_Times[14]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[14]); // convert hit time to XX.xx format
      Line3 = "A15:" + tmp + "B15:" + tmp2;
      tmp = TimeConvert(A_Times[15]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[15]); // convert hit time to XX.xx format
      Line4 = "A16:" + tmp + "B16:" + tmp2;
      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
      Line1 = "A17:" + tmp + "B17:" + tmp2;
      tmp = TimeConvert(A_Times[17]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[17]); // convert hit time to XX.xx format
      Line2 = "A18:" + tmp + "B18:" + tmp2;
      tmp = TimeConvert(A_Times[18]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[18]); // convert hit time to XX.xx format
      Line3 = "A19:" + tmp + "B19:" + tmp2;
      tmp = TimeConvert(A_Times[19]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(B_Times[19]); // convert hit time to XX.xx format
      Line4 = "A20:" + tmp + "B20:" + tmp2;
      break;
    default: 
      // do the default = 0
      // 4 string objects
      Line1 = "    Shot Timer v1   ";
      Line2 = "     is running     ";
      Line3 = "      Shooter:      ";
      Line4 = "    --STANDBY--     ";
  }
}


String 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
    return tmp3;
  }
  else
  {
    String tmp3 = "      "; // If the time variable is zero pass 6 blanks back as a string...
    return tmp3;
  }
}


void LCDdisplay()
{
  if (millis() - LastTime > LCDtime)
  {
    LastTime = millis();
    // 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);
  }
}

What I had done wrong was leave some gaps btw the case #s, hoping the compiler would be smart enough to do the right thing. This would have made adding more displays for up to 50 targets easier later on. Alas gaps may not be allowed, apparently all the case numbers must be contiguous from 1 - XX. So the above does that, leaving no gaps. Give it a try. And is it works, change the TimerMode to 1 and see if the A vs B works.

This is what it is doing. Your fix didn’t fix it.

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

sspbass:
This is what it is doing. Your fix didn’t fix it.

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

I’m lost for a good idea ATM. It sure looks like the Arduino is getting reset somehow after the countdown is finished and whenever the first hit comes in. This is very odd behavior, it’s like the ISR that runs with a hit is going to the reset vector instead of where we want it to go to. We haven’t (deliberately) changed any code that has anything to do with the ISRs. The PC monitor indicates the start button has been pushed but we can see that also from the buzzer and (presumably) the LED. At this point all I can think of is going back a few revs to where it did work and verify it still does work. This is such odd behavior that I do have to wonder if somehow parts of memory are overwriting other parts and that’s causing the oddness. Presumably an earlier build used less memory and so works ??? I have to suspect the use of the Strings we’ve been toying with to get the display puuurty. If the Arduino is running out of RAM, odd things can happen too.

Read this (I’ve only briefly read it myself) …

http://www.arduino.cc/cgi-bin/yabb2/YaB … ;start=all

So if an old build still works then I think we need to look into ways to measure memory use and simultaneously cut RAM usage, particulary where we’ve been adding strings. I can already think of 1 easy way to do that by getting rid of the Line1-4 and just storing the output for 1 line and then writing it to the LCD. Wash, rinse and repeat for the 2-4 lines, using the same memory space over and over. We can also stick more code inline, instead of calling functions. It’s just messier to read and keep track of. If this (RAM shortage) is the problem, it doesn’t bode well for more that 20 hits. Hmmm, that’s another way to see if this is a RAM problem. Change the MaxHits to something like 10. If the program is borderline that just might be enough to get it back to the good side. And it’s easy to do.

This doesn’t explain why “fast” hits induced the problem while slow one don’t … though right now the 1’st hit is enough to get AFU.

I’m at work now, trying to breathe life back into an old HP notebook. Perhaps when I get home tonight I’ll have had a brain hemmora … storm … and have thought of something more to do. But see if you can’t find a way to check on FLASH used and perhaps RAM used and see if an old rev and/or MaxHits = 10 (or 5) makes any difference.

Without having a debugger it’s hard to tell exactly how much RAM is being used. I know Arduino has a function to query available RAM but I’ve had issues with it and fragmented memory.

I haven’t been following real close the last few days but with the use of strings previously I didn’t see anything that was screaming RAM issues.

Looks like there is only one function that really does any string manipulation. FormatData() which mostly uses the globally declared Line1-4 variables and a few local string variables. All the local variables will fall off the stack when the function returns. My gut feeling is you’re no where near using the 2KB of RAM.

DirtyD:
My gut feeling is you’re no where near using the 2KB of RAM.

I’d agree with your gut but …

If I were very conservative about how many bytes are used by a String and added up all the variables for the max number of hits, I get something close to 500 B or 25% of the available RAM. Add in some more for storing the stack(s) and such and I don’t see it hitting the limit either. But …

There’s something very very weird going on. The first hit did a reset of the Arduino (see recent vid). How does that happen ? If it’s truely software dependant, if it comes and goes with what software is being used, it can’t be a hardware problem. To me it looks like the ISR is going to the reset vector but how can that be ? I was thinking that it was just the data to the LCD that was AFU but there’s no “printouts” to the serial monitor other than the one that happens with the start button push. So the problem lies “deeper” than just getting good data to the LCD. How does anything in the recent software builds make that happen ? Perhaps there’s some really stupid typo that doesn’t get caught be the compiler and results in a freeride to Arduino Hell but I don’t see it. And it’s worse with the recent rev than it was before. Now even 1 hit causes the timer to trip out.

I’m stumped ATM.

If I had a working notebook I’d be testing different things right now … alas it seems that it’s time for a trip to LaptopsRUs. :cry:

Here’s a question … look at the following snippet :

  switch (DisplayIndex)
  {
    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;
      tmp2 = TimeConvert(A_splits[A_count-1]); // convert split time
      Line3 = "Split time = " + tmp2;
      Line4 = "                    ";
      break;

Obviously a certain amount of RAM is being used for Line1, Line2, Line3, Line4, tmp and tmp2. Where does the "A: # hits = " come from ? I assume it’s program memory ? Is there any chance that it gets transfered to RAM prior to the String tmp = "A: # hits = "; ? How does the compiler know how much memory to put aside for tmp and tmp2 ? Line1-4 were declared globally upfront and I might believe the compiler sets aside 20 bytes + more for attributes because of that, but for tmp and tmp2 ?

Mee_n_Mac:
Where does the "A: # hits = " come from ? I assume it’s program memory ? Is there any chance that it gets transfered to RAM prior to the String tmp = "A: # hits = "; ? How does the compiler know how much memory to put aside for tmp and tmp2 ? Line1-4 were declared globally upfront and I might believe the compiler sets aside 20 bytes + more for attributes because of that, but for tmp and tmp2 ?

"A: # hits = " comes from program memory…

I’m pretty sure String object variables are dynamically allocated. This is usually a good thing in normal applications but embedded ones could cause issues with limited RAM. Thinking about it now since they are dynamically allocated they are probably getting fragmented.

I’ll do some testing and let you know…

DirtyD:
I’m pretty sure String object variables are dynamically allocated. This is usually a good thing in normal applications but embedded ones could cause issues with limited RAM. Thinking about it now since they are dynamically allocated they are probably getting fragmented.

The more I read about people having problems with Strings and concatenation, the more I’m inclined to believe that fragmentation leading to some odd overflow condition is the root cause. Also there seems to be some difference amongst Arduino environments, and thus libraries, as to how well memory is dynamically allocated and released. Apparently the last Arduino release, 1.0, is supposed to be better in these respects.

@sspbass : What Arduino release are you using ?

FWIW I revised the last POS along the lines I mentioned. There’s fewer Strings and fewer hits allowed and so perhaps there will be more free RAM. Even if it does work, it’s not a fix but rather a data point. But if it does run, it’ll allow some progress to be made on getting AvsB working. So give it a try and let’s see what happens.

#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

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;
    // 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
      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 = " Hit  time = " + tmp;
      lcd.setCursor(0, 1);
      lcd.print(Line);
      tmp = TimeConvert(A_splits[A_count-1]); // convert split time
      Line = "Split time = " + tmp;
      lcd.setCursor(0, 2);
      lcd.print(Line);
      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
      String Line = "A 1:" + tmp + "  5:" + tmp2;
      lcd.clear();
      lcd.print(Line)
      tmp = TimeConvert(A_Times[1]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[5]); // convert hit time to XX.xx format
      Line = "A 2:" + tmp + "  6:" + tmp2;
      lcd.setCursor(0, 1);
      lcd.print(Line);
      tmp = TimeConvert(A_Times[2]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[6]); // convert hit time to XX.xx format
      Line = "A 3:" + tmp + "  7:" + tmp2;
      lcd.setCursor(0, 2);
      lcd.print(Line);
      tmp = TimeConvert(A_Times[3]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[7]); // convert hit time to XX.xx format
      Line = "A 4:" + tmp + "  8:" + tmp2;
      lcd.setCursor(0, 3);
      lcd.print(Line);
      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
      String Line = "A 9:" + tmp + " 13:" + tmp2;
      lcd.clear();
      lcd.print(Line)
      tmp = TimeConvert(A_Times[9]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[13]); // convert hit time to XX.xx format
      Line = "A10:" + tmp + " 14:" + tmp2;
      lcd.setCursor(0, 1);
      lcd.print(Line);
      tmp = TimeConvert(A_Times[10]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[14]); // convert hit time to XX.xx format
      Line = "A11:" + tmp + " 15:" + tmp2;
      lcd.setCursor(0, 2);
      lcd.print(Line);
      tmp = TimeConvert(A_Times[11]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[15]); // convert hit time to XX.xx format
      Line = "A12:" + tmp + " 16:" + tmp2;
      lcd.setCursor(0, 3);
      lcd.print(Line);
      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
      String Line = "A17:" + tmp + " 19:" + tmp2;
      lcd.clear();
      lcd.print(Line)
      tmp = TimeConvert(A_Times[17]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[19]); // convert hit time to XX.xx format
      Line = "A18:" + tmp + " 20:" + tmp2;
      lcd.setCursor(0, 1);
      lcd.print(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--     ");
  }
}


String 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
    return tmp3;
  }
  else
  {
    String tmp3 = "      "; // If the time variable is zero pass 6 blanks back as a string...
    return tmp3;
  }
}

This looks interesting …

http://arduiniana.org/libraries/pstring/

Here’s a test to do. It takes the initialization and setup from the timer but implements a simple loop that sends a message to the LCD every 3 secs. It’ll be interesting to see if the lcd.print does the concatenation for us or not. That is does the display end up showing :

Shot

… then …

Shot Timer1
Testing

… or …

Shot

… then …

Timer1
Testing

If it’s the former we could forgoe all the concatenation we’re doing at present and let the lcd.print do it for us,

#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

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()
{
  lcd.clear();
  lcd.print("    Shot  ");
  delay(1000);
  lcd.print("Timer 1   ");
  lcd.setCursor(0, 1);
  lcd.print("     Testing      ");
  delay(2000);
}

http://arduino.cc/en/Tutorial/Memory

"Notice that there’s not much SRAM available. It’s easy to use it all up by having lots of strings in your program. For example, a declaration like:

If you run out of SRAM, your program may fail in unexpected ways; it will appear to upload successfully, but not run, or run strangely. To check if this is happening, you can try commenting out or shortening the strings or other data structures in your sketch (without changing the code). If it then runs successfully, you’re probably running out of SRAM."

Sure, sure now they tell you ! I still don’t see how we’re short on RAM but the symptoms sure look similar. Anyway the rev above does what was suggested so I guess we’ll see.

Mee_n_Mac:

DirtyD:
I’m pretty sure String object variables are dynamically allocated. This is usually a good thing in normal applications but embedded ones could cause issues with limited RAM. Thinking about it now since they are dynamically allocated they are probably getting fragmented.

The more I read about people having problems with Strings and concatenation, the more I’m inclined to believe that fragmentation leading to some odd overflow condition is the root cause. Also there seems to be some difference amongst Arduino environments, and thus libraries, as to how well memory is dynamically allocated and released. Apparently the last Arduino release, 1.0, is supposed to be better in these respects.

@sspbass : What Arduino release are you using ?

FWIW I revised the last POS along the lines I mentioned. There’s fewer Strings and fewer hits allowed and so perhaps there will be more free RAM. Even if it does work, it’s not a fix but rather a data point. But if it does run, it’ll allow some progress to be made on getting AvsB working. So give it a try and let’s see what happens.

#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

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;
// 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
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 = " Hit time = " + tmp;
lcd.setCursor(0, 1);
lcd.print(Line);
tmp = TimeConvert(A_splits[A_count-1]); // convert split time
Line = “Split time = " + tmp;
lcd.setCursor(0, 2);
lcd.print(Line);
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
String Line = “A 1:” + tmp + " 5:” + tmp2;
lcd.clear();
lcd.print(Line)
tmp = TimeConvert(A_Times[1]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[5]); // convert hit time to XX.xx format
Line = “A 2:” + tmp + " 6:” + tmp2;
lcd.setCursor(0, 1);
lcd.print(Line);
tmp = TimeConvert(A_Times[2]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[6]); // convert hit time to XX.xx format
Line = “A 3:” + tmp + " 7:” + tmp2;
lcd.setCursor(0, 2);
lcd.print(Line);
tmp = TimeConvert(A_Times[3]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[7]); // convert hit time to XX.xx format
Line = “A 4:” + tmp + " 8:" + tmp2;
lcd.setCursor(0, 3);
lcd.print(Line);
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
String Line = “A 9:” + tmp + " 13:" + tmp2;
lcd.clear();
lcd.print(Line)
tmp = TimeConvert(A_Times[9]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[13]); // convert hit time to XX.xx format
Line = “A10:” + tmp + " 14:" + tmp2;
lcd.setCursor(0, 1);
lcd.print(Line);
tmp = TimeConvert(A_Times[10]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[14]); // convert hit time to XX.xx format
Line = “A11:” + tmp + " 15:" + tmp2;
lcd.setCursor(0, 2);
lcd.print(Line);
tmp = TimeConvert(A_Times[11]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[15]); // convert hit time to XX.xx format
Line = “A12:” + tmp + " 16:" + tmp2;
lcd.setCursor(0, 3);
lcd.print(Line);
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
String Line = “A17:” + tmp + " 19:" + tmp2;
lcd.clear();
lcd.print(Line)
tmp = TimeConvert(A_Times[17]); // convert hit time to XX.xx format
tmp2 = TimeConvert(A_Times[19]); // convert hit time to XX.xx format
Line = “A18:” + tmp + " 20:" + tmp2;
lcd.setCursor(0, 1);
lcd.print(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-- ");
}
}

String 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
return tmp3;
}
else
{
String tmp3 = " "; // If the time variable is zero pass 6 blanks back as a string…
return tmp3;
}
}

This code registers the first hit but no time and it locks up.

Starts like normal, buzzer goes. Strike the sensor and the screen changes to “A: # hits = 1”

and that’s it. it doesn’t time out, it won’t max out hits. The serial monitor just displays “Timer is running”.

Mee_n_Mac:
Here’s a test to do. It takes the initialization and setup from the timer but implements a simple loop that sends a message to the LCD every 3 secs. It’ll be interesting to see if the lcd.print does the concatenation for us or not. That is does the display end up showing :

Shot

… then …

Shot Timer1
Testing

… or …

Shot

… then …

Timer1
Testing

If it’s the former we could forgoe all the concatenation we’re doing at present and let the lcd.print do it for us,

#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

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()
{
lcd.clear();
lcd.print(" Shot ");
delay(1000);
lcd.print("Timer 1 “);
lcd.setCursor(0, 1);
lcd.print(” Testing ");
delay(2000);
}

This displays

Shot

then

Shot Timer 1
Testing

sspbass:
This displays

Shot

then

Shot Timer 1
Testing

OK, 2 intriguing results. First the reduced string software helps a little but the underlying problem is still there. But the second result (above) gives me reason to think we can do with almost all of the string stuff. It was there to make concatenation easy to do. If we can use the lcd.print to do that instead, there’s (perhaps) little need to play with strings (and deal with the attendant problems).

What flavor of Arduino IDE are you using ?

1.0

Also note that there are two spaces between “Shot” and “Timer 1”

sspbass:
1.0

Also note that there are two spaces between “Shot” and “Timer 1”

Hmmm, 1.0 was supposed to fix all the memory allocation and release problems with strings. WTH ?!? In any case I think I see a way forward and with out using strings. I actually have work to do at work tonight so I may not be able to fool with it until tomorrow.

As for the spaces … yes there should be. But what that does show is that the LCD cursor sits where it last landed. I can “print” to the LCD some characters, then “print” some data, some more chars, some more data, etc. I can build the line to be displayed, piece by piece, using the lcd.print function and not have to concatenate f%^&#^& strings ahead of time, to be sent all at once to the LCD. It may be uglier to look at the new way, but perhaps we can get the timer working again.

I had 10 mins so this is what I’m thinking. Compare the present snippet with the proposed snippet.

Present :

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
      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 = " Hit  time = " + tmp;
      lcd.setCursor(0, 1);
      lcd.print(Line);
      tmp = TimeConvert(A_splits[A_count-1]); // convert split time
      Line = "Split time = " + tmp;
      lcd.setCursor(0, 2);
      lcd.print(Line);
      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
      String Line = "A 1:" + tmp + "  5:" + tmp2;
      lcd.clear();
      lcd.print(Line)
      tmp = TimeConvert(A_Times[1]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[5]); // convert hit time to XX.xx format
      Line = "A 2:" + tmp + "  6:" + tmp2;
      lcd.setCursor(0, 1);
      lcd.print(Line);
      tmp = TimeConvert(A_Times[2]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[6]); // convert hit time to XX.xx format
      Line = "A 3:" + tmp + "  7:" + tmp2;
      lcd.setCursor(0, 2);
      lcd.print(Line);
      tmp = TimeConvert(A_Times[3]); // convert hit time to XX.xx format
      tmp2 = TimeConvert(A_Times[7]); // convert hit time to XX.xx format
      Line = "A 4:" + tmp + "  8:" + tmp2;
      lcd.setCursor(0, 3);
      lcd.print(Line);
      break;

Proposed (tmp would now be a 6 character, 7 byte char array and TimeConvert() changed to return that variable type)

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

If you think the above is a good idea and will work, please feel free to alter the rest of FormatData() in a similar fashion, do what’s needed to TimeConvert() and then the variable declarations to rid the code of Strings.