Counting and Timing a shock sensor

sspbass:
Now I’m excited to get a functional setup on my target and then take it even further!

Holy shit ! If the timing seems to be … well, timing … I think it’s “easy” (ahem) to make your speaker buzz. There’s a tone() function already made just for this purpose. Perhaps you should paste the code you have (mod’ed to get it to compile) so I have the same baseline code.

Here is the code that compiles and has been tested to work.

// 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 = 5;      // the number of the buzzer output pin
const int LEDPin = 13;        // the number of the LED output pin

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 BuzzTime = 500;     // set the on time for the buzzer, 500 msecs

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
unsigned long StartTime = 0; // variable to hold the start time
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 int A_Times[10];    // array to hold up to 10 hit times for target A
volatile int B_Times[10];    // array to hold up to 10 hit times for target B

void setup()
{
  // 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 will change to random later on    
      delay(WaitTime);

      // turn buzzer on:    
      digitalWrite(BuzzerPin, HIGH);
      // 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();
      // now delay buzzer on time
      delay(BuzzTime);
      // turn buzzer off:    
      digitalWrite(BuzzerPin, LOW);
      // set state of timer to running
      TimerState = RUN;
      // no need to debounce as mucho time has elapsed
    }
    else
    {
      // start stop button has been pushed again to stop timer
      StopTimer();
      Buzz();
      // no need to debounce switch, a delay is built into Buzz function
      // just for the moment send times to PC only not to display
      SendTimes();
    }
  }
  //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();
    Buzz();
    // 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 Buzz()
{
  // turn buzzer on to alert user
  digitalWrite(BuzzerPin, HIGH);
  // delay the buzzer on time and then shut it off
  delay(BuzzTime);
  digitalWrite(BuzzerPin, LOW);  
}  

void ClearData()
{
  A_count = 0; 
  B_count = 0;
  A_Times  [0,0,0,0,0,0,0,0,0,0];
  B_Times  [0,0,0,0,0,0,0,0,0,0];
}

void SendTimes()
{
  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 times are : ");
  for (int i=0; i < A_count ; i++)
  {
    Serial.println(A_Times[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 times are : ");
  for (int i=0; i < B_count ; i++)
  {
    Serial.println(B_Times[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;
  }
}

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

sspbass:
… has been tested to work.

Thx. By "work" dare I ask if it seems to actually time "hits" *(even if simulated) ?

I took the baseline you posted and added the tone() function to drive your piezo speaker, now to be on pin 9 vs pin 5. I’m not entirely sure that the library tone() won’t screw up the millis() function. So if the timing doesn’t work or the times seem short by 0.5 secs (from the start), then the rev1 is what’s causing it. I believe there are ways to make the PWM run off a different timer (from the 16 bit one used by milis()) but I don’t know how to do that. I’ll leave that up to you to fix … if desired.

What should happen is a 0.5 sec buzz of 2kHz at the start and a 1.0 sec long buzz of 1kHz on the stop. You can see the new constants if you want to change these values.

I don’t think you need an #include XYZ but if the compiler barfs, that’s one thing to look for.

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

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
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
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 int A_Times[10];    // array to hold up to 10 hit times for target A
volatile int B_Times[10];    // array to hold up to 10 hit times for target B

void setup()
{
  // 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 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
      Buzz(FreqHi, BuzzTime5)

      // no need to debounce as mucho time has elapsed
    }
    else
    {
      // start stop button has been pushed again to stop timer
      StopTimer();
      Buzz(FreqLo, BuzzTime10);
      // no need to debounce switch, a delay is built into Buzz function
      // just for the moment send times to PC only not to display
      SendTimes();
    }
  }
  //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();
    Buzz(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 Buzz(Freq,BuzzTime)
{
  // turn buzzer on to alert user
  tone(BuzzerPin, Freq, BuzzTime);  
}  

void ClearData()
{
  A_count = 0; 
  B_count = 0;
  A_Times  [0,0,0,0,0,0,0,0,0,0];
  B_Times  [0,0,0,0,0,0,0,0,0,0];
}

void SendTimes()
{
  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 times are : ");
  for (int i=0; i < A_count ; i++)
  {
    Serial.println(A_Times[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 times are : ");
  for (int i=0; i < B_count ; i++)
  {
    Serial.println(B_Times[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;
  }
}

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

It worked as in it seemed to be timing. I didn’t get out a stopwatch to compare but I can tomorrow.

I’ve got a quick question about this part.

void SendTimes()

{

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 times are : ");

for (int i=0; i < A_count ; i++)
{
Serial.println(A_Times*);
__
}[/b]
*__
By the nature of the for statement the first part of the A_Times array to be sent will be location 1.
What I read was that the first number in the array is actually location 0.
If that’s the case then isn’t this missing the first time?

sspbass:
It worked as in it seemed to be timing. I didn’t get out a stopwatch to compare but I can tomorrow.

Fantastic ! I may just have to get one of these Arduino thingees myself ! :twisted:

sspbass:
By the nature of the for statement the first part of the A_Times array to be sent will be location 1.

What I read was that the first number in the array is actually location 0.

If that’s the case then isn’t this missing the first time?

You may be right, it was my intention in the ISR to store the time away and then increment the hit counter so the 1'st time would be at an index of 0. Then in the print loop I thought the i=0 would print that index=0 time. But I can look at that. If wrong it's easy enough to fix, make the ...

A_Times[ i]

into …

A_Times[ i-1]

(and the < into <=)

I didn’t put a lot of thought or effort into the printing as it will all be changed, or tossed out, to send the data to your LCD display.

BTW I looked more closely at the schematic for an Uno and I see there may be a yellow LED connected to pin 13 afterall. It’s near pin 13 and the TX/RX LEDs. See if it’s working as hoped for.

EDIT : I checked and i think [according to this the print should send the 1’st hit time. See if this makes sense to you.

For example, using a multiplication in the increment line will generate a logarithmic progression:

for(int x = 2; x < 100; x = x * 1.5){
println(x);
}

Generates: 2,3,4,6,9,13,19,28,42,63,94

Since X=2 initially and 2 gets printed out, the incrementation (?is that a word?) must happen at the bottom of the for loop.](http://arduino.cc/en/Reference/For)

I have been lurking on this thread a bit. I just wanted to point out that there is a LED on pin 13. Writing a high to pin 13 should turn it on!

Dan

sspbass:
I didn’t get out a stopwatch to compare but I can tomorrow.

That’s actually a good idea. I don’t know that you need stop watch exactly but waiting to hear the buzz (assuming the rev1 code compiles and runs) and then hitting the target (or simulated target) every 1 second (per your regular watch) for 7 or 8 seconds would be a good 1’st test to see if the tone() function is screwing with the millis() function.

dkulinski:
I have been lurking on this thread a bit. I just wanted to point out that there is a LED on pin 13. Writing a high to pin 13 should turn it on!

Dan

Hey you lurker … no lurking !! :mrgreen:

Yeah it’s a little hidden on the schematic but I did find it. Bad drawing practice not having at least a line indicating there’s something more on the line going from the MCU to pin 13 on the connector. The LED just kinda hangs out by U5B with a “13” next to it.

BTW got any tips on getting the tone() function not to futz up the 16 bit timer used for millis() ? I’m sure there’s a way to hard code it to use another timer but you’d think it would be made explicit somehow. Like a tone1(), tone2() … etc. I did see some mention in a forum that certain pins got assigned certain PWM/timers but who knows if that’s good info.

Also is there a good way to disable just the external interrupt pins, w/o disabling other interrupts (aka millis()) ?

Unfortunately there is no tone1, tone2, etc command. You could setup another timer to generate tones, you just don’t have the convenience of the Arduino library. I’ll post some timing code a bit later to use an 8bit timer.

You could write a naked ISR and then disable just the external interrupts, but again this is not inside the scope of the Arduino library as far as I know. I’ll also post some code on that.

As for PWM, there are two pins per timer that are hardware driven PWM pins. You can set one or the other to change on a timer match. In fact there is a mode for a phase locked pwm mode.

Dan

dkulinski:
Unfortunately there is no tone1, tone2, etc command. You could setup another timer to generate tones, you just don’t have the convenience of the Arduino library. I’ll post some timing code a bit later to use an 8bit timer.

You could write a naked ISR and then disable just the external interrupts, but again this is not inside the scope of the Arduino library as far as I know. I’ll also post some code on that.

As for PWM, there are two pins per timer that are hardware driven PWM pins. You can set one or the other to change on a timer match. In fact there is a mode for a phase locked pwm mode.

Dan

It may be that the usage above will work out OK. According to the “[ugly details”, I think timer2 is used first and timer0 (the 16 bit timer used for millis()) is used last. That plus your note above makes me hopefull that they will be no interference. I guess testing will show this to be true or not. If it turns out not … then it can be fixed.](Google Code Archive - Long-term storage for Google Code Project Hosting.)

Ah, very nice, I hadn’t read that documentation before but it is very clear.

Just some technical details, timer0 and timer2 are both 8 bit timers. timer1 is a 16 bit timer. The documentation refers to timer0 being used as the millis() generator and the default PWM driver.

For generating tones on my lasertag rig I use the hardware PWM pins and toggle them on and off. For a simple buzzer this isn’t going to be needed.

Dan

dkulinski:
You could write a naked ISR and then disable just the external interrupts, but again this is not inside the scope of the Arduino library as far as I know. I’ll also post some code on that.

Dan

Let me explain my thinking. When I’ve used my normal shot timer, it seems that the guy with the hand cannon next to me wants to let go a furious fusilade just after I’ve finished my string. The result is a lot of extra times recorded that aren’t mine. This isn’t too bad but it does mess up the auto “hit factor” (Comstock scoring) display. So to prevent something similar from happening with this project, I want a way to turn off the timing when the timer isn’t running. Thus the start button doubles as a stop button (and I also put in a “par time” time-out function). I thought it would be “elegant” to simply disable the external interrupts to accomplish this … alas I’m out of my skill set. The nointerrupts() would seem to do it but then do so much more that it’ll screw up other functions. I wasn’t sure that the detachInterrupt() call was the right way either. So I ended up gating the logging of millis() in the ISRs via a test on the timer being in the RUN state. This should work but it lacks “elegance” in that it chews up another few usec in the ISR and, as I understand it, time spent in any isr, is time that the timer0, being used for “time” (millis()), isn’t running (?or being kept track of?). A few extra usec of error every hit isn’t a practical concern, especially given the other Arduino timing errors, but it’s …inelegant. :mrgreen:

dkulinski:
timer1 is a 16 bit timer. The documentation refers to timer0 being used as the millis() generator and the default PWM driver.

Aaah. good to know. Thx.

I find it odd that the typical user an Arduino is aimed at would be unlikely to ever figure out all this “detail”.

Alright, to disable just the external timers you just need to twiddle a couple of bits in a register.

To turn off the PCINT (pin change interrupts) you simply do this:

PCICR &= 0xF8;

To turn of the external interrupts you simply do this:

EIMSK &= 0xFC;

Because Arduino ultimately uses the avr-gcc compiler, these are defined.

Dan

All this talk about details makes a nice seque into what comes next (assuming the buzzer works). I know the OP has a 16x2 LCD display. Obviously there’s only so much info that can fit, at any one time, on it. So what is to be displayed and how does one access the info that can’t displayed all-at-once ? Also what is all the “info” ? This last be needs to be (mostly) decided upon first.

For example I mentioned auto-computation and display of hit factor (# hits/time). So what info, at a minimum, is available ? For each shooter there would be the # of hits, the (cumulative) time of each hit, the time between each hit (split) and perhaps hit factor and ??? Conceivably you might want this smart timer to do some statistics on that data as well (min, max, average, ?). This can be a lot of stuff to scroll through, especially if you just want to compare/examine the times between shooter A and B. Obviously a larger display makes things easier (ie - the 20x4 LCD or even a graphical LCD) but that may be more effort and $$s than the OP wants ATM.

I would also mention that any display and controls will need to allow configuration and setup of the various timer modes and settings. Even my simple timer allows me to set the par time and set the wait time (to be random or fixed, if fixed then 0 - 3 secs). I think for this project there are other settings that would be nice to have (based on my experience). One might be an auto-start feature, wherein the timer restarts after the alloted par time so the user can concentrate on repeatly doing the shooting instead of having to push the start button. You might want to have this timer store away the data for later download to a PC (something I wish I had !) instead of wiping the data on every start. And then there are the various “game” modes that you may want to use … or not. IE - have the timer declare a winner and stop when the 1’st shooter scores N hits. You’ll need to invoke “plate mode” (or whatever you want to call it) and set the number of hits to win (N) prior to starting. Again more display options and control input. You’ll need a way to dsplay and chose what “mode” the timer is in, timing mode or data review mode or setup mode or ?? So time to think a bit about what you want to do. Not all has to be done on day 2 but it’s good to know what might come down the road.

And lastly on that note … let me put a plug in for what I’d use for a control input.

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

It’ll work more betterer :mrgreen: than plain old push buttons and I think is sexier. Especially if you want to add the (functionally useless) ring-o-LEDs. :dance:

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

Mee_n_Mac:

dkulinski:
timer1 is a 16 bit timer. The documentation refers to timer0 being used as the millis() generator and the default PWM driver.

Aaah. good to know. Thx.

I find it odd that the typical user an Arduino is aimed at would be unlikely to ever figure out all this “detail”.

I forgot to ask … is there a specific pin (or 2) that should be used to send the tone() PWM output to the speaker, so that it avoids conflicting with the timer used for miliis() ? I think that was the point of the forum post I read somewhere and now I’m not sure I haven’t screwed it up moving the speaker to pin 9 (from 5). I hope, from the following, it’s OK on 9 though now I think it might need to be on pin 11 (since pin3 is in use as ext int).

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

Use of the tone() function will interfere with PWM output on pins 3 and 11 (on boards other than the Mega).

http://letsmakerobots.com/node/18743

The PWM outputs on pins 5 and 6 share a timer with timing functions such as millis() and delay()

After some recent problems I have also discovered that the tone command kills PWM on pins 3 and 11. This is because the tone command uses the same timer. Unfortunately the noTone command does not reset the timer back to default settings

Only if you are using Timer2 explicitly. In the second link the guy is explicitly using PWM to control motors. Arduino typically does software PWM. When you analogWrite(pin, value) you are toggling that pin in software. Tone is doing the same thing, using the hardware timer to toggle a pin in software. The Arduino libraries hide a ton of implementation details in the end. This was why I skipped Arduino and started with basic C coding, I want complete control!

Sincerely, your local control freak -

Dan

Food for thought as to what fits on a 16x2 display …

Is there enough value in that info to justify the need to scroll through all of it?

Perhaps with the rotary encoder it would be easy to navigate through it, I just don’t know.

Maybe after each round of shooting the main data is displayed but details can be accessed at will.

I do really like the idea of logging the data to an sd card for computer review. You could then chart your progress

over time and have definite measurable results.

ETA: Another thought I just had was that it would be great to compare the two shooter side by side.

If the info could be made to scroll horizontally with the encoder then the top line could be shooter A and the bottomer line shooter B.

That would make comparing data easy. I think that being able to have multiple shooters is where this thing shines, otherwise you might as well just

buy a shot timer.