Here’s a new way of doing the old FormatData(), which is now replaced with UpdateLCD(), a more proper description of what it really does. Now the code does one “timer is running” display for a single shooter (same ole hit count, hit and split times) and a different one for 2 shooters (same ole A & B hit counts and times). There’s no difference for the “mode” or CoF choosen when the timer is running or stopped.
The CoF choice determines just (ATM) the stop time and allowed number of hits. So look where you did before to change “mode” and you’ll see commented out init’s for number of shooters and CoF. These are now the things to change to alter the timer’s functioning. Later on they will be settable via the new UI. As said before you should now be able to have 1 or 2 shooters for any CoF choosen.
I’ve returned to the single forward-only scroll action using the old pin 6 button. The shock sensor and other button should only do hits now. It might be a good idea to verify that with the timer stopped. That is, with the number of shooters = 2, and the timer stopped, whack the sensor and push the B button, and then start the timer and let’s make sure the hit counts and times are not AFU from the git-go, like there were last time (as seen in the last video).
Other than the above, this new code should act the same was as the old (but w/o the mistakes I hope). I’ve got rid of most of the old bones lying about. If this works the next rev will reduce the memory footprint by getting rid of split time storage and just computing them as/when needed for display. Then we can add the slidey pots, or an emulation of them, depending on what you have for parts, to get the UI up and running. I think work will be slow again tonight so I’ll try to post something coherent about that.
Give the new stuff a good look over and a work out when you can. See if it doesn’t make more sense to read now.
#include <LiquidCrystal.h>
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 9, 8);
// set pin numbers
const int StartPin = 7; // the number of the Start pushbutton pin
const int TargetAPin = 2; // the number of the A targets input pin
const int TargetBPin = 3; // the number of the B targets input pin
const int BuzzerPin = 10; // the number of the buzzer output pin
const int LEDPin = 13; // the number of the LED output pin
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
if(NumShooters == 1)
{
// this is for single A shooter running display
// 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 = ");
TimeConvert(A_Times[A_count-1]); // convert hit time to XX.xx format
// now display the split time btw last hit and prior hit
lcd.setCursor(0, 2);
lcd.print("Split time = ");
TimeConvert(A_splits[A_count-1]); // convert split time
}
else
{
// this is for 2 shooter running display
// display number of hits so far
lcd.clear();
lcd.print(" A: # hits = ");
if(A_count < 10);
{
lcd.print(' ');
}
lcd.print(A_count);
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);
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[A_count-1]); // Now for B times
}
}
else
{
// timer is stopped
if(NumShooters == 1)
{
// this is single shooter review display
lcd.clear();
byte k = 0;
byte j = 8*DisplayIndex; // DI goes 0 to number of displays - 1
if (DisplayIndex == diMax) // diMax indicates last screen of hits
{
byte jj = j + max((A_count % 8),4); // limit lines per number of hits
}
else
{
byte 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 ");
lcd.print(i+1); // send hit # to LCD
lcd.print(": ");
TimeConvert(A_Times[i]); // convert hit time to XX.xx format
lcd.print(" ");
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;
byte j = 4*DisplayIndex; // DI goes 0 to number of displays - 1
if (DisplayIndex == diMax) // diMax indicates last screen of hits
{
byte jj = j + max((A_count,B_count) % 4); // limit lines per number of hits
}
else
{
byte 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);
lcd.print(" A: ");
TimeConvert(A_Times[i]); // convert hit time to XX.xx format
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
}
}
ps - WRT reducing memory usage, what do you think is a real upper time limit for any string or CoF ? Right now it’s 99 secs because if time has to be displayed to 3 digits plus .xx (ie -100.xx), you’ll run out of room on the display. If the time limit can be further reduce to < 65 secs, say 1 min = 60 secs, then the times saved can be 2 bytes long instead of 4, thus halving the memory used to store them. I think you could then have up to 99 hits,though i don’t see you ever really needing that high a number.