Counting and Timing a shock sensor

Ok, I got the sketch to upload.( I went back to my laptop as I think the desktop isn’t communicating with the arduino.)

The tone function is now working and it appears to be timing correctly although it could be off by .5 seconds and due to my reaction time I may not be able to tell.

It when I hit the button to stop the timing it will often start back up in a 3 seconds without another button push. I imagine this has something to do with the bounce in the switch. I find it odd though that the new sketch would change anything to do with that.

sspbass:
Ok, I got the sketch to upload.( I went back to my laptop as I think the desktop isn’t communicating with the arduino.)

The tone function is now working and it appears to be timing correctly although it could be off by .5 seconds and due to my reaction time I may not be able to tell.

It when I hit the button to stop the timing it will often start back up in a 3 seconds without another button push. I imagine this has something to do with the bounce in the switch. I find it odd though that the new sketch would change anything to do with that.

Kewl ! So it now barks and hopefully with 2 different tones and durations for the start and stop.

RE: the restarts … my guess is you are correct, though I would have hoped the 1 sec delay introduced by the tone() would have prevented bounce from being a factor. It could be that tone() doesn’t act like delay() does, and so there’s no effective delay to wait out the switch push and any subsequent bounces. To confirm this add a delay(1000) just after the call for tone() (where the button is read and the timer state if found to be running). This may or may not be the “fix” for this problem but it will confirm that bounce is the root cause.

Does the timer stop and stay stopped after 30 secs of run time ? If so then that part is working and it is not a case of some odd interference from the tone() making the timer restart.

I added the delay like so.

// now buzz the speaker for 0.5 secs

tone(BuzzerPin, FreqHi, BuzzTime5);

// no need to debounce as mucho time has elapsed

// 1 second delay to debounce. Above comment may not be accurate.

delay(1000);

}

else

{

The automatic re-start still occurs most of the time.

Errr … wrong place. The stop timer button push (w/ new delay added) is here.

    else
    {
      // start stop button has been pushed again to stop timer
      StopTimer();
      tone(BuzzerPin, FreqLo, BuzzTime10);
      delay(1000);
      // no need to debounce switch, a delay is inherit with tone
      // just for the moment send times to PC only not to display
      SendTimes();
    }

oh, duh.

Yep, that took care of it.

So if the lcd requires use of pins 2 & 3 can I simply switch them to pins 8 & 9 and then it’s all in the programming?

Assuming I move the buzzerPin to pin 10 of course.

sspbass:
oh, duh.

Yep, that too care of it.

Good ! I guess I should re-think my earlier thinking re: debouncing. As you can tell from the comments, I was counting on inherent delays to eliminate the need for any other debouncing. I’ve now read that the tone() function is ‘non-blocking’ which I think means other stuff still runs and there’s no delay while the tone is being played. So that would mean there needs to be some debounce (or even a simple delay) on the button pushes. You’ve discovered what happens on the stop push but I suspect it might just as well happen on the start push. It’ll be masked by the timer starting and stopping and starting and … well you get the idea. A simple delay can eliminate the effects of switch bounce but it means a lack of “responsiveness”. Given the usage of the timer my guess is that this isn’t a big deal … or even noticeable in most cases. The delay you 1’st added, in the “start path”, won’t stop the timer from recording times. The ISRs will interupt the delay function to do their work but other functions will be … well … delayed. So a hit occuring before this delay time is finished can’t get displayed. Maybe that’s not a big deal but my thinking was to limit that delay to something less than the expect 1’st shot time. So perhaps set that new added delay to 500 msec and leave the “stop” delay at 1 sec ? When you build the timer for real a simple hardware addition (a cap across the switch) should reduce the problem. I guess the coding could be changed to recognize, and react to, the button release rather than the button push. But that’s a fine detail to be tuned in once you’ve got this all pretty much working as desired.

On that last bit … what’s your thoughts on how the display should “work” ? By that I mean do you want the display to show something new everytime a hit is sensed ? My timer does that. I think it natural and nice to have, even though I’m not looking ay my display when I’m doing the shooting being timed. I’m also thinking that to generalize the whole display function it should be split into 3 parts. On every hit, all the pertinent data gets calculated so it’s available to be displayed even if it isn’t at that moment. This way anything you want to display can be, w/o needing to change the calculation parts.

Then the info to be displayed should be formatted. This may depend on the “display state”, that’s whatever display you have chosen for the timer mode you have it in. For your 16x2 LCD it would mean formatting 2 lines with the data to be sent in a format the LCD can use. Maybe thats hit time and # of shots. Maybe it’s Shooter A times and Shooter B times.

Lastly there would be, effectively, a “driver” part, that knows how to manipulate whatever digital lines need to be manipulated to get the formatted display lines over to the display. That way you could change display types w/o having to recode other parts of the software, just redo the “driver” part. For the LCD I thought you had it would be something like a serial.print line1 and serial.print line2. For the one above … I dunno.

Any chance I could talk you into buying the 20x4 serial LCD display instead of the one above ? :twisted: :mrgreen:

sspbass:
So if the lcd requires use of pins 2 & 3 can I simply switch them to pins 8 & 9 and then it’s all in the programming?

Assuming I move the buzzerPin to pin 10 of course.

If by switch you mean switch in the LCD “driver” code then yes I think the above is true. The pins used to record the hits on target A and B must stay on pins 2 and 3. They are the only ones that map to the external interrupts.

Mee_n_Mac:

sspbass:
Any chance I could talk you into buying the 20x4 serial LCD display instead of the one above ? :twisted: :mrgreen:

Definitely not out of the question, I’ll try this one out first though.

would something like this accomplish the same thing? http://www.sparkfun.com/products/258

This is the code that will display Hello World on this LCD, it looks like it should be pretty easy.

*/

// include the library code:

#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

void setup() {

// set up the LCD’s number of columns and rows:

lcd.begin(16, 2);

// Print a message to the LCD.

lcd.print(“hello, world!”);

}

void loop() {

// set the cursor to column 0, line 1

// (note: line 1 is the second row, since counting begins with 0):

lcd.setCursor(0, 1);

// print the number of seconds since reset:

lcd.print(millis()/1000);

}

sspbass:
Definitely not out of the question, I’ll try this one out first though.

would something like this accomplish the same thing?

http://www.sparkfun.com/products/258

This is the code that will display Hello World on this LCD, it looks like it should be pretty easy.

The product you linked to is the “backpack” that SF adds to the LCD that you have that converts it into a “serial enabled LCD display”, basically the display I thought you had. Given you’ve found a library for the display you have and if you have enough pins … there’s no pressing need to convert it to a serial display that I can see now. Given the timer times and beeps and starts/stops, it’s time to get it to display stuff. I would start with the simple Hello World and then build from there.

Have a look at the below. I think I’ve include a skeleton of all the basic stuff needed to start. You’ll have to change the pin numbers/assignments to match your LCD wiring. They are wrong as is below. I’ve fixed a few things and added some others. I would get your Arduino wiring to the LCD checked out using a version of the LCD tutorial code (not the timer code) first and then give this a try … after you’ve reviewed it. It may do more than you want (and while simultaneously doing less than you’ll eventually want) but I figure it’s easier to cut out than to add in.

#include <LiquidCrystal.h>

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

// set pin numbers
const int StartPin = 4;       // the number of the Start pushbutton pin
const int UpPin = 6;          // the number of the ScrollUp pushbutton pin
const int DownPin = 7;        // the number of the ScrollDown 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 = 9;      // 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 = 3000;     // the wait time btw start button and timer running = 3 secs
const unsigned long DB_delay = 200;      // set a debounce wait time of 200 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
int MaxHits = 10;                        // 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 HitFlag = 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;
char Line1 [17];
char Line2 [17];

void setup()
{
  // set up the LCD's number of columns and rows: 
  lcd.begin(16, 2);

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

      // clear all the prior runs data
      ClearData();

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

      // turn on the LED to show timer is running
      digitalWrite(LEDPin, HIGH);

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

      // no need to debounce as mucho time has elapsed
    }
    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 only not to display
      SendTimes();
    }
  }
  // send data to display if they have been any hits
  if (HitFlag == 1)
  {
    CalcTimes()

    // this is goofy but for now change display state here
    DisplayState = 1;
  }
  FormatData()
  LCDdisplay()

  //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 only not to display
    SendTimes();
    }
  }
}

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


void ClearData()
{
  // This will need to change is more hits are allowed
  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 : ");
  for (int i=0; i < A_count ; i++)
  {
    Serial.print\(A_count[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 (int i=0; i < B_count ; i++)
  {
    Serial.print\(B_count[i+1];
    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
    HitFlag = 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
    HitFlag = 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[B_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
HitFlag = 0;
}

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
      // number of hits so far
      Line1 = char(A_count);
      // print the time of last hit in secs
      Line2 = char(A_Times(A_count-1)/1000);
      break;
    case 2:
      //this is for A vs B mode
      break;
    default: 
      // do the default =0 for now
      // 2 char arrays for lines 1 and 2
      Line1 = "Arduino timer v1";
      Line1 = "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);
}

{DK : feel free to review and comment the hell out of the above}

It’s not liking your array

volatile long A_Times[MaxHits];

Something to do with declaring them as a global variable instead of a local variable.

Found the info at the below website.

http://www.arduino.cc/cgi-bin/yabb2/YaB … 1277556665

sspbass:
It’s not liking your array

volatile long A_Times[MaxHits];

Something to do with declaring them as a global variable instead of a local variable.

Found the info at the below website.

http://www.arduino.cc/cgi-bin/yabb2/YaB … 1277556665

Yeah, I wasn't sure if using a constant to define the array size like that was allowed. For the moment you could replace the MaxHits with the 10 that was there before.

This …

volatile long A_Times[MaxHits];
volatile long B_Times[MaxHits];
long A_splits [MaxHits];
long B_splits [MaxHits];
long AB_splits [MaxHits];

becomes (goes back to) …

volatile long A_Times[10];// array to hold up to 10 hit times for target A
volatile long B_Times[10];// array to hold up to 10 hit times for target B
long A_splits [10];
long B_splits [10];
long AB_splits [10];

Might as well get the LCD display semi-working and then go back to polish this type of stuff up. My reasoning behind this was that I think you’ll want to define a max number of hits because you’ll only have so much SRAM available to store them. Making it a constant was to make it easy to change when you figure out what thay number should/can be.

You’ll also want (I think) to set some number as the “hits needed to win” for some Shooter vs Shooter contests. IE - first to score a hit on all 6 targets (6 hits) wins.

Hmmm perhaps the solution is to change this …

int MaxHits = 10; // Maximum number of hits allowed

to this ???

const unsigned int MaxHits = 10; // Maximum number of hits allowed

Might help if I declared a constant to be a constant, instead of a variable (which is what the bad code did). I’m not sure if making it a constant will fix the compiler error but it’s less typing so give it a try.

I haven’t reviewed any code yet, had a busy Sunday.

Try this though to define array size:

#define MAXHITS 10
volatile int array[MAXHITS];

Dan

The above looks good. Why can’t code just do want I intended it do rather than what I told it to do ?!

Found a typo in my prior post too …

  }  
  A_Hit_Factor = A_Times[B_count - 1]/A_count;

  // calculate B splits and cumlative hit factor

… should be …

  }  
  A_Hit_Factor = A_Times[A_count - 1]/A_count;

  // calculate B splits and cumlative hit factor

A’s “hit factor” should be A’s last time divided by the number of hits A has.

I was thinking about different “game modes” that you might want to have the timer programmed for and I was constraining myself to having just 2 channels, A and B, to differentiate between. That meant some of the contests I plan on implementing in my flavor of an electronic target system wouldn’t be do-able, at least w/o some added circuitry, with your setup. Then the answer (I think) occured to me ! Even though all the targets on a channel are using the same input pin and interrupt, there is a way for the Arduino to distinguish one target from another. You have a pulse-stretcher (CMOS version of a 555 timer) as part of each targets piezo-to-digital circuitry. IIRC the pulsewidth (PW) is presntly set to ~2 msec. This is fine as is but why not have differing PWs for each target. For example, target 1 could have a 1 msec PW, target 2 a 2 msec PW, etc, etc. In this way the Arduino could detect, not just that a hit occured, but by measuring the PW, what target was hit.

Just thought it worth a mention wnile it was in my mind as it requires no added circuitry, just added software.

You could also build some simple circuitry to activate a number of pins (and thus use the PCINT pins). At that point it is a simple matter of checking the registers against a static set of variables to find out which target went off. For 3 pins you could have up to 8 “players”.

Dan

dkulinski:
You could also build some simple circuitry to activate a number of pins (and thus use the PCINT pins). At that point it is a simple matter of checking the registers against a static set of variables to find out which target went off. For 3 pins you could have up to 8 “players”.

Dan

Yup, a “port expander” (aka a shift register) was my original thinking back pages ago but if the PW can be measured, then there’s no need to add circuitry, just change the RC constant on the target. Given these times are, at most, in the msec range, they’re still far below the shot-shot times or human perception capabilities. So you could require and confirm double taps on a target to score it. Or have shooter vs shooter matches with no shoot targets interspiced with the live ones. Or require targets be taken in a specified order (I’m not certain how useful this is by itself). Or perhaps some other ideas the OP may come up with.

Plus I’m not sure how many spare pins are left after connecting the LCD.

Mee_n_Mac:
Hmmm perhaps the solution is to change this …

int MaxHits = 10; // Maximum number of hits allowed

to this ???

const unsigned int MaxHits = 10; // Maximum number of hits allowed

Might help if I declared a constant to be a constant, instead of a variable (which is what the bad code did). I’m not sure if making it a constant will fix the compiler error but it’s less typing so give it a try.

This got me past the array part… but now this is an issue.

sspbass:
This got me past the array part… but now this is an issue.

Looks like a typo, it’s missing a ) in two places. It should be …

Serial.print(A_count[i+1]);

… and …

Serial.print(B_count[i+1]);

Odd it didn’t barf at the first occurence for the A target printout.

BTW what did you end up with for pin assignments and did you get the LCD working to say Hello World! with the tutorial code ?