Counting and Timing a shock sensor

I’ve changed around the interrupts, enabling and disabling them, ala DK’s advice. So A only mode shouldn’t record B hits and show goofy things and A vs B should work as hoped for. The first version is A only and the second is A vs B, with the only difference btw the 2 being the initialization of TimerMode. But because I’ve messed with the interrupts you should put each through it’s paces to make sure it all still works as it used to. In A only check that A hits are displayed and the review displays are what you’ve seen before. Also check that B hits do nothing at all now. Check the max number of hits stop the timer and the stop button does as well. I hope I remembered correctly that you’re using pin 2 connected to the shock sensor as A only mode is tied to using that pin (it can be easily changed). For AvsB give it your best beating, I do want to see some simultaneous A and B hits at some point (tie sensor to both pins 2 and 3). How the Arduino handles simultaneous (or nearly so) hits will determine if the interrupt setup is fine as it is or needs changing. Best if we find that out now.

I re-upped the max hits back to 20 as well. I’d put more “clean up” fixes in as well but it’s best not to taunt the coding gods too much.

TimerMode = 0, A only mode

#include <LiquidCrystal.h>

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

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

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

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


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(3000);

  // 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
    
    // 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 based on mode here ATM
      // this will change when config mode is added ?
      switch (TimerMode)
      {
        case 1:
          //this is for A vs B shooter mode
          attachInterrupt(0, ISR_A, FALLING);
          attachInterrupt(1, ISR_B, FALLING);
          DisplayIndex = 5;   // display will show count and time of hits
          break;
        default: 
          // the default = single A shooter mode
          attachInterrupt(0, ISR_A, FALLING);
          DisplayIndex = 1;   // display will show count and time of hits
      }
      
      // 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()
{
  // turn off interrupts
  detachInterrupt(0);
  detachInterrupt(1);
  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()
{
  // 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()
{
  // 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()
{
AupDtFlag = 0;
BupDtFlag = 0;
// 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 = ");
      if(A_count < 10);
      {
        lcd.print(' ');
      } 
      lcd.print(A_count);
      lcd.setCursor(0, 1);
      lcd.print(" Hit  time = ");
      TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      lcd.setCursor(0, 2);
      lcd.print("Split time = ");
      TimeConvert(A_splits[A_count-1]); // convert split time
      break;
    case 2:
      // this is A review mode hits 1-8
      lcd.clear();
      int k = 0;
      for (int i=0; i < 4 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print("  ");
        lcd.print(i+5);
        lcd.print(":");
        TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
      }
      break;
    case 3:
      // this is A review mode hits 9-16
      lcd.clear();
      int k = 0;
      for (int i=8; i < 12 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");    // this will display oddly for hits 9 and 13
        if(i == 8)
        {
          lcd.print(" ");
        }
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" ");
        lcd.print(i+5);
        lcd.print(":");
        TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
      }
      break;
    case 4:
      // this is A review mode hits 17-20
      lcd.clear();
      int k = 0;
      for (int i=16; i < 20 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(" ");
        // lcd.print(i+5);
        // lcd.print(":");
        // TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
      }
      break;
    case 5:
      //this is for AvsB shooter mode
      // display number of hits so far
      lcd.clear();
      lcd.print("A: # hits = ");
      if(A_count < 10);
      {
        lcd.print(' ');
      } 
      lcd.print(A_count);
      lcd.setCursor(0, 1);
      lcd.print("A Hit time = ");
      TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      lcd.setCursor(0, 2);
      lcd.print("B: # hits = ");
      if(B_count < 10);
      {
        lcd.print(' ');
      } 
      lcd.print(B_count);
      lcd.setCursor(0, 3);
      lcd.print("B Hit time = ");
      TimeConvert(B_Times[A_count-1]); // Now for B times
      break;
    case 6:
      // this is AvsB review mode hits 1-4
      lcd.clear();
      int k = 0;
      for (int i=0; i < 4 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print("B ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 7:
      // this is AvsB review mode hits 5-8
      lcd.clear();
      int k = 0;
      for (int i=4; i < 8 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print("B ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 8:
      // this is AvsB review mode hits 9-12
      break;
      lcd.clear();
      int k = 0;
      for (int i=8; i < 10 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print("B ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      for (int i=10; i < 12 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print("B");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 9:
      // this is AvsB review mode hits 13-16
      lcd.clear();
      int k = 0;
      for (int i=12; i < 16 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print("B");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 10:
      // this is AvsB review mode hits 17-20
      lcd.clear();
      int k = 0;
      for (int i=16; i < 20 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print("B");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    default: 
      // do the default = 0
      lcd.clear();
      lcd.print("    Shot Timer v1   ");
      lcd.setCursor(0, 1);
      lcd.print("     is running     ");
      lcd.setCursor(0, 2);      
      lcd.print("      Shooter:      ");
      lcd.setCursor(0, 3);
      lcd.print("    --STANDBY--     ");
  }
}


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

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

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

    if (secs < 10)
    {
      lcd.print(' ');               // for sub 10 sec times add leading space
    }
    lcd.print(secs);                // now add whole secs 
    lcd.print('.');                 // now add period

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

TimerMode = 1, A vs B mode

#include <LiquidCrystal.h>

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

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

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

// initialize global variables
volatile byte TimerState = 0;            // variable for state of timer, running or not running
volatile byte AupDtFlag = 0;             // variable indication an A hit has occurred
volatile byte BupDtFlag = 0;             // variable indication a B hit has occurred
byte 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 = 1;              // initialize the mode to be A vs B
volatile int secs = 0;
volatile int frac = 0;


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(3000);

  // 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
    
    // 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 based on mode here ATM
      // this will change when config mode is added ?
      switch (TimerMode)
      {
        case 1:
          //this is for A vs B shooter mode
          attachInterrupt(0, ISR_A, FALLING);
          attachInterrupt(1, ISR_B, FALLING);
          DisplayIndex = 5;   // display will show count and time of hits
          break;
        default: 
          // the default = single A shooter mode
          attachInterrupt(0, ISR_A, FALLING);
          DisplayIndex = 1;   // display will show count and time of hits
      }
      
      // 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()
{
  // turn off interrupts
  detachInterrupt(0);
  detachInterrupt(1);
  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()
{
  // 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()
{
  // 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()
{
AupDtFlag = 0;
BupDtFlag = 0;
// 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 = ");
      if(A_count < 10);
      {
        lcd.print(' ');
      } 
      lcd.print(A_count);
      lcd.setCursor(0, 1);
      lcd.print(" Hit  time = ");
      TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      lcd.setCursor(0, 2);
      lcd.print("Split time = ");
      TimeConvert(A_splits[A_count-1]); // convert split time
      break;
    case 2:
      // this is A review mode hits 1-8
      lcd.clear();
      int k = 0;
      for (int i=0; i < 4 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print("  ");
        lcd.print(i+5);
        lcd.print(":");
        TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
      }
      break;
    case 3:
      // this is A review mode hits 9-16
      lcd.clear();
      int k = 0;
      for (int i=8; i < 12 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");    // this will display oddly for hits 9 and 13
        if(i == 8)
        {
          lcd.print(" ");
        }
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" ");
        lcd.print(i+5);
        lcd.print(":");
        TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
      }
      break;
    case 4:
      // this is A review mode hits 17-20
      lcd.clear();
      int k = 0;
      for (int i=16; i < 20 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(" ");
        // lcd.print(i+5);
        // lcd.print(":");
        // TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
      }
      break;
    case 5:
      //this is for AvsB shooter mode
      // display number of hits so far
      lcd.clear();
      lcd.print("A: # hits = ");
      if(A_count < 10);
      {
        lcd.print(' ');
      } 
      lcd.print(A_count);
      lcd.setCursor(0, 1);
      lcd.print("A Hit time = ");
      TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      lcd.setCursor(0, 2);
      lcd.print("B: # hits = ");
      if(B_count < 10);
      {
        lcd.print(' ');
      } 
      lcd.print(B_count);
      lcd.setCursor(0, 3);
      lcd.print("B Hit time = ");
      TimeConvert(B_Times[A_count-1]); // Now for B times
      break;
    case 6:
      // this is AvsB review mode hits 1-4
      lcd.clear();
      int k = 0;
      for (int i=0; i < 4 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print("B ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 7:
      // this is AvsB review mode hits 5-8
      lcd.clear();
      int k = 0;
      for (int i=4; i < 8 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print("B ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 8:
      // this is AvsB review mode hits 9-12
      break;
      lcd.clear();
      int k = 0;
      for (int i=8; i < 10 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print("B ");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      for (int i=10; i < 12 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print("B");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 9:
      // this is AvsB review mode hits 13-16
      lcd.clear();
      int k = 0;
      for (int i=12; i < 16 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print("B");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 10:
      // this is AvsB review mode hits 17-20
      lcd.clear();
      int k = 0;
      for (int i=16; i < 20 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print("B");
        lcd.print(i+1);
        lcd.print(":");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    default: 
      // do the default = 0
      lcd.clear();
      lcd.print("    Shot Timer v1   ");
      lcd.setCursor(0, 1);
      lcd.print("     is running     ");
      lcd.setCursor(0, 2);      
      lcd.print("      Shooter:      ");
      lcd.setCursor(0, 3);
      lcd.print("    --STANDBY--     ");
  }
}


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

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

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

    if (secs < 10)
    {
      lcd.print(' ');               // for sub 10 sec times add leading space
    }
    lcd.print(secs);                // now add whole secs 
    lcd.print('.');                 // now add period

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

dkulinski:
Just a quick thought to get you out of an interrupt quickly. You should keep track of the value of the hit counter in two variables. This way when you are going through the loop you can do something like this:

if(A_count != A_count_past) { 

update_lcd();
A_count_past = A_count;
}




This way you aren't adding too many instructions to the ISR and are sure to jump out of it quickly. 



Dan

Hopefully disabling and enabling the interrupts means the TimerState test is no longer needed (as you noted earlier) and the ISR is pretty simple. That said, there’s no driving reason (that I can see) to make the ISRs really short. When the timer is running the code is looping just looking to update the display in the background while the ISRs are recording hits in the foreground. So long as the background processing doesn’t take too long the display gets updated in a “reasonable” amount of time. What’s reasonable ? There’s no hard requirement IMO, just that the display seems responsive. Heck I doubt the display itself reacts in less than 0.1 secs. So keep all the processing to perhaps 0.2 secs max ??

The ISR needs only to complete in the time above and is, really, limited by the min hit-to-hit time. In real life it would be tough to get 2 good hits in less than 0.1 secs. So long as the ISR completes in less than that, it’ll be ready to record the next hit. Are there some more restrictive reqs ? In an A vs B mode, there may be 2 nearly simultaneous hits. When the 1’st happens, it’s ISR will prohibit the 2’nd ISR from running until it’s done. We don’t want that 1’sr ISR to delay the 2’nd hit time from being recorded “too late”. What’s too late ? Perhaps a msec, that’s our timing resolution but even then the timer only displays to 10 msec resolution. So something btw 1 and 10 msec would look to be OK. Still an incredibly loooong time for an MCU to do ISR processing.

Can I dream up some other reason to limit the time spent in the ISR ? Perhaps, depending on what usage the timer might have in the future. If the ability to know which specific target was hit is desired, then measuring pulsewidth is one way to do it. The sensor has that ability, to set PW, now so this concept isn’t just a pipe dream. So looking ahead the ISR must be done in less than the min PW, so it can detect and time both the falling and rising edges of the pulse. What’s the min PW ? Right now it’s perhaps a bit more than 1 msec. That’s a good min number when I think about how long a cable the pulse might have to run through to get from target to timer. It can’t be much shorter.

So the max time an ISR has to complete it’s processing is perhaps as long as 1 msec. Heck, let’s give it a safety factor of 5 and say it’s to be done in 200 usec. That’s still a lot of instructions, so long as they aren’t A/D conversions.

Correction, I goofed up an accidentally changed the wrong variable. The A vs B is working correctly other than some formatting issues when the hit numbers get into the double digits. I think we should drop the “s” from the end of the times. I think it’s safe to say that they are implied and it will help the screen look a lot less cluttered. Especially on the A vs B mode.

sspbass:
I think we should drop the “s” from the end of the times. I think it’s safe to say that they are implied and it will help the screen look a lot less cluttered. Especially on the A vs B mode.

Try this then.

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

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

    if (secs < 10)
    {
      lcd.print(' ');               // for sub 10 sec times add leading space
    }
    lcd.print(secs);                // now add whole secs 
    lcd.print('.');                 // now add period

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

I also wonder if AvsB review mode might look better with this :

_9A: XX.xx B: XX.xx

10A: XX.xx B: XX.xx

11A: XX.xx B: XX.xx

12A: XX.xx B: XX.xx

Where the 1’st number is the hit number. (I had to use an _9 'cuz the forum deletes leading spaces)

Agreed, that looks a lot better.

The reason to keep the ISRs short is that other interrupts are disabled until the current one completes. So if you had both shooters hit at almost the same time you may miss the second hit. Also, the reason A displays junk in the older code could be due to the A_hits not being initialized. Really if you worked around that in single shooter mode you are good.

Dan

sspbass:
Agreed, that looks a lot better.

OK, give these 2 a try. ETA : the new 0 hit fix for AvsB

void FormatData()
{
AupDtFlag = 0;
BupDtFlag = 0;
// 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 = ");
      if(A_count < 10);
      {
        lcd.print(' ');
      } 
      lcd.print(A_count);
      lcd.setCursor(0, 1);
      lcd.print(" Hit  time = ");
      TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      lcd.setCursor(0, 2);
      lcd.print("Split time = ");
      TimeConvert(A_splits[A_count-1]); // convert split time
      break;
    case 2:
      // this is A review mode hits 1-8
      lcd.clear();
      int k = 0;
      for (int i=0; i < 4 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(": ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print("  ");
        lcd.print(i+5);
        lcd.print(": ");
        TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
      }
      break;
    case 3:
      // this is A review mode hits 9-16
      lcd.clear();
      int k = 0;
      for (int i=8; i < 12 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");    // this will display oddly for hits 9 and 13
        if(i == 8)
        {
          lcd.print(" ");
        }
        lcd.print(i+1);
        lcd.print(": ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" ");
        lcd.print(i+5);
        lcd.print(": ");
        TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
      }
      break;
    case 4:
      // this is A review mode hits 17-20
      lcd.clear();
      int k = 0;
      for (int i=16; i < 20 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(": ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(" ");
        // lcd.print(i+5);
        // lcd.print(": ");
        // TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
      }
      break;
    case 5:
      //this is for AvsB shooter mode
      // display number of hits so far
      lcd.clear();
      lcd.print("A: # hits = ");
      if(A_count < 10);
      {
        lcd.print(' ');
      } 
      lcd.print(A_count);
      lcd.setCursor(0, 1);
      lcd.print("A Hit time = ");
      if(A_count)
      {
        TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      }
      lcd.setCursor(0, 2);
      lcd.print("B: # hits = ");
      if(B_count < 10);
      {
        lcd.print(' ');
      } 
      lcd.print(B_count);
      lcd.setCursor(0, 3);
      lcd.print("B Hit time = ");
      if(B_count)
      {
        TimeConvert(B_Times[A_count-1]); // Now for B times
      }
      break;
    case 6:
      // this is AvsB review mode hits 1-4
      lcd.clear();
      int k = 0;
      for (int i=0; i < 4 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print(' ')
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 7:
      // this is AvsB review mode hits 5-8
      lcd.clear();
      int k = 0;
      for (int i=4; i < 8 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print(' ')
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 8:
      // this is AvsB review mode hits 9-12
      break;
      lcd.clear();
      int k = 0;
      for (int i=8; i < 10 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print(' ')
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      for (int i=10; i < 12 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 9:
      // this is AvsB review mode hits 13-16
      lcd.clear();
      int k = 0;
      for (int i=12; i < 16 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 10:
      // this is AvsB review mode hits 17-20
      lcd.clear();
      int k = 0;
      for (int i=16; i < 20 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    default: 
      // do the default = 0
      lcd.clear();
      lcd.print("    Shot Timer v1   ");
      lcd.setCursor(0, 1);
      lcd.print("     is running     ");
      lcd.setCursor(0, 2);      
      lcd.print("      Shooter:      ");
      lcd.setCursor(0, 3);
      lcd.print("    --STANDBY--     ");
  }
}
void TimeConvert(long input)
{
// takes the msecs as argument and sets the time for that hit as a XX.xxs string

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

    if (secs < 10)
    {
      lcd.print(' ');               // for sub 10 sec times add leading space
    }
    lcd.print(secs);                // now add whole secs 
    lcd.print('.');                 // now add period

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

BTW are you using the “new ISR” code (the whole 2 projects posted a few posts ago) ?

dkulinski:
The reason to keep the ISRs short is that other interrupts are disabled until the current one completes. So if you had both shooters hit at almost the same time you may miss the second hit. Also, the reason A displays junk in the older code could be due to the A_hits not being initialized. Really if you worked around that in single shooter mode you are good.

Dan

I forgot that the millis() function might get sidelined by the ISRs. Still the (potential) loss of a few msecs over the course should be inconsequential given the display resolution of 10 msec. The ISR now is :

void ISR_A()
{
  // 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;
}

If I remove the start time subtraction, it’s pretty minimal. Then as you proposed I could put a hit count comparison in the background processing (to detect that a hit has occured) and remove the update flag. Then it’s a minimized as possible.

The last version of the project detached and attached the interrupts based on the timer state (running/stopped) and timer mode (only A is attached in single shooter mode), so B hits shouldn’t do anything bad now. That said the ClearData() function should have zero’ed all the A (and B) hits so the A display should have read 0 hits and a blank time. This should also be the case for the AvsB hit-by-hit display. Afterall one hit, A or B, is going to happen first in this mode and the resulting display will have that 1’st hit time and the “zero” hit display for the other channel. I guess I’ll wait for sspbass to post a vid of some AvsB trial(s).

EDIT: Hmmm, seems I will need to change the AvsB hit-by-hit display. Because arrays are zero indexed, the A_Times[A_count-1] goes off the reservation with 0 hits. This explains the odd display in the last vid.

Alright, I don’t have any further ideas on the code. That means I can get back to beating my head over my LCD display.

I like to looks of the 4 line LCD, I may have to change over to that on my project too. Also, in my code, look a the random setup. I use the current millis() count to seed the random generator and then pull out a number. I know right now it is just a constantly 2 seconds.

Dan

Here’s the fixed FormatData() that makes the AvsB display good for 0 hits. I also replaced the version in the post above this.

void FormatData()
{
AupDtFlag = 0;
BupDtFlag = 0;
// 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 = ");
      if(A_count < 10);
      {
        lcd.print(' ');
      } 
      lcd.print(A_count);
      lcd.setCursor(0, 1);
      lcd.print(" Hit  time = ");
      TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      lcd.setCursor(0, 2);
      lcd.print("Split time = ");
      TimeConvert(A_splits[A_count-1]); // convert split time
      break;
    case 2:
      // this is A review mode hits 1-8
      lcd.clear();
      int k = 0;
      for (int i=0; i < 4 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(": ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print("  ");
        lcd.print(i+5);
        lcd.print(": ");
        TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
      }
      break;
    case 3:
      // this is A review mode hits 9-16
      lcd.clear();
      int k = 0;
      for (int i=8; i < 12 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");    // this will display oddly for hits 9 and 13
        if(i == 8)
        {
          lcd.print(" ");
        }
        lcd.print(i+1);
        lcd.print(": ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" ");
        lcd.print(i+5);
        lcd.print(": ");
        TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
      }
      break;
    case 4:
      // this is A review mode hits 17-20
      lcd.clear();
      int k = 0;
      for (int i=16; i < 20 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(": ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(" ");
        // lcd.print(i+5);
        // lcd.print(": ");
        // TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
      }
      break;
    case 5:
      //this is for AvsB shooter mode
      // display number of hits so far
      lcd.clear();
      lcd.print("A: # hits = ");
      if(A_count < 10);
      {
        lcd.print(' ');
      } 
      lcd.print(A_count);
      lcd.setCursor(0, 1);
      lcd.print("A Hit time = ");
      if(A_count)
      {
        TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      }
      lcd.setCursor(0, 2);
      lcd.print("B: # hits = ");
      if(B_count < 10);
      {
        lcd.print(' ');
      } 
      lcd.print(B_count);
      lcd.setCursor(0, 3);
      lcd.print("B Hit time = ");
      if(B_count)
      {
        TimeConvert(B_Times[A_count-1]); // Now for B times
      }
      break;
    case 6:
      // this is AvsB review mode hits 1-4
      lcd.clear();
      int k = 0;
      for (int i=0; i < 4 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print(' ')
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 7:
      // this is AvsB review mode hits 5-8
      lcd.clear();
      int k = 0;
      for (int i=4; i < 8 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print(' ')
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 8:
      // this is AvsB review mode hits 9-12
      break;
      lcd.clear();
      int k = 0;
      for (int i=8; i < 10 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print(' ')
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      for (int i=10; i < 12 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 9:
      // this is AvsB review mode hits 13-16
      lcd.clear();
      int k = 0;
      for (int i=12; i < 16 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 10:
      // this is AvsB review mode hits 17-20
      lcd.clear();
      int k = 0;
      for (int i=16; i < 20 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    default: 
      // do the default = 0
      lcd.clear();
      lcd.print("    Shot Timer v1   ");
      lcd.setCursor(0, 1);
      lcd.print("     is running     ");
      lcd.setCursor(0, 2);      
      lcd.print("      Shooter:      ");
      lcd.setCursor(0, 3);
      lcd.print("    --STANDBY--     ");
  }
}

I think the code needs to be retested before we go much farther to be sure some bugs didn’t creep in.

dkulinski:
Alright, I don’t have any further ideas on the code. That means I can get back to beating my head over my LCD display.

I like to looks of the 4 line LCD, I may have to change over to that on my project too. Also, in my code, look a the random setup. I use the current millis() count to seed the random generator and then pull out a number. I know right now it is just a constantly 2 seconds.

Dan

The 4x20 adds a lot compared to the 2x16. Those extra 4 chars on a line make a lot of difference. We’ll have to see how Static makes out in the other thread. This project, with all it’s data, cries out for a graphical display. But I’ve never played with one so …

My timer has 2 countdown modes. It’s either fixed or random. The random mode is so you don’t cheat yourself by guessing the time from start button to beep. The fixed mode is for use when you have an RO calling the start. No need for a random delay then. I know other timers have a user setable fixed time delay. One feature I thought might be nice (from my experience) would be an auto-start mode. This would start the timer some time (random, fixed ???) after it stopped (via hit count or time out) so you don’t have to move your hand to push the start button. That way you look to see your time and then go to the ready position, just like you’d do with an RO calling it. No need to remove your grip, push the start and regrip. Just shoot, look, wait, shoot … until manully stopped.

So sspbass … what do you want ?

Sounds good to me! Keep in mind I haven’t tried any of the code on this page yet, I’m still at work.

My thoughts would be that we want to work out some of these user configurable settings so we can change them on the fly via the lcd and push buttons first. But I’m a noob so perhaps it makes more sense to work out all of the modes via changing variables in the code before one worries about user interface. Also, keep in mind the future wireless. This thing is worthless functionality wise without wireless.

sspbass:
My thoughts would be that we want to work out some of these user configurable settings so we can change them on the fly via the lcd and push buttons first.

That was my first thought as well but now that I think about it … it might be better to hard code the mode input for now, if only to see what works and what doesn’t, and then determine what user inputs are actually needed for each mode/“game” before we come up with the user interface to set those inputs. I’ll have to go back and find the various modes discussed a while ago but IIRC they were flavors of how many hits in some pre-allotted time (which the timer can do now) or stop after X hits scored and display the time (also do-able now). One thing not discussed that comes to mind is some kind of “Winner” display. I think during some A vs B competition, the present A vs B hit-by-hit display is a good one (at least for now). But let’s say the Course of Fire (CoF) is first to score 6 hits, obviously lowest time wins. So when those 6 hits have been scored, the timer stops and buzzes as it can now and then … what ? Do you want a “A wins !!! B is a looooosah !!!” display with perhaps some cumulative times to automagically show up ? Seems to me that would be helpful (perhaps if not so sarcastically worded) rather than having to look at times to see who won.

Thoughts ?

I was thinking something along the lines of the winners info blinking on the main screen. Not the review screen but the one that displays the number of hits and elapsed time for each shooter. Or I think you can also invert the colors so the winners info is a colored background with the text blank.

here is the latest code running.

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

sspbass:
I was thinking something along the lines of the winners info blinking on the main screen. Not the review screen but the one that displays the number of hits and elapsed time for each shooter. Or I think you can also invert the colors so the winners info is a colored background with the text blank.

I see the formatting problems and here’s the fix for those.

void FormatData()
{
AupDtFlag = 0;
BupDtFlag = 0;
// 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 = ");
      if(A_count < 10);
      {
        lcd.print(' ');
      } 
      lcd.print(A_count);
      lcd.setCursor(0, 1);
      lcd.print(" Hit  time = ");
      TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      lcd.setCursor(0, 2);
      lcd.print("Split time = ");
      TimeConvert(A_splits[A_count-1]); // convert split time
      break;
    case 2:
      // this is A review mode hits 1-8
      lcd.clear();
      int k = 0;
      for (int i=0; i < 4 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(": ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print("  ");
        lcd.print(i+5);
        lcd.print(": ");
        TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
      }
      break;
    case 3:
      // this is A review mode hits 9-16
      lcd.clear();
      int k = 0;
      for (int i=8; i < 12 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");    // this will display oddly for hits 9 and 13
        if(i == 8)
        {
          lcd.print(" ");
        }
        lcd.print(i+1);
        lcd.print(": ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" ");
        lcd.print(i+5);
        lcd.print(": ");
        TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
      }
      break;
    case 4:
      // this is A review mode hits 17-20
      lcd.clear();
      int k = 0;
      for (int i=16; i < 20 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(": ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(" ");
        // lcd.print(i+5);
        // lcd.print(": ");
        // TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
      }
      break;
    case 5:
      //this is for AvsB shooter mode
      // display number of hits so far
      lcd.clear();
      lcd.print(" A: # hits = ");
      if(A_count < 10);
      {
        lcd.print(' ');
      } 
      lcd.print(A_count);
      lcd.setCursor(0, 1);
      lcd.print("A Hit time = ");
      if(A_count)
      {
        TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      }
      lcd.setCursor(0, 2);
      lcd.print(" B: # hits = ");
      if(B_count < 10);
      {
        lcd.print(' ');
      } 
      lcd.print(B_count);
      lcd.setCursor(0, 3);
      lcd.print("B Hit time = ");
      if(B_count)
      {
        TimeConvert(B_Times[A_count-1]); // Now for B times
      }
      break;
    case 6:
      // this is AvsB review mode hits 1-4
      lcd.clear();
      int k = 0;
      for (int i=0; i < 4 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print(' ')
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 7:
      // this is AvsB review mode hits 5-8
      lcd.clear();
      int k = 0;
      for (int i=4; i < 8 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print(' ')
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 8:
      // this is AvsB review mode hits 9-12
      break;
      lcd.clear();
      lcd.print(" 9 A: ");
      TimeConvert(A_Times[8]); // convert hit time to XX.xx format
      lcd.print(" B: ");
      TimeConvert(B_Times[8]); // convert hit time to XX.xx format
      for (int i=9; i < 12 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 9:
      // this is AvsB review mode hits 13-16
      lcd.clear();
      int k = 0;
      for (int i=12; i < 16 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    case 10:
      // this is AvsB review mode hits 17-20
      lcd.clear();
      int k = 0;
      for (int i=16; i < 20 ; i++)
      {
        k = (i % 4)
        lcd.setCursor(0,k);
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;
    default: 
      // do the default = 0
      lcd.clear();
      lcd.print("    Shot Timer v1   ");
      lcd.setCursor(0, 1);
      lcd.print("     is running     ");
      lcd.setCursor(0, 2);      
      lcd.print("      Shooter:      ");
      lcd.setCursor(0, 3);
      lcd.print("    --STANDBY--     ");
  }
}

This should make the A vs B look as good as the A only displays. As for the “Ooops” (going back the the standby display), that is not supposed to happen but it’s a consequence of recording more hits than the Max Hits number. This last build that limit was 20 hits and you see in the vid you got more than that recorded (21, 22 and 24). That is a result of the very fast switch bounce occuring with the switch you’ve been using for the B input. I don’t expect that to happen normally (the shock sensor has a 1.7 msec PW) so I’m not going to bother to fix that now. Try the simultaneous inputs again, but using the shock sensor instead of the switch. I think you’ll see a max of 20 hits and the review should scroll from the 16-20 hits through to the 1-4 hits display. Other than that I’m happy, it seems to be working ! :dance:

It looks great!

Heres the latest code in its entirety the compiles.

#include <LiquidCrystal.h>

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

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

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

// initialize global variables
volatile byte TimerState = 0;            // variable for state of timer, running or not running
volatile byte AupDtFlag = 0;             // variable indication an A hit has occurred
volatile byte BupDtFlag = 0;             // variable indication a B hit has occurred
byte 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 = 1;              // initialize the mode to be single shooter
volatile int secs = 0;
volatile int frac = 0;


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(3000);

  // 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
    
    // 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 based on mode here ATM
      // this will change when config mode is added ?
      switch (TimerMode)
      {
        case 1:
          //this is for A vs B shooter mode
          attachInterrupt(0, ISR_A, FALLING);
          attachInterrupt(1, ISR_B, FALLING);
          DisplayIndex = 5;   // display will show count and time of hits
          break;
        default: 
          // the default = single A shooter mode
          attachInterrupt(0, ISR_A, FALLING);
          DisplayIndex = 1;   // display will show count and time of hits
      }
      
      // 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()
{
  // turn off interrupts
  detachInterrupt(0);
  detachInterrupt(1);
  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()
{
  // 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()
{
  // 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()
{
AupDtFlag = 0;
BupDtFlag = 0;
// 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 = ");
      if(A_count < 10);
      {
        lcd.print(' ');
      } 
      lcd.print(A_count);
      lcd.setCursor(0, 1);
      lcd.print(" Hit  time = ");
      TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      lcd.setCursor(0, 2);
      lcd.print("Split time = ");
      TimeConvert(A_splits[A_count-1]); // convert split time
      break;}
    case 2:{
      // this is A review mode hits 1-8
      lcd.clear();
      int k = 0;
      for (int i=0; i < 4 ; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print("A ");
        lcd.print(i+1);
        lcd.print(": ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print("  ");
        lcd.print(i+5);
        lcd.print(": ");
        TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
      }
      break;}
    case 3:{
      // this is A review mode hits 9-16
      lcd.clear();
      int k = 0;
      for (int i=8; i < 12 ; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print("A");    // this will display oddly for hits 9 and 13
        if(i == 8)
        {
          lcd.print(" ");
        }
        lcd.print(i+1);
        lcd.print(": ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" ");
        lcd.print(i+5);
        lcd.print(": ");
        TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
      }
      break;}
    case 4:{
      // this is A review mode hits 17-20
      lcd.clear();
      int k = 0;
      for (int i=16; i < 20 ; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print("A");
        lcd.print(i+1);
        lcd.print(": ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        // lcd.print(" ");
        // lcd.print(i+5);
        // lcd.print(": ");
        // TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
      }
      break;}
    case 5:{
      //this is for AvsB shooter mode
      // display number of hits so far
      lcd.clear();
      lcd.print(" A: # hits = ");
      if(A_count < 10);
      {
        lcd.print(' ');
      } 
      lcd.print(A_count);
      lcd.setCursor(0, 1);
      lcd.print("A Hit time = ");
      if(A_count)
      {
        TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
      }
      lcd.setCursor(0, 2);
      lcd.print(" B: # hits = ");
      if(B_count < 10);
      {
        lcd.print(' ');
      } 
      lcd.print(B_count);
      lcd.setCursor(0, 3);
      lcd.print("B Hit time = ");
      if(B_count)
      {
        TimeConvert(B_Times[A_count-1]); // Now for B times
      }
      break;}
    case 6:{
      // this is AvsB review mode hits 1-4
      lcd.clear();
      int k = 0;
      for (int i=0; i < 4 ; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print(' ');
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;}
    case 7:{
      // this is AvsB review mode hits 5-8
      lcd.clear();
      int k = 0;
      for (int i=4; i < 8 ; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print(' ');
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;}
    case 8:{
      // this is AvsB review mode hits 9-12
      lcd.clear();
      int k = 0;
      lcd.print(" 9 A: ");
      TimeConvert(A_Times[8]); // convert hit time to XX.xx format
      lcd.print(" B: ");
      TimeConvert(B_Times[8]); // convert hit time to XX.xx format
      for (int i=9; i < 12 ; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;}
    case 9:{
      // this is AvsB review mode hits 13-16
      lcd.clear();
      int k = 0;
      for (int i=12; i < 16 ; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;}
    case 10:{
      // this is AvsB review mode hits 17-20
      lcd.clear();
      int k = 0;
      for (int i=16; i < 20 ; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print(i+1);
        lcd.print(" A: ");
        TimeConvert(A_Times[i]); // convert hit time to XX.xx format
        lcd.print(" B: ");
        TimeConvert(B_Times[i]); // convert hit time to XX.xx format
      }
      break;}
    default: 
      // do the default = 0
      lcd.clear();
      lcd.print("    Shot Timer v1   ");
      lcd.setCursor(0, 1);
      lcd.print("     is running     ");
      lcd.setCursor(0, 2);      
      lcd.print("      Shooter:      ");
      lcd.setCursor(0, 3);
      lcd.print("    --STANDBY--     ");
  }
}
void TimeConvert(long input)
{
// takes the msecs as argument and sets the time for that hit as a XX.xxs string

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

    if (secs < 10)
    {
      lcd.print(' ');               // for sub 10 sec times add leading space
    }
    lcd.print(secs);                // now add whole secs 
    lcd.print('.');                 // now add period

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

I’m going to put together an order to get my own display and to rig up another hit sensor. Any suggestions for something I should get?

sspbass:
It looks great!

Heres the latest code in its entirety the compiles.

Good. I see you fixed my hyper active deletion. Good find. It's now the baseline.

sspbass:
I’m going to put together an order to get my own display and to rig up another hit sensor. Any suggestions for something I should get?

For a display ? I'm not sure what to say. Some serial display would free up pins that you need for other stuff but I'm concerned that the ISRs, when running, might mess up the "soft serial" port you'd have to use to talk to it. A graphics display, perhaps with a touch screen, would be uber cool but a whole 'nuther learning curve. I guess for the relatively few $$s, I'd get what you have now and if you decide later to replace it and grow the project, it's not that many $$s wasted ... and that's assuming you don't use it for something else.

Perhaps it’s time to decide what’s needed, in general, to “configure” the timer. My thinking was to have another scroll button ('cuz having to go forward to get back to what you missed is a PITA) and a dedicate “configure” or “menu” or what-ever-you-call-it button. Hitting this would bring up a menu to scroll through and pick a mode. Selecting that mode might, depending on the mode, allow you to customize that mode (ie - stop time or # of hits to win or ??) or keep the settings and exit. So 2 more buttons which might say get a serial display and hope my concerns above are silly.

Earlier you mentioned wireless. As in wireless what ? And I might add data logging, as in saving times to something like an SD card. And these might be saying you need more than an Uno or we’d better think about multiplexing the input pins or ???

I think that ultimately the hit sensor will have to be a separate unit from the control/display device. So we’ll use another arduino of some type (probably a smaller one) and have that register the hits and then when the round is up have it send all of the elapsed times back in millis to the main unit that the shooter is holding for calculations converting and display. That will free up 4 pins on the main unit as an added bonus. Check out the Xbee wireless units for the Arduinos.

http://www.adafruit.com/category/29

I occurs to me that using DK’s method of attaching and detaching the interrupts might mean we can have pins 2 and 3 do double duty. When the timer is running they are interrupt pins, getting hits from the targets. When the timer is stopped and the interrupts are disabled, I don’t see why they can’t be used as scroll button up and down inputs. The buttons would be wired in parallel to the hit sensors and be normally open, momentary short-to-ground type switches, which is what I think you’ve used so far for all the switches. Then the present scroll button could be used as the menu or configure button. I like DK’s idea to have a timer on the button so it has to be held for a bit to get into configure mode, this would prevent accidental activations.

So to see if pins 2 and 3 can be shared, I’ve changed the single scroll action we’ve had to date to use the button I think you have on the B input now. Let’s see if that works to move through the review displays. Heck I s’pose you could even use the shock sensor as a button. In fact, give it a try and see what happens (a scroll down perhaps). But now you’ll have to be careful about post stop hits.

void loop()
{
  if (TimerState) // is timer running
  {
    // timer is running
    
    // 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) > StopTime) || A_count >= StopHits || B_count >= 

StopHits)
    {
      // 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;
        case 2:
          //this is for quick draw 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;
        case 3:
          //this is for 21 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(TargetBPin) == LOW)
    {
      DisplayIndex++;             // increment DisplayIndex upon push
      if (DisplayIndex > diMax)
      {
        DisplayIndex = diMin;     // wrap around of scrolling action
      }
      // change to new display
      FormatData();
      delay(DB_delay);
    }
    if (digitalRead(TargetAPin) == LOW)
    {
      DisplayIndex--;             // decrement DisplayIndex upon push
      if (DisplayIndex < diMin)
      {
        DisplayIndex = diMax;     // 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 based on mode here ATM
      // this will change when config mode is added ?
      switch (TimerMode)
      {
        case 1:
          //this is for A vs B shooter mode
          attachInterrupt(0, ISR_A, FALLING);
          attachInterrupt(1, ISR_B, FALLING);
          DisplayIndex = 5;   // display will show count and time of hits
          break;
        case 2:
          //this is for quick draw mode
          attachInterrupt(0, ISR_A, FALLING);
          attachInterrupt(1, ISR_B, FALLING);
          DisplayIndex = 5;   // display will show count and time of hits
          StopTime = 10000;
          StopHits = 6;
          break;
        case 3:
          //this is for 21 mode
          attachInterrupt(0, ISR_A, FALLING);
          attachInterrupt(1, ISR_B, FALLING);
          DisplayIndex = 5;   // display will show count and time of hits
          StopTime = 1500;
          StopHits = MaxHits;
          break;
        default: 
          // the default = single A shooter mode
          attachInterrupt(0, ISR_A, FALLING);
          DisplayIndex = 1;   // display will show count and time of hits
      }
      
      // 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);
    }
  }
}

I’ve also changed/added some mode stuff for your “21” and “Quick draw” modes. You may see the new variables StopHits and StopTime. These serve as variable versions of MaxHits and MaxTimes. The latter are the true maximums the timer can have, due to memory contraints or whatever. The former will serve as mode dependant limits to stop the timer when the prerequisite of hits have occurred or the CoF time has elapsed. If you want you can try those out as well but be forewarned that I’ve not put a lot of thought into making them work as of yet. YMMV, past results are no guarantee of future performance, etc, etc. Later on they’ll be properly named/enumerated in the code.

sspbass:
I think that ultimately the hit sensor will have to be a separate unit from the control/display device. So we’ll use another arduino of some type (probably a smaller one) and have that register the hits and then when the round is up have it send all of the elapsed times back in millis to the main unit that the shooter is holding for calculations converting and display. That will free up 4 pins on the main unit as an added bonus. Check out the Xbee wireless units for the Arduinos.

http://www.adafruit.com/category/29

That sounds do-able. Having each target be wirelessly tied to the timer is a bit harder.