Counting and Timing a shock sensor

And going backwards through this thread some more, why not use the OLED display in serial mode and use NewSoftSerial to talk to it?

EDIT: Ok, it says serial but it is more along the lines of SPI. If you can free up pins 10-13 n the Arduino this should be easy to use and very fast. I just used SPI the other night to play with a 7 segment display and a 74HC595 shift register. Took me less than an hour to wire it all up and have a count up timer going.

EDIT2: In order to change the configuration you will need to change some solder bridges on the back. These are definitely small.

Dan

There are certainly other options to get this working. There’s no need/usage for all the functions built into the OLED library. Certain parts could be picked out and just plopped into the timer code as standalone functions. The thing that would be lost and most missed is the inheritance of the print class ability to “print” any variable type to the LCD. Doing the above would require converting all the unsigned longs, and ints and whatever to characters before sending them to the LCD. Do-able but a PITA. Then again getting this LCD to work hasn’t been a walk in the garden. What’s bothering me is that this shouldn’t be all that hard to do. I get the gut feeling there’s something simple that I’m missing and when found it’ll all work and I’ll be going “well doh”.

The Beer Fridge Guys (BFG) code has a zillion defines of things that aren’t used in the OLED library. I left those all in (for the moment) because I thought the preprocessor wouldn’t find anything to make the substitions with … and so would still be “happy”. Perhaps it’s balking at these and we don’t see it, leading to the lcd object never being constructed ? I think you’re correct about the immediate cause of the error message but getting to root cause is the tricky part. I’d usually blame it on a typo and some name not exactly matching some other name when it was intended to. Or some file not being in the proper directory. Or some syntax error like the <> vs " ".

I may have some free time tonight so perhaps the thing to do is make another test sketch that incorporates the init of the LCD from the BFG library and then see if we can’t send it some simple characters to display ?? If the display can be initialized then, with some added work, the library can be bypassed (though I’d personally still like to know why this isn’t working).

The big thing is to find out what the actual error is. sspbass needs to expand the error window and you can troubleshoot from there. I can’t get it to run without that strange mess or errors you see above. The lcd scope issues tend to happen when it can’t find the library header.

Dan

dkulinski:
I can’t get it to run without that strange mess or errors you see above.

Dan

Well that’s odd too ! I was looking at the “mess above” and saw this …

OLEDFourBit::OLEDFourBit(unsigned char, unsigned char, unsigned char, unsigned char, unsigned char, unsigned char)’

The argument type in BFG’s code was uint8_t, which should be the same as unsigned char, but it got me to wondering if all the supporting files are being located. In the C++ file we have …

#include <stdio.h>

#include <string.h>

#include <inttypes.h>

#include “arduino.h”

… and I would assume that stdio.h is installed and locate-able even in the Arduino 1.0 environment but the others, particularly string.h and inttypes.h ?

Here is the expanded error listing.

Well that’s less of a mess than Dan has !! But the first line tells all. It seems that the Arduino IDE can’t find the header file. Check to see that the file OLEDFourBit.h is in the same directory as the .cpp file and that it has exactly the same name as in the error message (which unless I goofed up, should be the same as I just typed). If those are both the true then the Arduino IDE should be able to find that header file and use it. Reading about the Ardiuno IDE I think there was a way to locate and find such files and thus have the IDE put the #include needed into the appropriate .h and .cpp files. Perhaps, even though the #include “OLEDFourBit.h” is in the .cpp file, the IDE has to be told where it is manually ??? The thing is when you changed the #include <OLEDFourBit.h> to #include “OLEDFourBit.h”, IIRC the tab then became a different color … indicating the IDE “knew” where the header file was. Odd stuff.

Alright, if you are on windows, the way to install libraries is as follows:

Go to your my documents folder. There should be an Arduino folder there. Open that folder and create a new folder inside called libraries if it doesn’t already exist. If it does, open it. In that folder, create a new folder called OLEDFourBit. Into this folder, copy your OLEDFourBit.h and OLEDFourBit.cc files. Once this is done, restart the Arduino IDE.

Also, changing colors just means that the IDE recognizes a specific pattern, either “” or <> should work.

Now you get to enjoy the same errors I have been receiving!

Dan

dkulinski:
Go to your my documents folder. There should be an Arduino folder there. Open that folder and create a new folder inside called libraries if it doesn’t already exist. If it does, open it. In that folder, create a new folder called OLEDFourBit. Into this folder, copy your OLEDFourBit.h and OLEDFourBit.cc files. Once this is done, restart the Arduino IDE.

Dan

And the keywords file as well. But then sspbass already knows this as he figured out that the keyword file was missing earlier in this mess. My bet is on some silly typo, like a capitalization mismatch. Or perhaps a file extension error like ._h instead of a plain jane .h

As for the other errors to be “enjoyed” :naughty: :hand:

I believe the C++ file has …

#include "OLEDFourBit.h"

#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "arduino.h"

… which is like the examples I’ve seen wrg <> vs " " for arduino.h and the new_library header file. But if that header file had to have <> because it was in the libraries/OLEDblahblahblah directory and not in the same directory as the sketch file, then should one of the above use <> instead of " " ?? Given it seems the header file isn’t being found should the above be … ???

#include <OLEDFourBit.h>

#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "arduino.h"

Would that hurt at all, if all it does is widen the search path and there isn’t another file by the same name anywhere in that widened path ?

http://www.ralphtherobot.com/images/031 … straws.jpg

Love the picture. I don’t think it will hurt but I have my doubts it will help either.

Dan

Alright, I previously had the library on the C: drive with the rest of the libraries per something I had read online. (perhaps it was just my imagination.)

I created a new libraries folder in my documents per Dan’s instructions above and copied the OLEDFourBit library into there.

Tadaaaa! it works! No compiling errors and the sketch works fine.

I’ll try the target sketch later for now I only got the test sketch going.

#include <OLEDFourBit.h>


// initialize the library with the numbers of the interface pins
// with the OLED library this also seems to do the init and begin calls
OLEDFourBit lcd(12, 11, 5, 4, 9, 8);

unsigned int count = 0;

void setup()
{
  lcd.clear();
  delay(5);
  lcd.setCursor(0, 0);
  delay(5);
  lcd.print("    Incrementing    ");
  delay(5);
  lcd.setCursor(0, 1);
  delay(5);
  lcd.print("     count test     ");
  delay(3000);
}

void loop()
{
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Count = ");
  lcd.print(count);
  count++;
  delay(1000);
}

Mee_n_Mac:
Well that was nice. I just typed a long post on how the new library doesn’t have a “print” function and so more stuff needs to change. Specifically the new OLED library has a “write” function, which I’ll guess sends ASCII characters to the LCD but doesn’t convert a numeric int or long or byte or … to an ASCII char, like print and lcd.print do. So before we go down that road (again) let’s do a simple “Hello World” test to see if the new library compiles and if anything intelligible can be displayed on this LCD.

#include "OLEDFourBit.h"

// initialize the library with the numbers of the interface pins
// with the OLED library this also seems to do the init and begin calls
OLEDFourBit OLEDFourBit(12, 11, 5, 4, 9, 8);

void setup()
{
OLEDFourBit.clear();
delay(5);
OLEDFourBit.setCursor(0, 0);
delay(5);
OLEDFourBit.write(" Shot Timer 1 “);
delay(5);
OLEDFourBit.setCursor(0, 1);
delay(5);
OLEDFourBit.write(” Initializing ");
delay(3000);
}

void loop()
{
OLEDFourBit.clear();
delay(1000);
OLEDFourBit.setCursor(0, 0);
OLEDFourBit.write(“Line 1”);
OLEDFourBit.write(" cursor check");
OLEDFourBit.setCursor(0, 1);
OLEDFourBit.write(" Line 2");
OLEDFourBit.setCursor(0, 2);
OLEDFourBit.write(" Line 3");
OLEDFourBit.setCursor(0, 3);
OLEDFourBit.write(" Line 4");
delay(5000);
}




Assuming the above works then we'll need to change all the lcd.print(XYZ) to OLEDFourBit.write(XYZ) and convert all non-char XYZs to chars before "writing" them. I was planning on using the PString library for a similar use in the UI code, but IIRC we had problems with that. So if the above works, we'll need to do a PString test to get that working ... or find another method.



EDIT : Give the above a try but I'm not hopeful. The more I look at the code, the more I think a "write" sends a single character. To send a array of chars, we'll need to come up with our own "print" function. That's not too hard though. :liar:

The above code would not compile. It gave the error "invalid conversion from ‘const char*’ to ‘uint8_t’ "

Replacing all of the “OLEDFourBit.write” with “OLEDFourBit.print” got it to work again.

Back to the target code. Here is a video of the latest code I had running by simply changing the library call to the new OLEDFourBit library.

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

#include <OLEDFourBit.h>

// initialize the library with the numbers of the interface pins
OLEDFourBit 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];
float A_Hit_Factor = 0;
float B_Hit_Factor = 0;
byte diMax = 99;                         // Display index maximum
volatile int secs = 0;
volatile int frac = 0;

byte StopHits = MaxHits;                  // Number if hits allowed before stopping
unsigned long StopTime = MaxTime;         // Time that the timer will stop at 
byte CoF = 0;                             // initialize the mode to be standard
//byte CoF = 1;                           // initialize the mode to be quick draw
//byte CoF = 2;                           // initialize the mode to be 21
//byte NumShooters = 1;                     // initialize the number of shooters to 1
byte NumShooters = 2;                   // initialize the number of shooters to 2


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(TargetAPin, INPUT);
  pinMode(TargetBPin, INPUT);
  digitalWrite(StartPin, 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();
      UpdateLCD();
    }
    
    // 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);
      
      // just for the moment send times to PC for debug
      SendTimes();
      
      // delay enough to debounce stop button
      delay(DB_delay);
      
      // Setup the review-stopped state display based on number shooters and hits
      if(NumShooters == 1)
      {
        diMax = byte(A_count / 8) - 1;   // DI goes 0 to number of displays - 1
        if (A_count % 8)                 // diMax indicates last screen of hits
        {
          diMax ++;                      // limit lines per number of hits
        }
        DisplayIndex = diMax;            // display will show list of hit times
      }
      else
      {
        diMax = byte(max(A_count,B_count)/4) - 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
      }
    }
  }
  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 = 0;         // wrap around of scrolling action
      }
      // change to new display
      UpdateLCD();
      delay(DB_delay);
    }
    //if (digitalRead(TargetAPin) == LOW)
    //{
      //DisplayIndex--;             // decrement DisplayIndex upon push
      //if (DisplayIndex < 0)
      //{
        //DisplayIndex = diMax;     // wrap around of scrolling action
      //}
      // change to new display
      //UpdateLCD();
      //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();
      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 will change when config mode is added ?
      switch (CoF)
      {
        case 1:
          //this is for quick draw mode
          StopTime = 10000;
          StopHits = 6;
          break;
        case 2:
          //this is for 21 mode
          StopTime = 1500;
          StopHits = MaxHits;
          break;
        default: 
          // the default = standard timer mode
          StopTime = MaxTime;
          StopHits = MaxHits;
      }
      
      // 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);      
      
      // save the starting time of this run
      StartTime = millis();
      
      // set state of timer to running
      TimerState = 1;
      
      // turn on interrupts as needed for 1 or 2 shooters
      attachInterrupt(0, ISR_A, FALLING);
      if (NumShooters > 1)
      {
        attachInterrupt(1, ISR_B, FALLING);
      }
      
      // 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 (byte 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 : ");
  byte i = 0;
  for (i=0; i < A_count; i++)
  {
    Serial.print(i+1);
    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++)
  {
    Serial.print(i+1);
    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 UpdateLCD()
{
AupDtFlag = 0;
BupDtFlag = 0;
// routine to format lines of data to be sent to LCD
// presently assume 4 lines of 20 characters
// formatting based number of shooters and timer state
  if(TimerState)
  {
  // timer is running
    // display number of hits so far
    lcd.clear();
    lcd.print(" A: # hits = ");
    if(A_count < 10)
    {
      lcd.print(' ');
    } 
    lcd.print(A_count);
    // now display the time of last hit
    lcd.setCursor(0, 1);
    lcd.print(" Hit  time = ");
    if(A_count)
    {
      TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
    }
    lcd.setCursor(0, 2);
    // Chose 1 or 2 shooter portion of display
    if(NumShooters == 1)
    {
      // this is for single A shooter running display
      // display the split time btw last hit and prior hit
      unsigned int Split = 0;
      if (A_count > 1)
      {
        Split = A_Times[A_count-1] - A_Times[A_count-2];
      }
      lcd.print("Split time = ");
      TimeConvert(Split); // convert split time
      lcd.setCursor(0, 3);
      // compute A hit factor
      //float HF = A_count/A_Times[A_count - 1];
      //lcd.print("Hit factor = ");
      //char buf[6];
      //PString(buf, sizeof(buf), HF);  // convert FP to char
      //lcd.print(buf);
    }      
    else
    {
      // this is for 2 shooter running display
      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[B_count-1]); // Now for B times
      }    
    }
  }
  else
  {
  // timer is stopped
    if(NumShooters == 1)
    {
      // this is single shooter review display
      lcd.clear();
      byte k = 0;                    // k will hold LCD line number
      byte j = DisplayIndex << 3;    // index for first hit time displayed
      byte jj = (A_count % 8);   // jj holds number hits on last display
      if (DisplayIndex == diMax)     // diMax indicates last screen of hits
      {
        jj = j + min(jj,4);          // limit lines according to number of hits
      }
      else
      {
        jj = j + 4;                  // there are enough hits to fill 4 lines
      }
      for (byte i = j; i < jj; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print('A');
        if(i<9)
        {
         lcd.print(' ');             // add a space for single digit numbers
        }
        lcd.print(i+1);              // send hit # to LCD
        lcd.print(": ");
        TimeConvert(A_Times[i]);     // convert hit time to XX.xx format
        if(A_Times[i+4])             // if there's a non-zero time
        {
          lcd.print(' ');            // print a space
          if((i+4)<9)
          {
           lcd.print(' ');           // add a space for single digit numbers
          }          
          lcd.print(i+5);            // send hit # to LCD
          lcd.print(": ");
          TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
        }
      }
    }      
    else
    {
      // this is 2 shooter review display
      lcd.clear();
      byte k = 0;                     // k will hold LCD line number
      byte j = DisplayIndex << 2;        // index for first hit time displayed
      byte jj = max(A_count,B_count); // init jj to max hit count
            if (DisplayIndex == diMax)     // diMax indicates last screen of hits
      {
        jj = j + min(jj,4);          // limit lines according to number of hits
      }
      else
      {
        jj = j + 4;                   // there are enough hits to fill 4 lines
      }
      for (byte i = j; i < jj; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        if(i<9);
        {
          lcd.print(' ');              // for hits 1-9 put in a leading space
        }
        lcd.print(i+1);               // print hit number
        lcd.print(" A: ");
        TimeConvert(A_Times[i]);      // convert hit time to XX.xx format
        if(B_Times[i]);
        {
          lcd.print(" B: ");
          TimeConvert(B_Times[i]);      // convert hit time to XX.xx format
        }
      }  
    }
  }
}


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
  }
}

OK back from the WE off. I see things are back to where we were, perhaps a lil better even ! Yea !! I see some line indexing problems and I know the cause of those. The code you’re running is 2 revs back from what I think you should be using, specifically the new OLED lcd.clear does NOT return the cursor to the “home” (0,0) position. I have code where that is rectified. I see the old problem of 2 shooter review mode adding an extra space on the double digit hits it still there and I don’t know why that’s happening. I’ll look again at that (right now) and see if I can post some new code with that and the other fixes (from your code above). I do note that we’re not seeing the time = 0 problem anymore or some of the other really wierd stuff that was happening just prior to the OLED LCD. Let’s hope that luck holds !

I really like that new LCD for it’s clarity. How readable in direct sunlight is it ? I may just have to use that model in my project.

OK, I believe I knew what that spacing problem was back when and fixed it in the new code, There was an extraneous ; where the if (i>9) asked about the hit number and whether to add a space or not. That was fixed in the latest pre-OLED code.

So the one thing I see in thte vid that I’ve not seen before is the hit number badness. In the code you were running the max # of hits was set to 20. You got 21 hits. As a result the display showed garbage times for hits 22-24. I’m not 100% sure why you were able to accumulate 21 hits since the question asked by the code is if the hits are >= the stop amount. I can only GUESS that the last hits came in so fast that by the time the code got around to asking that question, hit # 21 had already been counted. Now I don’t think the split times for those last few hits were outrageously low. Perhaps faster than a single SA shooter could accurately shoot but not so fast the timer should choke on them. My GUESS is that the display update is taking a longer time than before and the ISRs, that count hits, don’t get shut off in time. I’ll propose an experiment to see if that’s the case below with the new code. If my guess turns out to be correct then it’s fixable by adding some code to the ISRs, but will complicate some other things. We could try to speed up the display updating, I know the new OLED library uses the BFGuys max busy time of 5 msecs. I though I read the spec to be 2 msecs. A contributing cause might be because you had the 2 hit sense lines tied to the same single hit detector. But this is close to the real life worst case so an A hit followed very very very closely by a B hit is a real possibility that the timer must be able to handle.

Here’s my 1’st cut at an OLED compatible timer build of software. It has all the problems I know about fixed (with the exception of the “too many hits” above). It’s more optimized than the build you were using in that it doesn’t calculate and store split times to RAM but rather only as needed “on the fly” when/as they are to be sent to the LCD display.

#include <OLEDFourBit.h>

// initialize the library with the numbers of the interface pins
// with the OLED library this also seems to do the init and begin calls
OLEDFourBit 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

// initialize the constants
const unsigned long MaxTime = 60001;     // the max time the timer can run for = 60 secs
const unsigned long WaitTime = 1000;     // wait time btw start and timer running = 1 sec 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 = 24;                 // Maximum number of hits allowed per shooter

// 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
volatile int secs = 0;                   // whole seconds of time for display
volatile int frac = 0;                   // fractional part of time for display
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 unsigned long A_Times[MaxHits]; // array to hold up to MaxHits hit times for target A
volatile unsigned long B_Times[MaxHits]; // array to hold up to MaxHits hit times for target B
byte DisplayIndex = 0;                   // variable for controlling what's displayed
byte diMax = 99;                         // Display index maximum

byte StopHits = MaxHits;                 // Number if hits allowed before stopping
unsigned long StopTime = MaxTime;        // Time that the timer will stop at 
byte CoF = 0;                            // initialize the mode to be standard
//byte CoF = 1;                          // initialize the mode to be quick draw
//byte CoF = 2;                          // initialize the mode to be 21
byte NumShooters = 1;                    // initialize the number of shooters to 1
//byte NumShooters = 2;                  // initialize the number of shooters to 2
byte RanDly = 0;                         // random delay disabled
//byte RanDly = 1;                       // random delay enabled

void setup()
{
  lcd.clear();
  lcd.setCursor(0, 0);
  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(TargetAPin, INPUT);
  pinMode(TargetBPin, INPUT);
  digitalWrite(StartPin, 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       ");

  ClearData();
}


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)
    {
      UpdateLCD();
    }
    
    // timer is running so now check for any stop conditions
    if ((digitalRead(StartPin) == LOW) || ((millis() - StartTime) >= StopTime) || A_count >= StopHits || B_count >= StopHits)
    {
      // update the LCD running display one last time
      UpdateLCD();
      
      // stop the timer
      StopTimer();
      tone(BuzzerPin, FreqLo, BuzzTime10);
      
      // just for the moment send times to PC for debug
      SendTimes();
      
      // delay enough to debounce stop button
      delay(DB_delay);
      
      // Setup the review-stopped state display based on number shooters and hits
      if(NumShooters == 1)
      {
        diMax = byte(A_count/8) - 1;   // DI goes 0 to number of displays - 1
        if (A_count % 8)                 // diMax indicates last screen of hits
        {
          diMax ++;                      // limit lines per number of hits
        }
        DisplayIndex = diMax;            // display will show list of hit times
      }
      else
      {
        diMax = byte(max(A_count,B_count)/4) - 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
      }
    }
  }
  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 = 0;         // wrap around of scrolling action
      }
      // update display
      UpdateLCD();
      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);
      
      // show the timer has been started
      lcd.clear();
      lcd.setCursor(0, 0);
      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 will change when config mode is added ?
      switch (CoF)
      {
        case 1:
          //this is for quick draw mode
          StopTime = 10001;
          StopHits = 6;
          break;
        case 2:
          //this is for 21 mode
          StopTime = 1501;
          StopHits = MaxHits;
          break;
        default: 
          // the default = standard timer mode
          StopTime = MaxTime;
          StopHits = MaxHits;
      }
      
      // delay the Wait Time from start button push
      randomSeed(millis());
      // delay is min delay plus random if enabled
      delay(WaitTime);               // 1 sec 
      if(RanDly)
      {
        delay(random(2000, 4000));   // total delay btw 3 and 5 secs
      }
      
      // save the starting time of this run
      StartTime = millis();
      
      // set state of timer to running
      TimerState = 1;
      
      // turn on interrupts as needed for 1 or 2 shooters
      attachInterrupt(0, ISR_A, FALLING);
      if (NumShooters > 1)
      {
        attachInterrupt(1, ISR_B, FALLING);
      }
      
      // clear all the prior runs data
      ClearData();      

      // 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 (byte i = 0; i < MaxHits; i++)
  {
    A_Times[i] = 0;
    B_Times[i] = 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 : ");
  byte i = 0;
  unsigned long Split = 0;
  for (i = 0; i < A_count; i++)
  {
    Serial.print(i+1);
    Serial.print("\t");
    Serial.print(A_Times[i]);
    Serial.print("\t");
    if (i)
    {
      Split = A_Times[i] - A_Times[i-1];
      Serial.print(Split);
    }
    Serial.println("\t");
  }
  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++)
  {
    Serial.print(i+1);
    Serial.print("\t");
    Serial.print(B_Times[i]);
    Serial.print("\t");
    if (i)
    {
      Split = B_Times[i] - B_Times[i-1];
      Serial.print(Split);
    }
    Serial.println("\t");
  }
}


void ISR_A()
{
  if(TimerState)
  {
    // store the hit time 
    A_Times[A_count] = millis() - StartTime;
    // increment the hit count and array index
    ++A_count;
    // set the Hit flag so other stuff will update
    AupDtFlag = 1;
  }
}


void ISR_B()
{
  if(TimerState)
  {
    // store the hit time 
    B_Times[B_count] = millis() - StartTime;
    // increment the hit count and array index
    ++B_count;
    // set the Hit flag so other stuff will update
    BupDtFlag = 1;
  }
}


void UpdateLCD()
{
AupDtFlag = 0;
BupDtFlag = 0;
// routine to format lines of data to be sent to LCD
// presently assume 4 lines of 20 characters
// formatting based number of shooters and timer state
  if(TimerState)
  {
  // timer is running
    // display number of hits so far
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print(" A: # hits = ");
    if(A_count < 10)
    {
      lcd.print(' ');
    } 
    lcd.print(A_count);
    // now display the time of last hit
    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);
    // Chose 1 or 2 shooter portion of display
    if(NumShooters == 1)
    {
      // this is for single A shooter running display
      // display the split time btw last hit and prior hit
      unsigned long Split = 0;
      if (A_count > 1)
      {
        Split = A_Times[A_count-1] - A_Times[A_count-2];
      }
      lcd.print("Split time = ");
      TimeConvert(Split); // convert split time
      lcd.setCursor(0, 3);
      // compute A hit factor
      //float HF = A_count/A_Times[A_count - 1];
      //lcd.print("Hit factor = ");
      //char buf[6];
      //PString(buf, sizeof(buf), HF);  // convert FP to char
      //lcd.print(buf);
    }      
    else
    {
      // this is for 2 shooter running display
      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[B_count-1]); // Now for B times
      }
    }    
  }
  else
  {
  // timer is stopped
    if(NumShooters == 1)
    {
      // this is single shooter review display
      lcd.clear();
      lcd.setCursor(0, 0);
      byte k = 0;                    // k will hold LCD line number
      byte j = DisplayIndex << 3;    // index for first hit time displayed
      byte jj = (A_count % 8);   // jj holds number hits on last display
      if (DisplayIndex == diMax)     // diMax indicates last screen of hits
      {
        jj = j + min(jj,4);          // limit lines according to number of hits
      }
      else
      {
        jj = j + 4;                  // there are enough hits to fill 4 lines
      }
      for (byte i = j; i < jj; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print('A');
        if(i<9)
        {
         lcd.print(' ');             // add a space for single digit numbers
        }
        lcd.print(i+1);              // send hit # to LCD
        lcd.print(": ");
        TimeConvert(A_Times[i]);     // convert hit time to XX.xx format
        if(A_Times[i+4])             // if there's a non-zero time
        {
          lcd.print(' ');            // print a space
          if((i+4)<9)
          {
           lcd.print(' ');           // add a space for single digit numbers
          }          
          lcd.print(i+5);            // send hit # to LCD
          lcd.print(": ");
          TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
        }
      }
    }      
    else
    {
      // this is 2 shooter review display
      lcd.clear();
      lcd.setCursor(0, 0);
      byte k = 0;                     // k will hold LCD line number
      byte j = DisplayIndex << 2;     // index for first hit time displayed
      byte jj = max(A_count,B_count); // init jj to max hit count
      if (DisplayIndex == diMax)      // diMax indicates last screen of hits
      {
        jj = j + (jj % 4);            // limit lines per number of hits
      }
      else
      {
        jj = j + 4;                   // there are enough hits to fill 4 lines
      }
      for (byte i = j; i < jj; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        if(i<9)
        {
          lcd.print(' ');              // for hits 1-9 put in a leading space
        }
        lcd.print(i+1);               // print hit number
        lcd.print(" A: ");
        TimeConvert(A_Times[i]);      // convert hit time to XX.xx format
        if(B_Times[i]);
        {
          lcd.print(" B: ");
          TimeConvert(B_Times[i]);      // convert hit time to XX.xx format
        }
      }  
    }
  }
}


void TimeConvert(unsigned 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
  }
}

The experiment I’d like you to try for the “too many hits” problem is simple. Make a trial run doing as you did before, banging on the hit detector like a monkey on crack throws poo. The hit limit in “normal” mode is now 24 hits. See if you get more than 24 hits recorded. Then for trial run #2, slow the hitting when you see hit #20 displayed. Take aout 1 sec between hits after #20 and see if the timer stops at hit #24 or at #25. My prediction is #24.

BTW the above build also has a random or fixed delay added in. Look at the constants section and you’ll see where to set the delay (RanDly) to enabled or disabled. If the prior display oddities are corrected then also try single shooter mode and perhaps your CoFs.

BTW if you looked at the vid carefully you’d see that the 1’st B time was actually the last hit time. My theory on this is that it’s a result of the “too many hits” problem. Both the A and B hits are stored in an array, initialized with zeros out to the max number of hits. When the A number of hits overran it’s limit, I think the last A hit time got put in the 1’st B hit time memory location, overwriting any number put there earlier in the run. So fixing the “too many hits” should also cure this.

As for that fix we can try to speed up the display processing but who knows if that’ll ever be guaranteed to be quick enough for the worst case min time between hits. We can restrict the updates to the LCD to occur no more quickly that some “long” time period, say 0.4 secs or so. That way the code in the loop checking for hits >= stop amount will have time to run and do it’s thing. The hits will still be recorded, just not displayed immediately if they occur too soon after the prior hit.

Or we can add an if(blahblah) in the ISRs to not count and not store the time of any hits > stop amount. This last option would seem to be bulletproof. Hits can dribble in while the LCD is getting updated but won’t be recorded if they’re over the limit. ATM I can’t see any reason not to do this. It might also be wise to limit the updates so there’s no chance of befuddling the LCD controller with too much info to often, but if your recent test is any indication, that doesn’t seem to be a real problem.

Are you ready to wire the slidey 2 axis pot into A0 and A1 and work on the user interface ? (assuming the above code runs well)

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

Mee_n_Mac:
Here’s my 1’st cut at an OLED compatible timer build of software. It has all the problems I know about fixed (with the exception of the “too many hits” above). It’s more optimized than the build you were using in that it doesn’t calculate and store split times to RAM but rather only as needed “on the fly” when/as they are to be sent to the LCD display.

#include <OLEDFourBit.h>

// initialize the library with the numbers of the interface pins
// with the OLED library this also seems to do the init and begin calls
OLEDFourBit 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

// initialize the constants
const unsigned long MaxTime = 60001; // the max time the timer can run for = 60 secs
const unsigned long WaitTime = 1000; // wait time btw start and timer running = 1 sec 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 = 24; // Maximum number of hits allowed per shooter

// 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
volatile int secs = 0; // whole seconds of time for display
volatile int frac = 0; // fractional part of time for display
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 unsigned long A_Times[MaxHits]; // array to hold up to MaxHits hit times for target A
volatile unsigned long B_Times[MaxHits]; // array to hold up to MaxHits hit times for target B
byte DisplayIndex = 0; // variable for controlling what’s displayed
byte diMax = 99; // Display index maximum

byte StopHits = MaxHits; // Number if hits allowed before stopping
unsigned long StopTime = MaxTime; // Time that the timer will stop at
byte CoF = 0; // initialize the mode to be standard
//byte CoF = 1; // initialize the mode to be quick draw
//byte CoF = 2; // initialize the mode to be 21
byte NumShooters = 1; // initialize the number of shooters to 1
//byte NumShooters = 2; // initialize the number of shooters to 2
byte RanDly = 0; // random delay disabled
//byte RanDly = 1; // random delay enabled

void setup()
{
lcd.clear();
lcd.setCursor(0, 0);
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(TargetAPin, INPUT);
pinMode(TargetBPin, INPUT);
digitalWrite(StartPin, 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 ");

ClearData();
}

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)
{
  UpdateLCD();
}

// timer is running so now check for any stop conditions
if ((digitalRead(StartPin) == LOW) || ((millis() - StartTime) >= StopTime) || A_count >= StopHits || B_count >= StopHits)
{
  // update the LCD running display one last time
  UpdateLCD();
  
  // stop the timer
  StopTimer();
  tone(BuzzerPin, FreqLo, BuzzTime10);
  
  // just for the moment send times to PC for debug
  SendTimes();
  
  // delay enough to debounce stop button
  delay(DB_delay);
  
  // Setup the review-stopped state display based on number shooters and hits
  if(NumShooters == 1)
  {
    diMax = byte(A_count/8) - 1;   // DI goes 0 to number of displays - 1
    if (A_count % 8)                 // diMax indicates last screen of hits
    {
      diMax ++;                      // limit lines per number of hits
    }
    DisplayIndex = diMax;            // display will show list of hit times
  }
  else
  {
    diMax = byte(max(A_count,B_count)/4) - 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
  }
}

}
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 = 0;         // wrap around of scrolling action
  }
  // update display
  UpdateLCD();
  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);
  
  // show the timer has been started
  lcd.clear();
  lcd.setCursor(0, 0);
  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 will change when config mode is added ?
  switch (CoF)
  {
    case 1:
      //this is for quick draw mode
      StopTime = 10001;
      StopHits = 6;
      break;
    case 2:
      //this is for 21 mode
      StopTime = 1501;
      StopHits = MaxHits;
      break;
    default: 
      // the default = standard timer mode
      StopTime = MaxTime;
      StopHits = MaxHits;
  }
  
  // delay the Wait Time from start button push
  randomSeed(millis());
  // delay is min delay plus random if enabled
  delay(WaitTime);               // 1 sec 
  if(RanDly)
  {
    delay(random(2000, 4000));   // total delay btw 3 and 5 secs
  }
  
  // save the starting time of this run
  StartTime = millis();
  
  // set state of timer to running
  TimerState = 1;
  
  // turn on interrupts as needed for 1 or 2 shooters
  attachInterrupt(0, ISR_A, FALLING);
  if (NumShooters > 1)
  {
    attachInterrupt(1, ISR_B, FALLING);
  }
  
  // clear all the prior runs data
  ClearData();      

  // 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 (byte i = 0; i < MaxHits; i++)
{
A_Times[i] = 0;
B_Times[i] = 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 : “);
byte i = 0;
unsigned long Split = 0;
for (i = 0; i < A_count; i++)
{
Serial.print(i+1);
Serial.print(”\t”);
Serial.print(A_Times[i]);
Serial.print(“\t”);
if (i)
{
Split = A_Times[i] - A_Times[i-1];
Serial.print(Split);
}
Serial.println(“\t”);
}
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++)
{
Serial.print(i+1);
Serial.print(”\t”);
Serial.print(B_Times[i]);
Serial.print(“\t”);
if (i)
{
Split = B_Times[i] - B_Times[i-1];
Serial.print(Split);
}
Serial.println(“\t”);
}
}

void ISR_A()
{
if(TimerState)
{
// store the hit time
A_Times[A_count] = millis() - StartTime;
// increment the hit count and array index
++A_count;
// set the Hit flag so other stuff will update
AupDtFlag = 1;
}
}

void ISR_B()
{
if(TimerState)
{
// store the hit time
B_Times[B_count] = millis() - StartTime;
// increment the hit count and array index
++B_count;
// set the Hit flag so other stuff will update
BupDtFlag = 1;
}
}

void UpdateLCD()
{
AupDtFlag = 0;
BupDtFlag = 0;
// routine to format lines of data to be sent to LCD
// presently assume 4 lines of 20 characters
// formatting based number of shooters and timer state
if(TimerState)
{
// timer is running
// display number of hits so far
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" A: # hits = “);
if(A_count < 10)
{
lcd.print(’ ‘);
}
lcd.print(A_count);
// now display the time of last hit
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);
// Chose 1 or 2 shooter portion of display
if(NumShooters == 1)
{
// this is for single A shooter running display
// display the split time btw last hit and prior hit
unsigned long Split = 0;
if (A_count > 1)
{
Split = A_Times[A_count-1] - A_Times[A_count-2];
}
lcd.print("Split time = ");
TimeConvert(Split); // convert split time
lcd.setCursor(0, 3);
// compute A hit factor
//float HF = A_count/A_Times[A_count - 1];
//lcd.print("Hit factor = “);
//char buf[6];
//PString(buf, sizeof(buf), HF); // convert FP to char
//lcd.print(buf);
}
else
{
// this is for 2 shooter running display
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[B_count-1]); // Now for B times
}
}
}
else
{
// timer is stopped
if(NumShooters == 1)
{
// this is single shooter review display
lcd.clear();
lcd.setCursor(0, 0);
byte k = 0; // k will hold LCD line number
byte j = DisplayIndex << 3; // index for first hit time displayed
byte jj = (A_count % 8); // jj holds number hits on last display
if (DisplayIndex == diMax) // diMax indicates last screen of hits
{
jj = j + min(jj,4); // limit lines according to number of hits
}
else
{
jj = j + 4; // there are enough hits to fill 4 lines
}
for (byte i = j; i < jj; i++)
{
k = (i % 4);
lcd.setCursor(0,k);
lcd.print(‘A’);
if(i<9)
{
lcd.print(’ '); // add a space for single digit numbers
}
lcd.print(i+1); // send hit # to LCD
lcd.print(”: “);
TimeConvert(A_Times[i]); // convert hit time to XX.xx format
if(A_Times[i+4]) // if there’s a non-zero time
{
lcd.print(’ ‘); // print a space
if((i+4)<9)
{
lcd.print(’ '); // add a space for single digit numbers
}
lcd.print(i+5); // send hit # to LCD
lcd.print(”: “);
TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
}
}
}
else
{
// this is 2 shooter review display
lcd.clear();
lcd.setCursor(0, 0);
byte k = 0; // k will hold LCD line number
byte j = DisplayIndex << 2; // index for first hit time displayed
byte jj = max(A_count,B_count); // init jj to max hit count
if (DisplayIndex == diMax) // diMax indicates last screen of hits
{
jj = j + (jj % 4); // limit lines per number of hits
}
else
{
jj = j + 4; // there are enough hits to fill 4 lines
}
for (byte i = j; i < jj; i++)
{
k = (i % 4);
lcd.setCursor(0,k);
if(i<9)
{
lcd.print(’ '); // for hits 1-9 put in a leading space
}
lcd.print(i+1); // print hit number
lcd.print(” A: “);
TimeConvert(A_Times[i]); // convert hit time to XX.xx format
if(B_Times[i]);
{
lcd.print(” B: ");
TimeConvert(B_Times[i]); // convert hit time to XX.xx format
}
}
}
}
}

void TimeConvert(unsigned 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
}
}




The experiment I'd like you to try for the "too many hits" problem is simple. Make a trial run doing as you did before, banging on the hit detector like a monkey on crack throws poo. The hit limit in "normal" mode is now 24 hits. See if you get more than 24 hits recorded. Then for trial run #2, slow the hitting when you see hit #20 displayed. Take aout 1 sec between hits after #20 and see if the timer stops at hit #24 or at #25. My prediction is #24.



BTW the above build also has a random or fixed delay added in. Look at the constants section and you'll see where to set the delay (RanDly) to enabled or disabled. If the prior display oddities are corrected then also try single shooter mode and perhaps your CoFs.

I see a few odd things there. There’s the last blank screen issue but I can guess what’s causing that. A couple of times I saw a running display with 0 hits display and blank times. That’s not supposed to happen and 2 of the trials it didn’t. Not sure what’s causing that. There’s the too many hits problem and oddly I think I heard the buzzer sound after 24 and then you dropped the sensor and it still registered a hit. That’s definitely not supposed to happen and can’t be a result of the display taking a long time to update. So got some stuff to fix but for the most part it seems like things are back to working and I note the offsets on the single shooter display were correct. All the times are displayed correctly and the blanking code blanks those Axx: that don’t have any times (perhaps a tad too vigorously!). Try the dual shooter case and see if the times are now displayed properly there as well.

I found the problem with the blank screens when the number of hits would just exactly fill the last screen. So that’s fixed and I added the aforemention ISR fix for the too many hits. Right now I can only GUESS that the discrepancy between the last hit shown in the running display and the last one shown in the review display is due to a hit coming in after the max number has been reached but while the LCD is getting updated for the now-next-to-last hit. What the code does to keep the last hit running display shown after the timer “stops” (for any reason) is to hold off actually stopping the timer even though one of the stop conditions has been met. The code then calls for an update to the LCD and then actually stops the timer and shuts off the ISRs (which count the hits). So if another hit (#25) or two, come in while the LCD is being updated for #24, then you’ll have that discrepancy … with the prior code. I believe the fix for too many hits will also fix this.

What I’ve yet to figure out is why the running display showed 0 hits with blanks times (that last part is OK) on 2 occasions in your last vid. It should have waited for the 1’st A hit to come in before switching from the “Standby” display to the running display. Therefore in 1 shooter mode the very 1’st running display should always show a hit count of 1 and it’s time and a blank for the split time. And on the other 2 trials it did just that, just as it should. It’s almost if the B hit line caused the LCD update even though there was no A hit … but the B ISR is shut off when in 1 shooter mode ???

In any case the code below should fix 3 of the 4 issues noted and the last issue just looks odd, there’s no wrong data ever displayed or otherwise recorded (the review display looked fine).

#include <OLEDFourBit.h>

// initialize the library with the numbers of the interface pins
// with the OLED library this also seems to do the init and begin calls
OLEDFourBit 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

// initialize the constants
const unsigned long MaxTime = 60001;     // the max time the timer can run for = 60 secs
const unsigned long WaitTime = 1000;     // wait time btw start and timer running = 1 sec 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 = 24;                 // Maximum number of hits allowed per shooter

// 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
volatile int secs = 0;                   // whole seconds of time for display
volatile int frac = 0;                   // fractional part of time for display
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 unsigned long A_Times[MaxHits]; // array to hold up to MaxHits hit times for target A
volatile unsigned long B_Times[MaxHits]; // array to hold up to MaxHits hit times for target B
byte DisplayIndex = 0;                   // variable for controlling what's displayed
byte diMax = 99;                         // Display index maximum

byte StopHits = MaxHits;                 // Number if hits allowed before stopping
unsigned long StopTime = MaxTime;        // Time that the timer will stop at 
byte CoF = 0;                            // initialize the mode to be standard
//byte CoF = 1;                          // initialize the mode to be quick draw
//byte CoF = 2;                          // initialize the mode to be 21
byte NumShooters = 1;                    // initialize the number of shooters to 1
//byte NumShooters = 2;                  // initialize the number of shooters to 2
byte RanDly = 0;                         // random delay disabled
//byte RanDly = 1;                       // random delay enabled

void setup()
{
  lcd.clear();
  lcd.setCursor(0, 0);
  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(TargetAPin, INPUT);
  pinMode(TargetBPin, INPUT);
  digitalWrite(StartPin, 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       ");

  ClearData();
}


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)
    {
      UpdateLCD();
    }
    
    // timer is running so now check for any stop conditions
    if ((digitalRead(StartPin) == LOW) || ((millis() - StartTime) >= StopTime) || A_count >= StopHits || B_count >= StopHits)
    {
      // update the LCD running display one last time
      UpdateLCD();
      
      // stop the timer
      StopTimer();
      tone(BuzzerPin, FreqLo, BuzzTime10);
      
      // just for the moment send times to PC for debug
      SendTimes();
      
      // delay enough to debounce stop button
      delay(DB_delay);
      
      // Setup the review-stopped state display based on number shooters and hits
      if(NumShooters == 1)
      {
        diMax = byte(A_count/8) - 1;   // DI goes 0 to number of displays - 1
        if (A_count % 8)                 // diMax indicates last screen of hits
        {
          diMax ++;                      // limit lines per number of hits
        }
        DisplayIndex = diMax;            // display will show list of hit times
      }
      else
      {
        diMax = byte(max(A_count,B_count)/4) - 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
      }
    }
  }
  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 = 0;         // wrap around of scrolling action
      }
      // update display
      UpdateLCD();
      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);
      
      // show the timer has been started
      lcd.clear();
      lcd.setCursor(0, 0);
      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 will change when config mode is added ?
      switch (CoF)
      {
        case 1:
          //this is for quick draw mode
          StopTime = 10001;
          StopHits = 6;
          break;
        case 2:
          //this is for 21 mode
          StopTime = 1501;
          StopHits = MaxHits;
          break;
        default: 
          // the default = standard timer mode
          StopTime = MaxTime;
          StopHits = MaxHits;
      }
      
      // delay the Wait Time from start button push
      randomSeed(millis());
      // delay is min delay plus random if enabled
      delay(WaitTime);               // 1 sec min delay
      if(RanDly)
      {
        delay(random(2000, 4000));   // total delay btw 3 and 5 secs
      }
      
      // turn on interrupts as needed for 1 or 2 shooters
      attachInterrupt(0, ISR_A, FALLING);
      if (NumShooters > 1)
      {
        attachInterrupt(1, ISR_B, FALLING);
      }
      
      // clear all the prior runs data
      ClearData();      
      
      // 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 (byte i = 0; i < MaxHits; i++)
  {
    A_Times[i] = 0;
    B_Times[i] = 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 : ");
  byte i = 0;
  unsigned long Split = 0;
  for (i = 0; i < A_count; i++)
  {
    Serial.print(i+1);
    Serial.print("\t");
    Serial.print(A_Times[i]);
    Serial.print("\t");
    if (i)
    {
      Split = A_Times[i] - A_Times[i-1];
      Serial.print(Split);
    }
    Serial.println("\t");
  }
  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++)
  {
    Serial.print(i+1);
    Serial.print("\t");
    Serial.print(B_Times[i]);
    Serial.print("\t");
    if (i)
    {
      Split = B_Times[i] - B_Times[i-1];
      Serial.print(Split);
    }
    Serial.println("\t");
  }
}


void ISR_A()
{
  if(TimerState)
  {
    if(A_count < StopHits)
    {
      // store the hit time 
      A_Times[A_count] = millis() - StartTime;
      // increment the hit count and array index
      A_count++;
      // set the Hit flag so other stuff will update
      AupDtFlag = 1;
    }
  }
}


void ISR_B()
{
  if(TimerState)
  {
    if(B_count < StopHits)
    {
      // 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 UpdateLCD()
{
AupDtFlag = 0;
BupDtFlag = 0;
// routine to format lines of data to be sent to LCD
// presently assume 4 lines of 20 characters
// formatting based number of shooters and timer state
  if(TimerState)
  {
  // timer is running
    // display number of hits so far
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print(" A: # hits = ");
    if(A_count < 10)
    {
      lcd.print(' ');
    } 
    lcd.print(A_count);
    // now display the time of last hit
    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);
    // Chose 1 or 2 shooter portion of display
    if(NumShooters == 1)
    {
      // this is for single A shooter running display
      // display the split time btw last hit and prior hit
      unsigned long Split = 0;
      if (A_count > 1)
      {
        Split = A_Times[A_count-1] - A_Times[A_count-2];
      }
      lcd.print("Split time = ");
      TimeConvert(Split); // convert split time
      lcd.setCursor(0, 3);
      // compute A hit factor
      //float HF = A_count/A_Times[A_count - 1];
      //lcd.print("Hit factor = ");
      //char buf[6];
      //PString(buf, sizeof(buf), HF);  // convert FP to char
      //lcd.print(buf);
    }      
    else
    {
      // this is for 2 shooter running display
      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[B_count-1]); // Now for B times
      }
    }    
  }
  else
  {
  // timer is stopped
    if(NumShooters == 1)
    {
      // this is single shooter review display
      lcd.clear();
      lcd.setCursor(0, 0);
      byte k = 0;                    // k will hold LCD line number
      byte j = DisplayIndex << 3;    // index for first hit time displayed
      byte jj = (A_count % 8);       // jj holds number hits on last display
      if (jj == 0)
      {
        jj = 8;                      // acount for 8 hits on last screeen
      }
      if (DisplayIndex == diMax)     // diMax indicates last screen of hits
      {
        jj = j + min(jj,4);          // display 1 line per hit up to 4 lines
      }
      else
      {
        jj = j + 4;                  // there are enough hits to fill 4 lines
      }
      for (byte i = j; i < jj; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        lcd.print('A');
        if(i<9)
        {
         lcd.print(' ');             // add a space for single digit numbers
        }
        lcd.print(i+1);              // send hit # to LCD
        lcd.print(": ");
        TimeConvert(A_Times[i]);     // convert hit time to XX.xx format
        if(A_Times[i+4])             // if there's a non-zero time
        {
          lcd.print(' ');            // print a space
          if((i+4)<9)
          {
           lcd.print(' ');           // add a space for single digit numbers
          }          
          lcd.print(i+5);            // send hit # to LCD
          lcd.print(": ");
          TimeConvert(A_Times[i+4]); // convert hit time to XX.xx format
        }
      }
    }      
    else
    {
      // this is 2 shooter review display
      lcd.clear();
      lcd.setCursor(0, 0);
      byte k = 0;                     // k will hold LCD line number
      byte j = DisplayIndex << 2;     // index for first hit time displayed
      byte jj = max(A_count,B_count); // init jj to max hit count
      jj = jj % 4;                    // jj holds number hits on last display
      if (jj == 0)
      {
        jj = 4;                       // acount for 4 hits on last screeen
      }
      if (DisplayIndex == diMax)      // diMax indicates last screen of hits
      {
        jj = j + min(jj,4);           // display 1 line per hit up to 4 lines
      }
      else
      {
        jj = j + 4;                   // there are enough hits to fill 4 lines
      }
      for (byte i = j; i < jj; i++)
      {
        k = (i % 4);
        lcd.setCursor(0,k);
        if(i<9)
        {
          lcd.print(' ');               // for hits 1-9 put in a leading space
        }
        lcd.print(i+1);                 // print hit number
        lcd.print(" A: ");
        TimeConvert(A_Times[i]);        // convert hit time to XX.xx format
        if(B_Times[i]);
        {
          lcd.print(" B: ");
          TimeConvert(B_Times[i]);      // convert hit time to XX.xx format
        }
      }  
    }
  }
}


void TimeConvert(unsigned 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
  }
}

So do a “slow” trial as before and an crazed trial as before. There shouldn’t be any difference btw the two now and all the hits should be displayed in review mode, with no hits > than the stop limit for the CoF in use.

RE: the 1’st running display with a 0 hit number … I notice that both times it occured, it happened (near as I can tell) coincident with the start of the “running” buzzer. Now this could be some form of electrical interference but it would only have to be affecting the B input and not the A or the time would be near but not quite 0 (the buzzer starts after all the timer functions have started). Besides I think we ruled out the buzzer when we were investigating the 0 time problem (which I’m wondering if this is). The other possibility is that the enabling of the ISRs is causing them to trigger and run. But with the last build or 2 I’ve put back in the timer-is-running test before letting the ISR do much of anything. Presently the ISRs are enabled and then the timer state variable is changed (some usecs) after that. Even so the update flags, which would trigger the LCD update, are dependant on the timer running … so that path also doesn’t hunt as well.

So I don’t have an answer to that mystery right now but let me ask that you wait 1 sec after the buzzer before you whack the sensor for the 1’st time, just to make it clear as to what caused the 1’st running display. Also put the sensor on some pad so the buzzer vibrating the surface can’t be picked up by the sensor. They can be very sensitive.

Other than that, I have high expectations for the code above !

EDIT : Actually I do think I (now) have an answer for that mystery (0 hit count in the running display) ! I noticed that both times that it occured in the vid were after a run where the hit count over-ran the max amount. We saw with the prior code, in the 2 shooter mode, the A time over-ran into the B time memory space and so the 1’st B time was = the last A time. What I think is happening above is another version of this. The A or B count, or time(s), is over-running some other memory location, perhaps one of the update flag locations. Then in the next run, as soon as the timer state becomes “running” and the code asks if the LCD should be updated, the answer due to the corrupted memory location is “yes”. So the LCD gets the info available, which is 0 hits and no time, and displays it. It seems that a good run “resets” the corruption and you don’t see it happen after one of these runs. A good run in the vid is where there were exactly 24 hits and the timer stopped w/o recording #25. Of course a bug in the code prevented the last 8 hits from being displayed, and the result was a blank screen for them. So with that bug fixed and the fix to the ISRs to prevent the count > max allowed, I think this problem will also be gone.