Well thems are results. Sounds like a lot of the same underlying problem. Did the PC monitor show zero times for the 1’st hit as well ? Sounds like my indexing is off by one.
A stupid question but I have to ask … how sure are you that in the single shooters cases you were hitting the A channel (pin 2) ? I ask because it sounded that in the last vid, the shock sensor was perhaps now on the B channel and some of the results would be explained (perhaps) is this were the case and the B interrupts were not disabled (though they should have been).
sspbass:
Notes about code:Standard Mode:
Single Shooter
- 20th hit, hit time, and split time do not show up on the main screen hit is sensed and timer stops on 20 but screen never shows it.
OK, I see this problem and know where it came in. Just have to figure a good way to get that last update in w/o the display going into “review” mode.
This was there in the prior coding too. I have to think it came in when we switched to enabling and disabling the interrupts via attaching and detaching. It's very odd there's a hit count but no time. Almost impossible I might say. Does the PC monitor show the same ? If not then it's some odd write to the LCD that's causing it. The code tries to suppress any of the data when hit count = 0 so when there's 2 people, there's no mis-info displayed because there's no hit data (yet) for that channel. Yet that doesn't explain the oddity w/o something else being wrong.sspbass:
3. Second round starts with 1 hit already but no times registered and ended on 20 hits. 1st hit didn’t have any time in the review display.
My inclination is to say it’s some very very very early “hit” and that the time got rounded off/truncated and so doesn’t show in the LCD. But if it’s there at all, it should show up in the PC monitor as those get sent as pure millis(), not rounded or processed in any way. It may be that enabling the ints this new way somehow induces an early false “hit” that needs to get ignored. The millis() printout will tell.
sspbass:
Notes about code:2 Shooters
- lines 9, 10, 13, 14, 17 & 18 have a random number in front the line. Line 9 is aligned like the single digit lines but since it’s with the double
digits it’s shifted left one.
OK, if this was confined to the 2 shooters review display only (1 shooter was good) then I think I see the problem. The leading space insertion code was missing the ; after the lcd.print(’ ‘) … should be … lcd.print(’ '); … So without that end statement perhaps screwy things happen. I’d have thought it would have messed up elsewhere too but perhaps the times were such that the screwup wasn’t visible.
If you’ve read the earlier posts from Mon, you might have seen that there was 1 more “clean up” build of software waiting in the wings. Well here it is along with what I think are the fixes to the problems you noticed. This clean up gets rid of the storing of split times and instead just calculates them when it’s needed for the display. It also stores the hit times as 2 byte unsigned integers, which limits MaxTime to 60 secs now (vs 99 secs). These changes should save quite of bit of RAM. Max hits are now set to 24 because it’s a even number of hits that fills the single and dual shooter review displays. It can be made higher or lower.
Fixes should ensure the last hit is always displayed on the last “running” display when stopped but before going to review mode. It was somewhat probablistic previously, with the odds favoring it would be missed there but show up in the review displays. I added the missing ; so I hope that fixes the dual shooter review issues and lastly I took a guess as to what I think is a very very very early (false) “hit” problem. I moved the clearing of old data to be after the enabling of the interrupts. If this “got a 1st hit w/no time” happens again, we can go back to the old way of filtering the interrupts. Perhaps one test to be done is to start the timer and just let it time out, with no deliberate hits, and see that it records no hits (to verify we don’t now have some new noise problem).
So the whole new enchilda …
#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
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 int MaxTime = 60001; // the max time the timer can run for = 60 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 = 24; // 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
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 int A_Times[MaxHits]; // array to hold up to MaxHits hit times for target A
volatile unsigned int 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 int 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)
{
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.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);
}
// 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 int 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()
{
// 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 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 + (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 int 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
}
}
Still some issues. The remaining one is still there on the second rounds.
Line 9 is still shifted to the left & having worse issues on standard mode with 2 shooters. (see pics)
Here are the serial monitor shots to see the goofy hits.
I think you did take care of the goofy times on the second rounds though.
There was one other issue but I can’t recall what it is right now.
Some very odd stuff going on !!
Can you post the code you’re using ? I especially want to verify SendTimes() and UpdateLCD(). There’s crap being printed out to both the LCD and monitor that should NOT be there. First we have zero times for the 1’st hit in both the A and B channels. I assume the exact same times are due to the hit sensor being tied into both channels. Do I deduce correctly that the 1’st run after an Arduino reset does NOT (ever?) have zero hit times for the 1’st hit ? AND that all subsequent runs (w/ no reset in between) DO have zero times ? I also note that the split times are good for that 1’st run (after a reset?) but that the last split time is zero for both subsequent runs, despite the splits during those runs being perfect !!! For that matter the code is not supposed to calculate nor send a split time for the 1’st hit … and yet there they are, even during the good first run !!??!!
I can’t see where the code is screwing up for dual shooters, hits >= 10. It looks like hit #9 is justified properly and those after are not. The code looks fine but … I don’t know what to make of the “8” and the “1” characters in the LCD where they shouldn’t be. An 8 as a hit number shouldn’t ever be at the top, left of the LCD. It’s like the LCD didn’t get cleared and yet I have to believe that function works otherwise you’d be seeing other crap spewed across the LCD.
I might believe that the logical test condition that seems to be failing to stop the non-existant split times from being sent to the monitor might also be not working for the hit > 9 test. I dunno, it’s very very very odd. I don’t know how a split time that’s not even supposed to get calculated does … and then gets printed.
FWIW did all the single shooter stuff print and display (on the LCD) OK ? Have you ever seen a zero time 1’st hit in that mode of operation ?
I note the “while running” display looks almost perfect above. The justification now looks good, I just have to fix the disimilar (A) hit times vs B hit times labels.
Here’s a stupid simple sketch to test what I thought should work. See what the serial monitor “prints”.
void setup()
{
// opens serial port, sets data rate to 9600 bps
Serial.begin(9600);
}
void loop()
{
// test the i in if
for (byte i = 0, i < 4, i++)
{
Serial.print(i);
Serial.print("\t");
Serial.println("A large Koala");
if(i)
{
Serial.println("ate my mom");
}
if(i>2)
{
Serial.println("and puked");
}
}
}
Here is the trial code, I only added delays.
OK that would seem to prove what I thought should work … does.
Here’s a test sketch to investigate why we’re seeing zero hit times. It simply loops while attaching and detaching the interrupts. Ideally after 30 or so secs it should print to the serial monitor and show 0 hits. If the interrupt process somehow generates false hits, then they should show up on the LCD and in the printout. The LED should flicker while running.
#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 TargetAPin = 2; // the number of the A targets input pin
const int TargetBPin = 3; // the number of the B targets input pin
const int LEDPin = 13; // the number of the LED output pin
// initialize the constants
// initialize global variables
volatile byte AupDtFlag = 0; // variable indication an A hit has occurred
volatile byte BupDtFlag = 0; // variable indication a B hit has occurred
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 int A_Times[50]; // array to hold up to MaxHits hit times for target A
volatile unsigned int B_Times[50]; // array to hold up to MaxHits hit times for target B
byte cycle = 1; // counter
unsigned long StartTime; // Start time saved
void setup()
{
lcd.begin(20, 4);
// initialize output pins
pinMode(LEDPin, OUTPUT);
digitalWrite(LEDPin, LOW);
// initialize the input pins with internal pullups
pinMode(TargetAPin, INPUT);
pinMode(TargetBPin, INPUT);
digitalWrite(TargetAPin, HIGH);
digitalWrite(TargetBPin, HIGH);
// opens serial port, sets data rate to 9600 bps
Serial.begin(9600);
// set all the times to some non-zero number
for (byte i = 0; i < 50; i++)
{
A_Times[i] = 1;
B_Times[i] = 1;
}
lcd.clear();
lcd.print("Interrupt test");
StartTime = millis();
}
void loop()
{
digitalWrite(LEDPin, LOW);
// setup ext pins as interrupts
attachInterrupt(0, ISR_A, FALLING);
attachInterrupt(1, ISR_B, FALLING);
delay(250);
// send data to display if there have been any hits
if (AupDtFlag == 1 || BupDtFlag == 1)
{
lcd.clear();
lcd.print(A_count);
lcd.setCursor(0,1);
lcd.print(B_count);
lcd.setCursor(0,2);
lcd.print(cycle);
}
digitalWrite(LEDPin, HIGH);
detachInterrupt(0);
detachInterrupt(1);
delay(250);
cycle++;
if (cycle > 50)
{
SendTimes();
StartTime = mills();
cycle = 1;
for (byte i = 0; i < 50; i++)
{
A_Times[i] = 1;
B_Times[i] = 1;
}
}
}
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 int 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()
{
// 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;
}
The above should have had this in the setup()
lcd.begin(20, 4);
so I’ve now added it.
it’s showing 0 hits.
sspbass:
it’s showing 0 hits.
As it should. I still don’t know where the 0 time hits are coming from. Try this modified sketch. It may display and print some odd times for non-hits but it may shed some light on what’s happening. I’m espeically interesting in what times show up for the 1’st hit in the serial monitor printout for the 1’st run and 2’nd runs after a reset.
#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
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 int MaxTime = 60001; // the max time the timer can run for = 60 secs
const unsigned long WaitTime = 1000; // 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 = 24; // 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
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 int A_Times[MaxHits]; // array to hold up to MaxHits hit times for target A
volatile unsigned int 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 int 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()
{
// 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 ");
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.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
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] = 1;
B_Times[i] = 1;
}
}
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 int 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()
{
// 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 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("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 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 + (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 int 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
}
}
I’d also like to see a copy of what you’ve been running. I wonder if there’s been some odd error introduced in copying stuff from the forum to the Arduino IDE.
Here is what I was running.
#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
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
}
}
I tried your new code briefly. The single shoot in standard mode worked fine, subsequent rounds were fine as well.
Quick draw with 2 shooters (still goes to 6 rounds) worked fine for round 1 and I guess it was fine for round 2 as well.
When round 2 started it showed 0 hits with no other data for either shooter which it has never done. serial monitor displayed everything fine as well.
sspbass:
I tried your new code briefly. The single shoot in standard mode worked fine, subsequent rounds were fine as well.Quick draw with 2 shooters (still goes to 6 rounds) worked fine for round 1 and I guess it was fine for round 2 as well.
When round 2 started it showed 0 hits with no other data for either shooter which it has never done. serial monitor displayed everything fine as well.
Well if it’s always OK with a single shooter and sometimes screwy w/2 shooters, that’s a clue … if true. Let me compare the running code to the baseline I have. That might indicate some portion of the 2 shooter code is writing over memory used and that the single shooter code doesn’t go down the path. That sounds soooo unlikely but …
Did the serial monitor ever show a hit time of 1 msec ? I altered the code to initialze and reset the hit times to 1 msec, which should show up in the serial monitor as 1 (msec) for a time and on the LCD as 0.00 (vs 5 blank spaces in the baseline code). Keep a eye open for these.
When you say “When round 2 started it showed 0 hits with no other data for either shooter which it has never done” do I assume properly you mean the “while running” display on the LCD ? That’s like one of the update flags got set so the display got updated but there was a 0 for a hit time and for the hit count, so that may be a improbable symptom of the same underluing problem. Perhaps now happening because the code was changed to force an LCD update when the timer stops (for any reason). Perhaps the start/stop is somehow glitching ? I dunno. :?
Did the review displays look OK for the 2 shooter QD ? Given the hits are <10 I’d guess so.
The code you posted looks to be 1 revision behind my baseline. I think there were some LCD display fixes in the more recent rev, that aren’t in the version you posted. Also there might be a mix of variable types, such as longs vs ints, that could cause some oddness. Run the version of code below through a number of CoFs and 1/2 shooters and see if the LCD in review mode is still AFU. I changed the ISRs back to the old versions that tested TimerState before storing a hit and time. Perhaps this will fix the 1’st hit, 0 time problem.
You can also try enabling the random delay and see if that works.
I’ll continue to look at what you’ve been running to see if I can spot the reasons for the LCD screwups you took a pic of above.
#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
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 = 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 = 24; // 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
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()
{
// 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 ");
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.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.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();
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 + (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
}
}
i got all of my goodies in today. I hooked up my fancy new display and its AFU. Its gotta be hardware b/c I can’t get it to properly display the hello world example sketch so I’ll have to keep looking. I’m going to be out tomorrow to my brothers graduation party so most of this will have to wait for Sunday. Shouldn’t be long and I can have something working on a target! if these stupid issues would just quit popping up!
sspbass:
i got all of my goodies in today. I hooked up my fancy new display and its AFU. Its gotta be hardware b/c I can’t get it to properly display the hello world example sketch so I’ll have to keep looking. I’m going to be out tomorrow to my brothers graduation party so most of this will have to wait for Sunday. Shouldn’t be long and I can have something working on a target! if these stupid issues would just quit popping up!
Goodies … as in the slidey pot thing too ? My hope is the last code you saw will work (again). I was trying to collapse (cast) a 4 byte variable into a 2 byte variable and I think that was a problem. I’ve since made all times and things time related, a long unsigned (4 byte) variable. That plus a return to the old ISRs should prove to be a “belt and suspenders” fix. Small formatting errors aside, it should all work wrt recording times and hits and stopping when it’s supposed to. Assuming it does, then it would be good to define as many different CoFs as you can imagine. I’m thinking the UI should work and the next big thing will be getting CoFs stored to (and recalled from) EEPROM. But storing to EEPROM has been done before so getting that to work is just a matter of time and some trial and error. Then, for the most part, the basic timer functions are done. I’d be making 5 or 6 targets and bringing this puppy to the range at that time. Then I can challenge you to finish what I started 15 years ago. :twisted:
As it happens I have to open the lake house this weekend and I don’t have WWW or even cable TV turned on***. So have fun and “see” you Mon.
***MaiTais and getting the PWC out would seem to be the order of the day this weekend.
Just not at the same time. :mrgreen:
http://www.youtube.com/watch?v=XyI-FCOX … e=youtu.be
Unfortunately I’m not going to be able to work on this much this week. I’m going on vacation towards the end of the week as well so time is limited.
It would be nice to just get it functioning.
I like the contrast of the new display ! That said … ugh. The real simple displays for “initializing” and “ready” are AFU. They’re on the wrong lines and not even repeatably so. Assuming there’s no miswire, you might want to see if the controller IC for that display is the same as the old one … or at least the the LCD libraries support it. It’s odd we can read the “Shot Timer 1” and “initializing” and “ready” and the rest is garbage. Obviously the cursor position commands for line 1, line 2, etc … are getting ignored. I’d almost say there’s some timing problem, clocking the data from the arduino to the LCD, except for the aforementioned readble parts. How sure are you that the LCD connector pins are numbered the same as the ones on the old LCD ? Could be the same signals are there, just on different pin numbers ?
As far as the start/stop … the pin13 LED and the serial monitor printouts should still imdicate that it’s functioning … or not. I guess I’d get that working first and then see what’s up with the new LCD. I’d even remove the LCD from the circuit, and get the base working and then add the LCD back into the mix. Nothing has changed code-wise wrt pin7 since ages and ages ago. You might want to write a simple debug sketch that reads the input pins and prints their state to the serial monitor. Prehaps a ground has come loose on the protoboard with the switches on it ? Are hits being detected and their times being sent to the serial monitor (assuming it still times out or “hits out”) ?