Counting and Timing a shock sensor

It fixed it but there’s another issue on the same one and I think this is a bigger deal.

Same error for both versions.

sspbass:
It fixed it but there’s another issue on the same one and I think this is a bigger deal.

Same error for both versions.

Damm, another typo.

Can you see what it is ?

;

and a doh moment …

As in (it should be) …

String tmp2 = String(k);

I take it that you tried the simple ) fix and that failed as well ? According to what I can tell from …

http://arduino.cc/en/Reference/StringConstructor

… changing an int, k in this case, to a String type should be allowed. But you do have to use the command, String(), to do it.

For that matter the prior fix should have been the ) and the proper conversion syntax …

String tmp2 = String(int(((A_Times[A_count-1])+5)/10)); //round msecs into csecs, convert to string

Fix the ommissions and see what happens. I’d prefer the immediately above as the fix (and not re-using k) it’s cleaner and simpler IMO.

So the revised function would be …

void FormatData()
{
// routine to format lines of data to be sent to LCD
// presently assume 2 lines of 16 characters
// switch formatting done based on display state
  switch (DisplayState)
  {
    case 1:{
      //this is for single A shooter mode
      // display number of hits so far
      String tmp = "A: # hits = ";
      Line1 = tmp + A_count;
      // now display the time of last hit in secs out to hundreths of secs
      String tmp2 = String(int(((A_Times[A_count-1])+5)/10)); // round msecs into csecs, convert to string
      int k = tmp2.length();  // k will be index to last char in padded string
      int km1 = k-1;          // km1 will be index to next to last char
      int km2 = k-2;          // km2 will be position of period
      tmp2 = tmp2 + '0';  // pad the end of the truncated string with a zero
      //now move chars to make space to add a period
      tmp2.setCharAt(k, tmp2.charAt(km1));   // move next-2-last to last position in string
      tmp2.setCharAt(km1, tmp2.charAt(km2)); // move char to next-2-last position
      // now insert period
      tmp2.setCharAt(km2, '.');
      // tmp2 now holds rounded time in secs XX.xx format
      Line2 = "Hit time = " + tmp2 ;
      break;}
    case 2:{
      // this is for A vs B mode
      // put that display code here
      break;
    default: 
      // do the default =0 for now
      // 2 string objects for lines 1 and 2
      Line1 = "Arduino timer v1";
      Line2 = "says Hello World";}
  }
}

sspbass:
ETA: I am also attaching a synopsis for what I had envisioned for modes. I’m not 100% sure how to handle shooters using multiple targets. For now this is probably sufficient.

In looking at the above I notice that “Quick Draw” and “First to x hits” are mostly the same (the QD has an additional time out) and “21 Rule” (a Bill Drill of sorts) and “Most hits in x secs” are also mostly the same. Is it your intent to have a few specialzed cases of some more general modes (as in the user input(s) are already specified) just so it’s easier to pick and use these special cases ? Also in “Quick Draw” is it your intent to have each target individually identified as being hit (ie - the timer knows which target was hit or not) or am I reading too much into that ? As I posted previously I think that can be done by varying, and measuring, the pulsewidths of each target. But doing so within the simple Arduino language and given the present usage of timers for tone() and millis() and such, it might be tough. I think the timer will need to have 2 free timers, one for A targets and one for B, to do that. That might be something to hold off on ATM.

Also 1-4 shooters will have to be 1-2 shooters or teams of shooters as the timer only has separate A and B channels.

Lastly I assume you want a capability, when the timer is not running, to scroll through and review all the stored times from a run.

I just thought they would be some fun modes that would be quick to access. For all intents and purposes there is really only two modes.

How long to make X hits and How many hits in X seconds. My thoughts on Quick Draw were that each shooter would be shooting at one target and the arduino would simply now which interrupt was triggered just like normal.

As to the 1-4 shooters I could perhaps get the Arduino Mega or what about this http://www.me.ucsb.edu/~me170c/Code/How … NY_pin.pdf

As to scrolling through data that would be great. Could it be setup such that after each round it displayed the basic data and then by pushing one of the up or down buttons it could scroll to another page with data on all of the hits?

sspbass:
I just thought they would be some fun modes that would be quick to access. For all intents and purposes there is really only two modes. How long to make X hits and How many hits in X seconds. My thoughts on Quick Draw were that each shooter would be shooting at one target and the arduino would simply now which interrupt was triggered just like normal.

Understood. The above would be an A vs B situation. Having presets that are chosen by choosing a "timer mode" is easy enough and makes sense.

sspbass:
As to the 1-4 shooters I could perhaps get the Arduino Mega or what about this http://www.me.ucsb.edu/~me170c/Code/How … NY_pin.pdf

Sure, they are ways to make more than A and B channels. You just need more pins or more circuitry.

sspbass:
As to scrolling through data that would be great. Could it be setup such that after each round it displayed the basic data and then by pushing one of the up or down buttons it could scroll to another page with data on all of the hits?

I don't know that ALL the data can be displayed on a 2 or 4 line LCD but you can certainly scroll through some number of displays to review all the data. This is how mine works.

And you aren’t limited to just the LCD. You can always tie into a smartphone via Bluetooth, connect to a PC via USB or if you wanted to you could print to a thermal printer and have immediate output.

A friend lent me a 4x20 parallel display to play with so we’ve got a little more real-estate to play with.

sspbass:
A friend lent me a 4x20 parallel display to play with so we’ve got a little more real-estate to play with.

So should future efforts be geared towards 4x20 ?

If so what data would you want on it ? Let’s start simple and have a single shooter. What would be displayed after each shot ? Would this same display be used during the review of the string ?

I’d think you’d want :

  • shot X of Y

  • cumlative time of hit (total time elapsed since string started)

  • split time (time since last hit)

  • hit factor ?? or something else ???

For an A vs B mode you might want something different, perhaps A vs B splits, and including an “A wins” or “B wins” depending on the mode.

dkulinski:
And you aren’t limited to just the LCD. You can always tie into a smartphone via Bluetooth, connect to a PC via USB or if you wanted to you could print to a thermal printer and have immediate output.

All depends on how much time and $$s the OP wants to spend. If I may paraphrase myself from some posts ago …

So how fast do you want to go ? :mrgreen:

On a different note : what do you know about the micros() function ? Can that be used concurrently with the millis() function and the tone() and ?? What I’m trying to determine is if a PW measurement can be done w/o departing from the standard Arduino function calls. To that end my thinking was (as far as it’s gone) that when the hit ISR runs it also stores the time in micros. Then it changes it’s trigger condition to be from the falling edge to the rising edge**. That way the ISR will run again when the pulse is done. Again it stores the time in micros() and changes the state of the ISR trigger. It then computes the PW via the difference in micros. This can be used to determine what target the hit occured on.

** I don’t know that the Arduino language will allow this change in “mid-stream”. I’m not sure but setting the ISR mode in the initial AttachInterrupt() function to “change” might accomplish the same thing, except that this is even easier to do.

I threw this together for the displays in excel.

What do you think?

sspbass:
I threw this together for the displays in excel.

What do you think?

Looks good. What would expect to have for the max number of hits ? I assume more than 8 and so a scroll would be needed to review the next set of 8 and so on and so forth for the next N sets of 8.

There’s a limitation on how many bytes can be stored in RAM and that will limit how many hits can be stored. There’s a fixed number of bytes that will be used to run the timer. Then there’s a variable number of bytes that will grow proportionally to the number of hits. Right now I have stored the time of each hit, the split from the prior hit and, potentially, the split time from A to B for each same numbered hit (ie - A hit #4 vs B hit #4). Each of these times takes 4 bytes, for a total of 20 bytes for each A / B hit. If you allocate 1 K of RAM for this (leaving 1 K for the fixed amount needed), that would be about 51 A hits plus 51 B hits max. Or 128 hits (there’d be no A-B splits) in single shooter mode. Now it occurs to me that I really only need to store the hit times. I could calculate the splits as needed, only as/when they are to be displayed. That would double the number of hits that could be stored before having to stop the timer. And perhaps more than 1 K could be used. But my thoughts are that 50 hits for A and 50 for B would be more than enough (perhaps limit the single shooter mode to 99 max so as to keep the display to 2 digits).

Thoughts ??

BTW do you know how much flash and how much RAM the project is presently consuming ?

50 hits each seems like more than enough. I’d imagine that in most cases it would be 10 or less.

How do I find out how much flash and RAM it’s consuming.

I can’t recall which number is displayed when the sketch is sent but it’s using approx 1/3 of total capacity.

sspbass:
How do I find out how much flash and RAM it’s consuming.

Got me.

Tadaa

http://www.youtube.com/watch?v=xJGHzRDvung

your 1st fix worked.

Quirks I’ve noticed. When freshly uploaded or reset, it takes two button pushes to get it to start.

The display won’t change when hit only once. It takes to strikes for the lcd to display any hits/times.

Here is the code I’m running now.

#include <LiquidCrystal.h>

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


// set pin numbers
const int StartPin = 7;       // the number of the Start pushbutton pin
const int TargetAPin = 2;     // the number of the A targets input pin
const int TargetBPin = 3;     // the number of the B targets input pin
const int BuzzerPin = 10;     // the number of the buzzer output pin
const int LEDPin = 13;        // the number of the LED output pin

// 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 unsigned int MaxHits = 20;         // Maximum number if hits allowed
const int RUN = 1;                       // make code easy to understand

// initialize global variables
volatile int TimerState = 0;   // variable for state of timer, running or not running
int DisplayState = 0;          // variable for controlling what's displayed
int UpDtFlag = 0;               // variable indication a hit has occurred
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 int A_count = 0;      // variable to hold the number of A hits
volatile int B_count = 0;      // variable to hold the number of B hits
volatile long A_Times[MaxHits];// array to hold up to 10 hit times for target A
volatile long B_Times[MaxHits];// array to hold up to 10 hit times for target B
long A_splits [MaxHits];
long B_splits [MaxHits];
long AB_splits [MaxHits];
int A_Hit_Factor = 0;
int B_Hit_Factor = 0;

// setup and intialize two strings for the display each 16 chars long
String Line1 = "Shot Timer v1";
String Line2 = "Test Line 2";


void setup()
{
  // set up the LCD's number of columns and rows: 
  lcd.begin(16, 2);
  lcd.clear();
  lcd.print("Shot Timer 1");
  delay(5000);

  // initialize output pins
  pinMode(BuzzerPin, OUTPUT);
  pinMode(LEDPin, OUTPUT);
  digitalWrite(BuzzerPin, LOW);
  digitalWrite(LEDPin, LOW);
   
  // initialize the input pins with internal pullups
  pinMode(StartPin, INPUT);
  //pinMode(UpPin, INPUT);
  //pinMode(DownPin, INPUT);
  pinMode(TargetAPin, INPUT);
  pinMode(TargetBPin, INPUT);
  digitalWrite(StartPin, HIGH);   
  //digitalWrite(UpPin, HIGH);
  //digitalWrite(DownPin, HIGH);
  digitalWrite(TargetAPin, HIGH);
  digitalWrite(TargetBPin, HIGH);

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


void loop()
{
  if (digitalRead(StartPin) == LOW) 
  {     
    // start stop button has been pushed, is timer running
    if (TimerState == !RUN)
    {
      // timer is not running so start it

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

      // delay the Wait Time from start button push
      // this delay is for debounce purposes and should stay
      delay(DB_delay);
      // this delay will change to random later on    
      delay(WaitTime);

      // enable the interrupts just in case
      interrupts();

      // save the starting time of this run
      StartTime = millis();
     
      // set state of timer to running
      TimerState = RUN;

      // now buzz the speaker for 0.5 secs
      tone(BuzzerPin, FreqHi, BuzzTime5);
    }
    else
    {
      // start stop button has been pushed again to stop timer
      StopTimer();
      tone(BuzzerPin, FreqLo, BuzzTime10);
      // delay enough to debounce stop button
      delay(DB_delay);

      // just for the moment send times to PC
      SendTimes();
    }
  }
  // send data to display if there have been any hits
  if (UpDtFlag == 1)
  {
    CalcTimes();
    FormatData();
    LCDdisplay();
    // this is goofy but for now change display state here
    DisplayState = 1;
  }

  //Check for max time out if timer is running
  if (TimerState == RUN)
  { 
    if ((millis() - StartTime) > MaxTime)
    {
    // call the function that does all the things needed to stop
    StopTimer();
    tone(BuzzerPin, FreqLo, BuzzTime10);

    // just for the moment send times to PC
    SendTimes();
    }
  }
}


void StopTimer()
{
  //noInterrupts();
  TimerState = !RUN;
  // turn the LED off to show timer is stopped
  digitalWrite(LEDPin, LOW);
}


void ClearData()
{
  A_count = 0; 
  B_count = 0;
  for (int i=0; i < MaxHits ; i++)
  {
    A_Times[i] = 0;
    B_Times[i] = 0;
    A_splits[i] = 0;
    B_splits[i] = 0;
    AB_splits[i] = 0;
  }
  A_Hit_Factor = 0;
  B_Hit_Factor = 0;
  DisplayState = 0;
}


void SendTimes()
// temp routine to send data to serial monitor
{
  Serial.println("Timer is stopped");
  Serial.println("Here are the times for Shooter A");
  Serial.print("Number of hits : ");
  Serial.print("\t");
  Serial.println(A_count);
  Serial.println("A Hit and split times are : ");
  int i = 0;
  int k = 0;
  for (i=0; i < A_count ; i++)
  {
    k = i + 1;
    //Serial.print(A_count[k]);
    Serial.print("\t");
    Serial.print(A_Times[i]);
    Serial.print("\t");
    Serial.println(A_splits[i]);
  }
  Serial.println("Here are the times for Shooter B");
  Serial.print("Number of hits : ");
  Serial.print("\t");
  Serial.println(B_count);
  Serial.println("B Hit and split times are : ");
  for (i=0; i < B_count ; i++)
  {
    k = i + 1;
    //Serial.print(B_count[k]);
    Serial.print("\t");
    Serial.print(B_Times[i]);
    Serial.print("\t");
    Serial.println(B_splits[i]);
  }
}


void ISR_A()
{
  if(TimerState == RUN)
  {
    // 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
    UpDtFlag = 1;
  }
}


void ISR_B()
{
  if(TimerState == RUN)
  {
    // 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
    UpDtFlag = 1;
  }
}


void CalcTimes()
{
// routine to calculate all data and declare winner
// not all calcs having meaning for uses of timer
  // calculate A splits and cumlative hit factor
  if (A_count > 1)
  {
    for (int i=1; i < A_count ; i++)
    {
      A_splits[i] = A_Times[i] - A_Times[i-1];
    }
  }
  else
  {
    A_splits[0] = A_Times[0];
  }  
  A_Hit_Factor = A_Times[A_count - 1]/A_count;
  // calculate B splits and cumlative hit factor
  if (B_count > 1)
  {
    for (int i=1; i < B_count ; i++)
    {
      B_splits[i] = B_Times[i] - B_Times[i-1];
    }
  }
  else
  {
    B_splits[0] = B_Times[0];
  }  
  B_Hit_Factor = B_Times[B_count - 1]/B_count;
  // Calculate A - B times just in case
  int Min_count = min(A_count, B_count);
  for (int i=0; i < Min_count ; i++)
    {
      AB_splits[i] = A_Times[i] - B_Times[i];
    }
// add more here for A vs B modes

}


void FormatData()
{
// routine to format lines of data to be sent to LCD
// presently assume 2 lines of 16 characters
// switch formatting done based on display state
  switch (DisplayState)
  {
    case 1:{
      //this is for single A shooter mode
      // display number of hits so far
      String tmp = "A: # hits = ";
      Line1 = tmp + A_count;
      // now display the time of last hit in secs out to hundreths of secs
      int k = int(((A_Times[A_count-1])+5)/10); //  round msecs into csecs
      String tmp2 = String(k);     // convert result above into a String
      k = tmp2.length();  // k will be index to last char in padded string
      int km1 = k-1;         // km1 will be index to next to last char
      int km2 = k-2;          // km2 will be position of period
      tmp2 = tmp2 + '0';   // pad the end of the truncated string with a zero
      //now move chars to make space to add a period
      tmp2.setCharAt(k, tmp2.charAt(km1));   // move next-2-last to last position in string
      tmp2.setCharAt(km1, tmp2.charAt(km2)); // move char to next-2-last position
      // now insert period
      tmp2.setCharAt(km2, '.');
      // tmp2 now holds rounded time in secs XX.xx format
      Line2 = "Hit time = " + tmp2 ;
      break;}
    case 2:{
      // this is for A vs B mode
      // put that display code here
      break;
    default: 
      // do the default =0 for now
      // 2 string objects for lines 1 and 2
      Line1 = "Arduino timer v1";
      Line2 = "says Hello World";}
  }
}

void LCDdisplay()
{
  // sends 2 lines to LCD using library functions
  lcd.clear();
  lcd.print(Line1);
  lcd.setCursor(0, 1);
  lcd.print(Line2);
  // reset the display update flag
  UpDtFlag = 0;
}

sspbass:
Tadaa

http://www.youtube.com/watch?v=xJGHzRDvung

OK ! Let toss something of a monkey wrench in. Looking at the code and thinking a bit about what has to come next, it appeared to me that it was organized wrong … or at least in a hard to understand (and maintain and change) way. So I changed it around a bit and made it easy to see what happens when the timer is running and when it’s not running. I added just a tiny bit of functionality but I’d like you to see if this re-ordered code still works like the version above.

I fixed some mistakes and made a few optimazation in the variable and constant delcarations. I added the functionality that the timer should stop when the max hit count is reached (it didn’t before). So if you could test this out and see that it still works it would be good. See that the LED comes on and goes off when it should, that the buzzer buzzes like it should and the display works as it did. Also so that it still stops after 30 secs and that it now stops if you get to 20 hits. Then it can go to the next level.

#include <LiquidCrystal.h>

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


// set pin numbers
const int StartPin = 7;       // the number of the Start pushbutton pin
const int TargetAPin = 2;     // the number of the A targets input pin
const int TargetBPin = 3;     // the number of the B targets input pin
const int BuzzerPin = 10;     // the number of the buzzer output pin
const int LEDPin = 13;        // the number of the LED output pin

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

// initialize global variables
volatile byte TimerState = 0;  // variable for state of timer, running or not running
volatile byte AupDtFlag = 0;   // variable indication an A hit has occurred
volatile byte BupDtFlag = 0;   // variable indication a B hit has occurred
byte DisplayState = 0;          // variable for controlling what's displayed
unsigned long StartTime = 0;   // variable to hold the start time
unsigned long BuzzTime = 500;  // variable to hold the buzzer on time
unsigned int Freq = 2000;      // variable for high or low buzzer tone
volatile byte A_count = 0;      // variable to hold the number of A hits
volatile byte B_count = 0;      // variable to hold the number of B hits
volatile long A_Times[MaxHits];// array to hold up to 10 hit times for target A
volatile long B_Times[MaxHits];// array to hold up to 10 hit times for target B
long A_splits [MaxHits];
long B_splits [MaxHits];
long AB_splits [MaxHits];
float A_Hit_Factor = 0;
float B_Hit_Factor = 0;

// setup and intialize two strings for the display each 16 chars long
String Line1 = "  Shot Timer v1 ";
String Line2 = "   is running   ";


void setup()
{
  // set up the LCD's number of columns and rows: 
  lcd.begin(16, 2);
  lcd.clear();
  lcd.print("  Shot Timer 1  ");
  lcd.setCursor(0, 1);
  lcd.print("  Initializing  ");
  delay(5000);

  // initialize output pins
  pinMode(BuzzerPin, OUTPUT);
  pinMode(LEDPin, OUTPUT);
  digitalWrite(BuzzerPin, LOW);
  digitalWrite(LEDPin, LOW);
   
  // initialize the input pins with internal pullups
  pinMode(StartPin, INPUT);
  pinMode(UpPin, INPUT);
  pinMode(DownPin, INPUT);
  pinMode(TargetAPin, INPUT);
  pinMode(TargetBPin, INPUT);
  digitalWrite(StartPin, HIGH);   
  digitalWrite(UpPin, HIGH);
  digitalWrite(DownPin, HIGH);
  digitalWrite(TargetAPin, HIGH);
  digitalWrite(TargetBPin, HIGH);

  // 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 == RUN)
  {
    // check to see if this is first pass since start push
    // this is goofy and will change when modes are coded
    if (DisplayState == 0)
    {
      DisplayState = 1; // display will be set depending on mode selected
    }
    // send data to display if there have been any hits
    if (AupDtFlag == 1 || BupDtFlag == 1)
    {
      CalcTimes();
      FormatData();
      LCDdisplay();
    }
    
    // timer is running so check for any stop conditions
    if ((digitalRead(StartPin) == LOW) || ((millis() - StartTime) > MaxTime) || A_count >= MaxHits || B_count >= MaxHits)
    {
      StopTimer();
      tone(BuzzerPin, FreqLo, BuzzTime10);
      // update display just in case last msec hit came in
      if (AupDtFlag == 1 || BupDtFlag == 1)
      {
        CalcTimes();
        FormatData();
        LCDdisplay();
      }
      
      // just for the moment send times to PC for debug
      SendTimes();
      
      // delay enough to debounce stop button
      delay(DB_delay);
    }
  }
  else
  {
  // timer is stopped look for start button or review button pushes
    if (digitalRead(StartPin) == LOW)
    {
      // start button pushed
      // Do the following debug code only to get system running
      // This will send message to PC to show button was pushed
      Serial.println("Timer is running");
      
      // turn on the LED to show timer is running
      digitalWrite(LEDPin, HIGH);
      
      // clear all the prior runs data, reset the display
      ClearData();
      DisplayState = 0;
      FormatData();
      LCDdisplay();
      
      // delay the Wait Time from start button push
      // this delay is for debounce purposes and should stay
      delay(DB_delay);
      // this delay will change to random later on    
      delay(WaitTime);
       
      // enable the interrupts just in case
      interrupts();
      
      // save the starting time of this run
      StartTime = millis();
      
      // set state of timer to running
      TimerState = RUN;
      
      // now buzz the speaker for 0.5 secs
      tone(BuzzerPin, FreqHi, BuzzTime5);
    }
    // check for up down button pushes change display state as reqd
  }
}


void StopTimer()
{
  //noInterrupts();
  TimerState = !RUN;
  // turn the LED off to show timer is stopped
  digitalWrite(LEDPin, LOW);
}


void ClearData()
{
  A_count = 0; 
  B_count = 0;
  for (int i=0; i < MaxHits ; i++)
  {
    A_Times[i] = 0;
    B_Times[i] = 0;
    A_splits[i] = 0;
    B_splits[i] = 0;
    AB_splits[i] = 0;
  }
  A_Hit_Factor = 0;
  B_Hit_Factor = 0;
}


void SendTimes()
// temp routine to send data to serial monitor
{
  Serial.println("Timer is stopped");
  Serial.println("Here are the times for Shooter A");
  Serial.print("Number of hits : ");
  Serial.print("\t");
  Serial.println(A_count);
  Serial.println("A Hit and split times are : ");
  int i = 0;
  int k = 0;
  for (i=0; i < A_count ; i++)
  {
    k = i + 1;
    //Serial.print(A_count[k]);
    Serial.print("\t");
    Serial.print(A_Times[i]);
    Serial.print("\t");
    Serial.println(A_splits[i]);
  }
  Serial.println("Here are the times for Shooter B");
  Serial.print("Number of hits : ");
  Serial.print("\t");
  Serial.println(B_count);
  Serial.println("B Hit and split times are : ");
  for (i=0; i < B_count ; i++)
  {
    k = i + 1;
    //Serial.print(B_count[k]);
    Serial.print("\t");
    Serial.print(B_Times[i]);
    Serial.print("\t");
    Serial.println(B_splits[i]);
  }
}


void ISR_A()
{
  if(TimerState == RUN)
  {
    // 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 == RUN)
  {
    // store the hit time 
    B_Times[B_count] = millis() - StartTime;
    // increment the hit count and array index
    ++B_count;
    // set the Hit flag so other stuff will update
    BupDtFlag = 1;
  }
}


void CalcTimes()
{
// routine to calculate all data and declare winner
// not all calcs having meaning for uses of timer
  // calculate A splits and cumlative hit factor
  if (A_count > 1)
  {
    for (int i=1; i < A_count ; i++)
    {
      A_splits[i] = A_Times[i] - A_Times[i-1];
    }
  }
  else
  {
    A_splits[0] = A_Times[0];
  }  
  A_Hit_Factor = A_Times[A_count - 1]/A_count;
  // calculate B splits and cumlative hit factor
  if (B_count > 1)
  {
    for (int i=1; i < B_count ; i++)
    {
      B_splits[i] = B_Times[i] - B_Times[i-1];
    }
  }
  else
  {
    B_splits[0] = B_Times[0];
  }  
  B_Hit_Factor = B_Times[B_count - 1]/B_count;
  // Calculate A - B times just in case
  int Min_count = min(A_count, B_count);
  for (int i=0; i < Min_count ; i++)
    {
      AB_splits[i] = A_Times[i] - B_Times[i];
    }
// add more here for A vs B modes

}

void FormatData()
{
// routine to format lines of data to be sent to LCD
// presently assume 2 lines of 16 characters
// switch formatting done based on display state
  switch (DisplayState)
  {
    case 1:{
      //this is for single A shooter mode
      // display number of hits so far
      String tmp = "A: # hits = ";
      Line1 = tmp + A_count;
      // now display the time of last hit in secs out to hundreths of secs
      String tmp2 = String(int(((A_Times[A_count-1])+5)/10)); // round msecs into csecs
      int k = tmp2.length();  // k will be index to last char in padded string
      int km1 = k-1;          // km1 will be index to next to last char
      int km2 = k-2;          // km2 will be position of period
      tmp2 = tmp2 + '0';  // pad the end of the truncated string with a zero
      //now move chars to make space to add a period
      tmp2.setCharAt(k, tmp2.charAt(km1));   // move next-2-last to last position in string
      tmp2.setCharAt(km1, tmp2.charAt(km2)); // move char to next-2-last position
      // now insert period
      tmp2.setCharAt(km2, '.');
      // tmp2 now holds rounded time in secs XX.xx format
      Line2 = "Hit time = " + tmp2 ;
      break;}
    case 2:{
      // this is for A vs B mode
      // put that display code here
      break;
    default: 
      // do the default =0 for now
      // 2 string objects for lines 1 and 2
      Line1 = "Arduino timer v1";
      Line2 = "says Hello World";}
  }
}


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

Barring some more typos it should be OK. Mostly I copied and pasted the working parts, just changed their order.

Did you ever verify that the rounding was really rounding up and down properly ?

sspbass:
Quirks I’ve noticed. When freshly uploaded or reset, it takes two button pushes to get it to start.

The display won’t change when hit only once. It takes to strikes for the lcd to display any hits/times.

I noticed that in the last vid as well. Let’s see if that still occurs with the code above. The display updating is less goofy above and hopefully the problem will be gone w/o having to think about it. I also saw one vid where the display got wierd at hit #21 I think. That won’t happen now.

ps - look at the new code and see if it doesn’t make more sense given there are things to be checked and calculated when it’s running and mostly different things to be checked and calculated when it’s not. These will only grow in the future and the prior version was basically organized around reading the start/stop switch.

I’m not going to bullshit ya, my helpfulness is limited. This is my first arduino project and I don’t know any programming languages.

I am looking through your code though and learning from it so it is good experience for me.

And yes, it is rounding correctly.

sspbass:
I’m not going to bullshit ya, my helpfulness is limited. This is my first arduino project and I don’t know any programming languages.

I am looking through your code though and learning from it so it is good experience for me.

It’s a learning experience for me as well. When I’ve done stuff lke this, it’s been with a PIC and mostly in assembly language. So doing this in Arduino-speak is new to me. Admittedly it’s a lot lke C but I’d be at best a dabbler in that. I think for the most part, with a little work, the code can be understood. Hopefully the comments are good enough to help.

I’m looking a this as a good indicator of what I can do with my Airsoft project this summer. While I intend to get a PC in the loop to do real-time target control, it would be nice to have some basic functionality built into the local controller/timer.

BTW I see why the display only updates after the 1’st hit. That should be fixed above.

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

Here are the results. The new 4 line display reads ok in person but is a little more difficult to pick up on the video.