Counting and Timing a shock sensor

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

Yup, it's almost back to where it was. Gack.

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

Agreed. I was going to remove the split times as they just waste memory. Originally I had thought to store the times as (time-start time) and limit the time duration to 60 secs so the stored values could be unsigned ints but I think the present design max time is 99 secs so that's out.

dkulinski:
As for the interrupts, maybe turn them on when the start signal is received and then turn them off when the buzzer is off? This

way you can get rid of some control structures in the ISRs.

That was my original thinking. How do you do that in Arduino-land ? I was worried the "no_interrupts" (you see commented out) would prohibit other stuff from running.

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

Dan

The display has me flumoxed ATM. There's nothing obvious to me so I wonder if there isn't a strobe line to the display that's somehow gone bad. I suspect the debug "print" I put in won't be exercised abnormally and that's the only path that supposed to lead to any lcd.print. Betcha if there was a 1000 msec delay in the "running" loop the display would look better.

Perhaps Athena re-wired the BB while getting sspbass a beer ? :mrgreen:

That was my original thinking. How do you do that in Arduino-land ? I was worried the “no_interrupts” (you see commented out) would prohibit other stuff from running.

detachInterrupt

Right now I am just spitting out code into the Arduino editor making sure it compiles. I don’t think storing split times is a bad idea. I’ll see if I can work it in.

Right now I have defined 5 states. INIT, READY, ARMING, RUNNING and REVIEW. I am currently coding the review state where you can set number of shooters. I’ll post that up if there is a desire to test early code. Might be good because I can’t assemble this hardware at home.

Edit: preliminary code. Won’t do anything after a long press of start

#include <LiquidCrystal.h>

// Enable debugging code
#define DEBUG

LiquidCrystal *lcd;

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


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


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

// Strings for formatting
char *hitCount = "^: #hits = ^";
char *hitTime = "Hit time = ^";
char *hitSplit = "Split time = ^";

// Mode enumeration
enum state { INIT, READY, ARMING, RUNNING, REVIEW };
enum mode { ONEPLAYER, TWOPLAYER };
enum readyState { MAIN_MENU, PLAYER_SELECT};

enum state currentState;
enum readyState readyState = MAIN_MENU;

int numShooters = 1;

void setup()
{
  // Initialization state
  currentState = INIT;
  
  // The LCD display and associated pins
  lcd = new LiquidCrystal(12, 11, 5, 4, 9, 8);
  
  // Initialize the LCD
  // 20 columns and 4 rows
  lcd->begin(20, 4);
  
  // Display a start up message while unit is initialized
  // Wait 5 seconds at the end to make sure the message is visible
  lcd->clear();
  lcd->print("    Shot Timer 1    ");
  lcd->setCursor(0, 1);
  lcd->print("    Initializing    ");
  delay(5000);
  
  // Set the data direction of output pins
  // Initialize the values to low
  pinMode(BuzzerPin, OUTPUT);
  pinMode(LEDPin, OUTPUT);
  digitalWrite(BuzzerPin, LOW);
  digitalWrite(LEDPin, LOW);
  
  // Set the input pins and turn on pull-up resistors
  pinMode(StartPin, INPUT);
  pinMode(TargetAPin, INPUT);
  pinMode(TargetBPin, INPUT);
  digitalWrite(StartPin, HIGH);
  digitalWrite(TargetAPin, HIGH);
  digitalWrite(TargetBPin, HIGH);
  
  #ifdef DEBUG
  // Diagnostic information to be sent to PC over Serial
  Serial.begin(9600);
  Serial.print("Shot Timer v1 Ready...");
  #endif
  
  // Set display to ready
  lcd->setCursor(0, 1);
  lcd->print("      Ready       ");
  delay(5000);
  
  // All initialization finished, set state to ready
  currentState = READY;
}

void loop()
{
  int startCycles = 0;
  
  switch(currentState)
  {
    case INIT:  // Do nothing in this state, we should never be here
      break;
    case READY:  // Wait for the start button to be pressed and the mode selected
      // Check if the button is down, if so increment the counter
      if( digitalRead(StartPin) )
      {
        startCycles++;
      } else {
        startCycles = 0;
      }
      delay(100);
      readyMenu();
      
      if(startCycles >= 10)  // Button was held 1+ seconds, move to arming
      {
        startCycles = 0;
        currentState = ARMING;
        tone(BuzzerPin, 1000, 100);
        lcd->noBlink();
        break;
      }
      
      if(startCycles >= 2)
      {
        if( digitalRead(StartPin) )  // Don't do anything if the button is still down
          break;
          
        if(numShooters == 1) // Toggle number of shooters
          numShooters = 2;
        else
          numShooters = 1;
          
        tone(BuzzerPin, 2000, 100);
        startCycles = 0;
      }
      
      break;
    case ARMING:  // Enable interrupts, set random start time, turn LED on, when countdown expires sound buzzer
      break;
    case RUNNING:  // Update display on a hit
      break;
    case REVIEW:  // Disable interrupts, change to review mode
      break;
  }
}

void readyMenu()
{
  switch(readyState)
  {
    case MAIN_MENU:  // Select number of shooters, short press toggles between 1 and 2
      lcd->setCursor(0, 1);
      lcd->print("To start,hold 1 sec");
      lcd->setCursor(0, 2);
      lcd->print("Number of shooters:");
      lcd->setCursor(0, 3);
      lcd->print(numShooters);
      lcd->blink();
  }
}

Dan

Alright, code with the timer running. No review done just yet.

#include <LiquidCrystal.h>

// Enable debugging code
#define DEBUG

LiquidCrystal *lcd;

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


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


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

// Strings for formatting
char *hitCount = "^: #hits = ^";
char *hitTime = "Hit time = ^";
char *hitSplit = "Split time = ^";

// Mode enumeration
enum state { INIT, READY, ARMING, RUNNING, REVIEW };
enum mode { ONEPLAYER, TWOPLAYER };
enum readyState { MAIN_MENU, PLAYER_SELECT};

enum state currentState;
enum readyState readyState = MAIN_MENU;

int numShooters = 1;

void setup()
{
  // Initialization state
  currentState = INIT;
  
  // The LCD display and associated pins
  lcd = new LiquidCrystal(12, 11, 5, 4, 9, 8);
  
  // Initialize the LCD
  // 20 columns and 4 rows
  lcd->begin(20, 4);
  
  // Display a start up message while unit is initialized
  // Wait 5 seconds at the end to make sure the message is visible
  lcd->clear();
  lcd->print("    Shot Timer 1    ");
  lcd->setCursor(0, 1);
  lcd->print("    Initializing    ");
  delay(5000);
  
  // Set the data direction of output pins
  // Initialize the values to low
  pinMode(BuzzerPin, OUTPUT);
  pinMode(LEDPin, OUTPUT);
  digitalWrite(BuzzerPin, LOW);
  digitalWrite(LEDPin, LOW);
  
  // Set the input pins and turn on pull-up resistors
  pinMode(StartPin, INPUT);
  pinMode(TargetAPin, INPUT);
  pinMode(TargetBPin, INPUT);
  digitalWrite(StartPin, HIGH);
  digitalWrite(TargetAPin, HIGH);
  digitalWrite(TargetBPin, HIGH);
  
  #ifdef DEBUG
  // Diagnostic information to be sent to PC over Serial
  Serial.begin(9600);
  Serial.print("Shot Timer v1 Ready...");
  #endif
  
  // Set display to ready
  lcd->setCursor(0, 1);
  lcd->print("      Ready       ");
  delay(5000);
  
  // All initialization finished, set state to ready
  currentState = READY;
}

void loop()
{
  int startCycles = 0;
  
  switch(currentState)
  {
    case INIT:  // Do nothing in this state, we should never be here
      break;
    case READY:  // Wait for the start button to be pressed and the mode selected
      // Check if the button is down, if so increment the counter
      if( !digitalRead(StartPin) )
      {
        startCycles++;
      } else {
        startCycles = 0;
      }
      delay(100);
      readyMenu();
      
      if(startCycles >= 10)  // Button was held 1+ seconds, move to arming
      {
        startCycles = 0;
        currentState = ARMING;
        tone(BuzzerPin, 1000, 100);
        lcd->noBlink();
        break;
      }
      
      if(startCycles >= 2)
      {
        if( digitalRead(StartPin) )  // Don't do anything if the button is still down
          break;
          
        if(numShooters == 1) // Toggle number of shooters
          numShooters = 2;
        else
          numShooters = 1;
          
        tone(BuzzerPin, 2000, 100);
        startCycles = 0;
      }
      
      break;
    case ARMING:  // Enable interrupts, set random start time, turn LED on, when countdown expires sound buzzer
      randomSeed(millis());  // Seed the random number generator with the current millisecond count
      
      // Turn LED on to inform user that timer is running
      digitalWrite(LEDPin, HIGH);
      
      // Get the shooters ready
      lcd->clear();
      lcd->setCursor(0,0);
      lcd->print("   Get Ready!!!");
      
      attachInterrupt(0,TargetAHit, FALLING); // Start interrupts for target A

      if(numShooters > 1) // Check if there are two players
        attachInterrupt(1,TargetBHit, FALLING);
        
      delay(WaitTime + random(3000)); // Delay a random starting time
      
      StartTime = millis(); // Set the start time for later usage
      
      tone(BuzzerPin, 1000, 500); // Sound buzzer for 1/2 a second
      
      lcd->setCursor(0,0);
      lcd->print("       GO!!!       ");
      
      currentState = RUNNING;
        
      break;
    case RUNNING:  // Update display on a hit
      // Wait until the time is up, buzz again and move to review
      if((millis() - StartTime) > MaxTime)
      {
        tone(BuzzerPin, 1000, 500);
        currentState = REVIEW;
      }
      
      break;
    case REVIEW:  // Disable interrupts, change to review mode
      // Timer no longer running, turn off LED
      digitalWrite(LEDPin, LOW);
      
      detachInterrupt(0);
      detachInterrupt(1);
      
      break;
  }
}

void readyMenu()
{
  switch(readyState)
  {
    case MAIN_MENU:  // Select number of shooters, short press toggles between 1 and 2
      lcd->setCursor(0, 1);
      lcd->print("To start,hold 1 sec");
      lcd->setCursor(0, 2);
      lcd->print("Number of shooters:");
      lcd->setCursor(0, 3);
      lcd->print(numShooters);
      lcd->blink();
  }
}

void updateLcd()
{
  lcd->clear();
  lcd->print("A hits: ");
  lcd->print(A_count);
  lcd->setCursor(0,1);
  lcd->print(formatTime(A_Times[A_count - 1] - StartTime));
  
  if(numShooters > 1)
  {
    lcd->setCursor(0, 2);
    lcd->print("B hits: ");
    lcd->print(B_count);
    lcd->setCursor(0,3);
    lcd->print(formatTime(B_Times[B_count - 1] - StartTime));
  }
}

void TargetAHit()
{
  A_Times[A_count++] = millis();
  updateLcd();
}

void TargetBHit()
{
  B_Times[B_count++] = millis();
  updateLcd();
}

char * formatTime(long time)
{
  char * temp = "00.00";
  int seconds = time/1000;
  int milliseconds = time%1000;
  milliseconds /= 100;  // Only 2 digits of precision
  
  if(seconds >= 10)
    temp[0] = seconds/10+48;
  temp[1] = seconds%10+48;
 
  if(milliseconds >= 10)
    temp[3] = milliseconds/10+48;
  temp[4] = milliseconds%10+48;
  
  return temp;
}

For the moment I am done tonight. I will post review screen code tomorrow.

Quick question, is running two shooters simultaneous?

Dan

dkulinski:
Quick question, is running two shooters simultaneous?

Dan

Yup. And the timer must be able to handle 2 hits simultaneously. This is one of the nicer features of this custom build. Put pretty much any 2 people together and some competition will result. With guns it’ll be who’s faster or more accurate or some mixture of the two. In a head-to-head speed competition the usual method is to have the competitors clear a field of targets and then be the 1’st to hit a stop target. This target is design to fall one way when hit by one shooter and the other way when hit by the other. This way there’s no debating who won. But such targets are expensive, at least for us mere mortals. An electronic version of targets, that’s also non-debatable, while not costing beaucoup $$s is a very good thing.

Electronic targets also allow, IMO, a shooting competition that’s more realistic training exercise. Unless you’re engaging some large number of foes, the normal doctrine is to double tap each and assess what to do next. But trying to double tap a normal plate target knocks it down after the 1’st hit. Not so with an E-target. So a standard 5 or 6 target course of fire can be morphed into an even more fun (and worthwhile) double tap on each target, with the timer/controller being judge and jury that the required taps were performed. Add in a shooter vs shooter mode and it’s time to let the lead fly !

dkulinski:
detachInterrupt

Right now I am just spitting out code into the Arduino editor making sure it compiles. I don’t think storing split times is a bad idea. I’ll see if I can work it in.

Dan

So you’d attach then detach and later attach ?

Splits are no different than not storing the times-since-started. Really you only need to know the splits you’re displaying. I figured that storing splits and times and limiting the RAM used for such to 1k, leaving 1k for other uses, was a good start. This in turn meant a limit of 50 hits per shooter. That seemed reasonable. But now I’m left wondering if I shouldn’t work just a little harder to save RAM. It’s not like the usecs needed to calculate a split or 4 on the fly will matter when reviewing data. So storing the times of 2 shooters hits, at 4 bytes per hit and 50 hits max each yields a RAM usage of 400 bytes. That’s better than 1k.

Not to mention that you can figure splits after the fact. Hopefully you can see that the code I posted pretty much follows your flow. I just tried to simplify the interrupts to be as quick as possible. I also try to update the LCD as rarely as possible too. Review state will be pretty much like the ready state just different screens.

Dan

I spent an extra 12 hrs at work today, so my mind is too much mush to be reviewing code tonight. The 2.5 Black Russians prolly ain’t helping that either. :mrgreen:

dkulinski:
I also try to update the LCD as rarely as possible too. Dan

Have you had a chance to run the code the sspbass was running ? I don’t think there’s any intentional way that’s updating the display once per loop. So there’s either something else making the display dim or there’s some memory/heap/stack wierdness that just happens to tickle the pins used by the LCD or there’s some hardware oddness at the root of it. While you don’t have the LCD, you should be able to see if that path (to write to the LCD) is being hyper-used instead of used only on a hit-by-hit basis via the serial monitor.

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

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

void FormatData()

{

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

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




and ...




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

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

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

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

if (frac < 10)
{
  lcd.print('0');               // If frac < 10 then pad with a zero
}
lcd.print(frac);                // now add fractional secs
lcd.print('s');                 // add s to tail

}
else
{
lcd.print = (" "); // If the input variable is zero set time to 6 blank spaces
}
}

The display is still hard to read. The formatting looks perfect!

The serial monitor displays a ton of “Too Quick’s” once the timing ends on the serial monitor before it displays the times.

dkulinski:
Alright, code with the timer running. No review done just yet.

#include <LiquidCrystal.h>

// Enable debugging code
#define DEBUG

LiquidCrystal *lcd;

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

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

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

// Strings for formatting
char *hitCount = “^: #hits = ^”;
char *hitTime = “Hit time = ^”;
char *hitSplit = “Split time = ^”;

// Mode enumeration
enum state { INIT, READY, ARMING, RUNNING, REVIEW };
enum mode { ONEPLAYER, TWOPLAYER };
enum readyState { MAIN_MENU, PLAYER_SELECT};

enum state currentState;
enum readyState readyState = MAIN_MENU;

int numShooters = 1;

void setup()
{
// Initialization state
currentState = INIT;

// The LCD display and associated pins
lcd = new LiquidCrystal(12, 11, 5, 4, 9, 8);

// Initialize the LCD
// 20 columns and 4 rows
lcd->begin(20, 4);

// Display a start up message while unit is initialized
// Wait 5 seconds at the end to make sure the message is visible
lcd->clear();
lcd->print(" Shot Timer 1 “);
lcd->setCursor(0, 1);
lcd->print(” Initializing ");
delay(5000);

// Set the data direction of output pins
// Initialize the values to low
pinMode(BuzzerPin, OUTPUT);
pinMode(LEDPin, OUTPUT);
digitalWrite(BuzzerPin, LOW);
digitalWrite(LEDPin, LOW);

// Set the input pins and turn on pull-up resistors
pinMode(StartPin, INPUT);
pinMode(TargetAPin, INPUT);
pinMode(TargetBPin, INPUT);
digitalWrite(StartPin, HIGH);
digitalWrite(TargetAPin, HIGH);
digitalWrite(TargetBPin, HIGH);

#ifdef DEBUG
// Diagnostic information to be sent to PC over Serial
Serial.begin(9600);
Serial.print(“Shot Timer v1 Ready…”);
#endif

// Set display to ready
lcd->setCursor(0, 1);
lcd->print(" Ready ");
delay(5000);

// All initialization finished, set state to ready
currentState = READY;
}

void loop()
{
int startCycles = 0;

switch(currentState)
{
case INIT: // Do nothing in this state, we should never be here
break;
case READY: // Wait for the start button to be pressed and the mode selected
// Check if the button is down, if so increment the counter
if( !digitalRead(StartPin) )
{
startCycles++;
} else {
startCycles = 0;
}
delay(100);
readyMenu();

  if(startCycles >= 10)  // Button was held 1+ seconds, move to arming
  {
    startCycles = 0;
    currentState = ARMING;
    tone(BuzzerPin, 1000, 100);
    lcd->noBlink();
    break;
  }
  
  if(startCycles >= 2)
  {
    if( digitalRead(StartPin) )  // Don't do anything if the button is still down
      break;
      
    if(numShooters == 1) // Toggle number of shooters
      numShooters = 2;
    else
      numShooters = 1;
      
    tone(BuzzerPin, 2000, 100);
    startCycles = 0;
  }
  
  break;
case ARMING:  // Enable interrupts, set random start time, turn LED on, when countdown expires sound buzzer
  randomSeed(millis());  // Seed the random number generator with the current millisecond count
  
  // Turn LED on to inform user that timer is running
  digitalWrite(LEDPin, HIGH);
  
  // Get the shooters ready
  lcd->clear();
  lcd->setCursor(0,0);
  lcd->print("   Get Ready!!!");
  
  attachInterrupt(0,TargetAHit, FALLING); // Start interrupts for target A

  if(numShooters > 1) // Check if there are two players
    attachInterrupt(1,TargetBHit, FALLING);
    
  delay(WaitTime + random(3000)); // Delay a random starting time
  
  StartTime = millis(); // Set the start time for later usage
  
  tone(BuzzerPin, 1000, 500); // Sound buzzer for 1/2 a second
  
  lcd->setCursor(0,0);
  lcd->print("       GO!!!       ");
  
  currentState = RUNNING;
    
  break;
case RUNNING:  // Update display on a hit
  // Wait until the time is up, buzz again and move to review
  if((millis() - StartTime) > MaxTime)
  {
    tone(BuzzerPin, 1000, 500);
    currentState = REVIEW;
  }
  
  break;
case REVIEW:  // Disable interrupts, change to review mode
  // Timer no longer running, turn off LED
  digitalWrite(LEDPin, LOW);
  
  detachInterrupt(0);
  detachInterrupt(1);
  
  break;

}
}

void readyMenu()
{
switch(readyState)
{
case MAIN_MENU: // Select number of shooters, short press toggles between 1 and 2
lcd->setCursor(0, 1);
lcd->print(“To start,hold 1 sec”);
lcd->setCursor(0, 2);
lcd->print(“Number of shooters:”);
lcd->setCursor(0, 3);
lcd->print(numShooters);
lcd->blink();
}
}

void updateLcd()
{
lcd->clear();
lcd->print("A hits: ");
lcd->print(A_count);
lcd->setCursor(0,1);
lcd->print(formatTime(A_Times[A_count - 1] - StartTime));

if(numShooters > 1)
{
lcd->setCursor(0, 2);
lcd->print("B hits: ");
lcd->print(B_count);
lcd->setCursor(0,3);
lcd->print(formatTime(B_Times[B_count - 1] - StartTime));
}
}

void TargetAHit()
{
A_Times[A_count++] = millis();
updateLcd();
}

void TargetBHit()
{
B_Times[B_count++] = millis();
updateLcd();
}

char * formatTime(long time)
{
char * temp = “00.00”;
int seconds = time/1000;
int milliseconds = time%1000;
milliseconds /= 100; // Only 2 digits of precision

if(seconds >= 10)
temp[0] = seconds/10+48;
temp[1] = seconds%10+48;

if(milliseconds >= 10)
temp[3] = milliseconds/10+48;
temp[4] = milliseconds%10+48;

return temp;
}




For the moment I am done tonight. I will post review screen code tomorrow.



Quick question, is running two shooters simultaneous? 



Dan

This code loads fine but won’t start even with the 1 second button push it requests.

Ok, that is what happens when I don’t have hardware sitting in front of me :slight_smile:

Let me peruse it.

Dan

Alright, I just changed the if statements. I noticed one of them was wrong. Do it at least get to the select # of shooters screen?

#include <LiquidCrystal.h>

// Enable debugging code
#define DEBUG

LiquidCrystal *lcd;

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


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


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

// Strings for formatting
char *hitCount = "^: #hits = ^";
char *hitTime = "Hit time = ^";
char *hitSplit = "Split time = ^";

// Mode enumeration
enum state { INIT, READY, ARMING, RUNNING, REVIEW };
enum mode { ONEPLAYER, TWOPLAYER };
enum readyState { MAIN_MENU, PLAYER_SELECT};

enum state currentState;
enum readyState readyState = MAIN_MENU;

int numShooters = 1;

void setup()
{
  // Initialization state
  currentState = INIT;
  
  // The LCD display and associated pins
  lcd = new LiquidCrystal(12, 11, 5, 4, 9, 8);
  
  // Initialize the LCD
  // 20 columns and 4 rows
  lcd->begin(20, 4);
  
  // Display a start up message while unit is initialized
  // Wait 5 seconds at the end to make sure the message is visible
  lcd->clear();
  lcd->print("    Shot Timer 1    ");
  lcd->setCursor(0, 1);
  lcd->print("    Initializing    ");
  delay(5000);
  
  // Set the data direction of output pins
  // Initialize the values to low
  pinMode(BuzzerPin, OUTPUT);
  pinMode(LEDPin, OUTPUT);
  digitalWrite(BuzzerPin, LOW);
  digitalWrite(LEDPin, LOW);
  
  // Set the input pins and turn on pull-up resistors
  pinMode(StartPin, INPUT);
  pinMode(TargetAPin, INPUT);
  pinMode(TargetBPin, INPUT);
  digitalWrite(StartPin, HIGH);
  digitalWrite(TargetAPin, HIGH);
  digitalWrite(TargetBPin, HIGH);
  
  #ifdef DEBUG
  // Diagnostic information to be sent to PC over Serial
  Serial.begin(9600);
  Serial.print("Shot Timer v1 Ready...");
  #endif
  
  // Set display to ready
  lcd->setCursor(0, 1);
  lcd->print("      Ready       ");
  delay(5000);
  
  // All initialization finished, set state to ready
  currentState = READY;
}

void loop()
{
  int startCycles = 0;
  
  switch(currentState)
  {
    case INIT:  // Do nothing in this state, we should never be here
      break;
    case READY:  // Wait for the start button to be pressed and the mode selected
      // Check if the button is down, if so increment the counter
      if( digitalRead(StartPin) == LOW )
      {
        startCycles++;
      } else {
        startCycles = 0;
      }
      readyMenu();
      delay(100);
      
      if(startCycles >= 10)  // Button was held 1+ seconds, move to arming
      {
        startCycles = 0;
        currentState = ARMING;
        tone(BuzzerPin, 1000, 100);
        lcd->noBlink();
        break;
      }
      
      if(startCycles >= 2)
      {
        if( digitalRead(StartPin) == LOW )  // Don't do anything if the button is still down
          break;
          
        if(numShooters == 1) // Toggle number of shooters
          numShooters = 2;
        else
          numShooters = 1;
          
        tone(BuzzerPin, 2000, 100);
        startCycles = 0;
      }
      
      break;
    case ARMING:  // Enable interrupts, set random start time, turn LED on, when countdown expires sound buzzer
      randomSeed(millis());  // Seed the random number generator with the current millisecond count
      
      // Turn LED on to inform user that timer is running
      digitalWrite(LEDPin, HIGH);
      
      // Get the shooters ready
      lcd->clear();
      lcd->setCursor(0,0);
      lcd->print("   Get Ready!!!");
      
      attachInterrupt(0,TargetAHit, FALLING); // Start interrupts for target A

      if(numShooters > 1) // Check if there are two players
        attachInterrupt(1,TargetBHit, FALLING);
        
      delay(WaitTime + random(3000)); // Delay a random starting time
      
      StartTime = millis(); // Set the start time for later usage
      
      tone(BuzzerPin, 1000, 500); // Sound buzzer for 1/2 a second
      
      lcd->setCursor(0,0);
      lcd->print("       GO!!!       ");
      
      currentState = RUNNING;
        
      break;
    case RUNNING:  // Update display on a hit
      // Wait until the time is up, buzz again and move to review
      if((millis() - StartTime) > MaxTime)
      {
        tone(BuzzerPin, 1000, 500);
        currentState = REVIEW;
      }
      
      break;
    case REVIEW:  // Disable interrupts, change to review mode
      // Timer no longer running, turn off LED
      digitalWrite(LEDPin, LOW);
      
      detachInterrupt(0);
      detachInterrupt(1);
      
      break;
  }
}

void readyMenu()
{
  switch(readyState)
  {
    case MAIN_MENU:  // Select number of shooters, short press toggles between 1 and 2
      lcd->setCursor(0, 1);
      lcd->print("To start,hold 1 sec");
      lcd->setCursor(0, 2);
      lcd->print("Number of shooters:");
      lcd->setCursor(0, 3);
      lcd->print(numShooters);
      lcd->blink();
  }
}

void updateLcd()
{
  lcd->clear();
  lcd->print("A hits: ");
  lcd->print(A_count);
  lcd->setCursor(0,1);
  lcd->print(formatTime(A_Times[A_count - 1] - StartTime));
  
  if(numShooters > 1)
  {
    lcd->setCursor(0, 2);
    lcd->print("B hits: ");
    lcd->print(B_count);
    lcd->setCursor(0,3);
    lcd->print(formatTime(B_Times[B_count - 1] - StartTime));
  }
}

void TargetAHit()
{
  A_Times[A_count++] = millis();
  updateLcd();
}

void TargetBHit()
{
  B_Times[B_count++] = millis();
  updateLcd();
}

char * formatTime(long time)
{
  char * temp = "00.00";
  int seconds = time/1000;
  int milliseconds = time%1000;
  milliseconds /= 100;  // Only 2 digits of precision
  
  if(seconds >= 10)
    temp[0] = seconds/10+48;
  temp[1] = seconds%10+48;
 
  if(milliseconds >= 10)
    temp[3] = milliseconds/10+48;
  temp[4] = milliseconds%10+48;
  
  return temp;
}

Dan

It gets to this screen.

" Shot Timer 1

To start, hold 1 sec

Number of shooters:

1"

The start button and the scroll button don’t do anything.

Alright, not sure why that is happening. Let me add a bit of serial debug code.

Dan

Of course if Mac_n_Mee’s code is working, I would suggest getting the LCD update loop removed and going with that. I am only going to start the debug process over again (obviously).

Dan

sspbass:
The display is still hard to read. The formatting looks perfect!

The serial monitor displays a ton of “Too Quick’s” once the timing ends on the serial monitor before it displays the times.

OK, that’s officially odd. The only way you should see Too Quick is a repeated hit on the sensor within 100 msec of the last one. If you see “a ton” that means a lot of very fast hits. This would explain the dim display allright but you should also be seeing a ton of recorded hits (large hit count and many many hits to review). The last vid didn’t show this. Then again I think we still have the MaxHitx set to the 10 we used to try to debug the memory problem. I’ll watch the vid again, but do you think the 10 hits went by too fast ? As if they all occured 100 msecs after the other. And the serial monitor only showed the 10 A hits, no B hits ?

EDIT : I looked at the last vid again and I see 10 hits spaced over 7+ secs. None were that close in time to the others. So assuming the dim problem was the same on that run as it was on the last, it’s not a case of the hit sensor oscillating somehow.

From your description above I take it that the serial monitor showed :

“Timer is running”

“Timer is stopped”

… a ton of "Too quick"s …

… a list of some hit times, presumably all A times …

OK I figured it out ! Dumb, dumb, dumb. With the old String based code there was a function called LCDdisplay(). In it the A and B update flags were reset. When the code changed to not using Strings, that function got deleted but the reset of the update flags is still needed. It wasn’t moved and it should have been. The result is the code spends a lot of time updating the display even though there was only 1 hit. Give me a few minutes and I’ll add a fix below.

For now this should work and should fix the dim display. I think I had some other concerns about making sure the display was updated should a B hit occur while A hit processing was ongoing (and visaversa) so it may not be the permanent fix.

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

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

Bingo it works great!

I even tried some A vs B finally.

The times are going to the serial monitor great for both A & B shooters.

The A hits are registering great on the LCD as well just like normal… F it, I’m going to make a vid instead of explaining it all.

http://www.youtube.com/watch?v=-QwF3bfWg-I

Hmmmm, did you recompile with TimerMode = 1 ? Until you add another switch, there’s no other way to switch modes. The hit-by-hit display in the vid looks to be the same as the A only mode. That does point out that the B interrupts should be disabled in A only mode. This is something to be added when the ability to configure the timer via buttons is added. For now I think I’ll put a somewhat stupid hook in the code just so it doesn’t get forgot.