I am creating a timer for a race. I have a photosensor that has a laser pointed to so when someone crosses the finish, it trips the sensor, and the system logs the racer's time. I am using millis() to time the race, but I need the timer to start when I push the button. I have tried using edge detection to start the timer, but the timer starts when the program starts, not when the program starts. The only other problem I have is that if the time since the race started goes over the time between when the program starts vs when the race starts, the time goes into the negatives. Any help is much appreciated, I have been googling how to do this all day.
Code:
const int sensorPin = A0;
int sensorValue = 0;
int place = 1;
float timer;
int prevButtonState = 0;
float personTime;
const int greenPin = 3;
const int redPin = 4;
const int switchPin = 5;
int currentMillis = 0;
int prevMillis = millis;
int buttonPushCounter = 0;
int buttonState = 0;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(greenPin, OUTPUT);
pinMode(redPin, OUTPUT);
pinMode(switchPin, INPUT);
}
void flash(){
digitalWrite(greenPin, HIGH);
delay(100);
digitalWrite(greenPin, LOW);
}
void runTimer(){
timer = currentMillis - prevMillis;
float personTime = timer/1000;
sensorValue = analogRead(sensorPin);
//Serial.println(sensorValue);
delay(100);
if(sensorValue < 250){
Serial.print("PLACE ");
Serial.print(place);
Serial.print(" TIME: ");
Serial.print(personTime, 3);
Serial.println(" SECONDS");
flash();
place++;
}
}
void waiting(){
Serial.println("WAITING");
}
void loop() {
// put your main code here, to run repeatedly:
buttonState = digitalRead(switchPin);
if(buttonState != prevButtonState){
if(buttonState == HIGH){
buttonPushCounter ++;
}
}
if(buttonPushCounter % 2 == 0){
runTimer();
}
else{
waiting();
}
}
Or, you could try this. Its a stop watch class that times in 1/10 second chunks. It can be set to smaller chunks, but this gives you some time to do other Arduino things while racing.
#include <mechButton.h>
#include <timeObj.h>
// *****************************************
// A nifty stopwatch class..
// *****************************************
#define MS_PER_COUNT 100
class stopWatch : public idler {
public:
stopWatch(void);
~stopWatch(void);
void clickStart(void);
void clickStop(void);
void clickReset(void);
float readTime(void);
virtual void idle(void);
timeObj mTimer;
int mCounts;
};
stopWatch::stopWatch(void) {
mCounts = 0;
mTimer.setTime(MS_PER_COUNT);
}
stopWatch::~stopWatch(void) { }
void stopWatch::clickStart(void) {
mTimer.start();
mCounts = 0;
hookup();
}
void stopWatch::clickStop(void) { mTimer.reset(); }
void stopWatch::clickReset(void) { mCounts = 0; }
float stopWatch::readTime(void) { return (mCounts*MS_PER_COUNT)/1000.0; }
void stopWatch::idle(void) {
if (mTimer.ding()) {
mTimer.stepTime();
mCounts++;
}
}
// *****************************************
// Begin main program..
// *****************************************
#define BTN_PIN 2
stopWatch stopwatchOne;
mechButton watchButton(BTN_PIN);
bool running;
void setup(void) {
Serial.begin(57600); // Serial to see what's going on.
running = false; // We are not running a race at the moment.
watchButton.setCallback(btnClicked); // When the button is clicked, it'll call this function.
}
// Button clicked, do action!
void btnClicked(void) {
if (!watchButton.trueFalse()) { // If the button has been ground. (pressed)
if (running) { // If we are currently runnig a race..
stopwatchOne.clickStop(); // We click stop on the stopwatch.
running = false; // We are no longer running.
Serial.print("Finish time : "); // Lets see the results.
Serial.print(stopwatchOne.readTime(),1); //
Serial.println(" Sec."); //
} else { // Else, we are NOT running a race..
stopwatchOne.clickStart(); // Start the race!
Serial.println("And they're off!"); // Tell the users.
running = true; // Note that the race is running.
}
}
}
void loop(void) {
idle(); // idle() lets things like the button & stopwatch run.
if (running) { // If we are running..
Serial.print(stopwatchOne.readTime(),1); // We can see what's going on.
Serial.println(" Sec."); //
}
}
If you'd like to try this, you will need to grab LC_baseTools from the IDE library manager to get it to compile.
I notice that mCounts is declared as an int, and MS_PER_COUNT looks like it would default to an int.
Wouldn't this lead to a problem after 32.8 seconds?
I tried running this program on the Arduino, but I couldn't get any input on the Serial Monitor. I checked to be sure everything was connected properly and the serial was set up correctly, but I didn't see any data. I also made sure that the pin for the button was defined. Still, thank you so much for the help! I really appreciate it.
Thanks again for all the help! I fixed the problem where the timer value would suddenly turn negative around 30 seconds into the race, but I am still having difficulty with getting the timer to start when I press the button, not when the program starts running.
Updated code:
const int sensorPin = A0;
int sensorValue = 0;
int place = 1;
float timer;
int prevButtonState = 0;
float personTime;
const int greenPin = 3;
const int redPin = 4;
const int switchPin = 7;
unsigned long currentMillis = 0;
unsigned long prevMillis = millis;
int buttonPushCounter = 0;
int buttonState = 0;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(greenPin, OUTPUT);
pinMode(redPin, OUTPUT);
pinMode(switchPin, INPUT);
prevButtonState = digitalRead(switchPin);
}
void flash(){
digitalWrite(greenPin, HIGH);
delay(100);
digitalWrite(greenPin, LOW);
}
void runTimer(){
currentMillis = millis();
timer = currentMillis - prevMillis;
float personTime = timer/1000;
sensorValue = analogRead(sensorPin);
//Serial.println(sensorValue);
delay(50);
if(sensorValue < 175){
Serial.print("PLACE ");
Serial.print(place);
Serial.print(" TIME: ");
Serial.print(personTime, 3);
Serial.println(" SECONDS");
flash();
place++;
}
}
void waiting(){
Serial.println("WAITING");
}
void loop() {
buttonState = digitalRead(switchPin);
if(buttonState != prevButtonState){
if(buttonState == HIGH){
buttonPushCounter ++;
delay(50);
}
}
prevButtonState = buttonState;
if(buttonPushCounter % 2 == 0){
runTimer();
}
else{
waiting();
}
}
Fixed. Thank you for the help! Is there any way I could run the function runTimer() I created for my program with this program?
code:
void runTimer(){
currentMillis = millis();
timer = currentMillis - prevMillis;
float personTime = timer/1000;
sensorValue = analogRead(sensorPin);
//test sensor value for calibration on race location
//Serial.println(sensorValue);
delay(50);
if(sensorValue < 175){ // value can be changed based on racing environment
Serial.print("PLACE ");
Serial.print(place); //print the place of the person who crossed the line
Serial.print(" TIME: ");
Serial.print(personTime, 3); // print the person's time to the millisecond (done by dividing the milliseconds elapsed by 1000)
Serial.println(" SECONDS");
flash();
place++;
}
}
variables used:
const int sensorPin = A0;
int sensorValue = 0;
int place = 1;
float timer;
float personTime;
const int greenPin = 2;
unsigned long currentMillis = 0;
unsigned long prevMillis = millis;
void flash(){
digitalWrite(greenPin, HIGH);
delay(100);
digitalWrite(greenPin, LOW);
}
It might be a little tricky, I am working on that right now. If there isn't a way to run this function on this great program, that's totally fine. But if it can, that would be amazing! Thanks again for all your great help.
You can use an interrupt and make every tick the desired lenght (1/10/100ms).
And then using variables to count the ticks.
The debounce is made with a while loop.
If you have bad bouncing or need a longer treshold
then you can add a capacitator over the button (output).
Its getting little complicated to hold all in my head without anything to test it on. But here's a shot at it. Hopefully this can at least help.
#include <mechButton.h>
#include <timeObj.h>
// *****************************************
// A nifty stopwatch class..
// *****************************************
#define MS_PER_COUNT 100
class stopWatch : public idler {
public:
stopWatch(void);
~stopWatch(void);
void clickStart(void);
void clickStop(void);
void clickReset(void);
float readTime(void);
virtual void idle(void);
timeObj mTimer;
int mCounts;
};
stopWatch::stopWatch(void) {
mCounts = 0;
mTimer.setTime(MS_PER_COUNT);
}
stopWatch::~stopWatch(void) { }
void stopWatch::clickStart(void) {
mTimer.start();
mCounts = 0;
hookup();
}
void stopWatch::clickStop(void) { mTimer.reset(); }
void stopWatch::clickReset(void) { mCounts = 0; }
float stopWatch::readTime(void) { return (mCounts*MS_PER_COUNT)/1000.0; }
void stopWatch::idle(void) {
if (mTimer.ding()) {
mTimer.stepTime();
mCounts++;
}
}
// *****************************************
// How about a one shot LED class?
// *****************************************
class oneShot : public idler {
public :
oneShot(int pin);
~oneShot(void);
void flash(float ms);
virtual void idle(void);
timeObj timer;
bool init;
int pinNum;
};
oneShot::oneShot(int pin) {
init = false;
pinNum = pin;
}
oneShot::~oneShot(void) { }
void oneShot::flash(float ms) {
if (!init) {
pinMode(pinNum,OUTPUT);
hookup();
init = true;
}
timer.setTime(ms,true);
digitalWrite(pinNum, HIGH);
}
void oneShot::idle(void) {
if (timer.ding()) {
digitalWrite(pinNum, LOW);
timer.reset();
}
}
// *****************************************
// Begin main program..
// *****************************************
#define BTN_PIN 3
const int sensorPin = A0;
const int greenPin = 2;
stopWatch stopwatchOne;
mechButton watchButton(BTN_PIN);
oneShot theLED(greenPin);
bool running;
int place;
void setup(void) {
Serial.begin(9600); // Serial to see what's going on.
running = false; // We are not running a race at the moment.
watchButton.setCallback(btnClicked); // When the button is clicked, it'll call this function.
theLED.flash(100); // Flash the LED!
}
// Button clicked, do action!
void btnClicked(void) {
if (!watchButton.trueFalse()) { // If the button has been ground. (pressed)
if (running) { // If we are currently runnig a race..
stopwatchOne.clickStop(); // We click stop on the stopwatch.
running = false; // We are no longer running.
Serial.print("Finish time : "); // Lets see the results.
Serial.print(stopwatchOne.readTime(),1); //
Serial.println(" Sec."); //
} else { // Else, we are NOT running a race..
stopwatchOne.clickStart(); // Start the race!
Serial.println("And they're off!"); // Tell the users.
place = 1; // Setup for the winner.
running = true; // Note that the race is running.
}
}
}
// Standard loop().
void loop(void) {
int sensorValue;
float seconds;
idle(); // idle() lets things like the button & stopwatch run.
if (running) { // If we are running..
sensorValue = analogRead(sensorPin); // Read the sensor.
if(sensorValue < 175) { // value can be changed based on racing environment
seconds = stopwatchOne.readTime(); // Grab the time.
theLED.flash(100); // Flash the LED!
Serial.print("PLACE "); // Label.
Serial.print(place); // Print the place of the person who crossed the line
Serial.print(" TIME: "); // Label.
Serial.print(seconds,3); // print the person's time.
Serial.println(" SECONDS"); // Label.
place++; // Bump up place.
while(analogRead(sensorPin)<175) { // Hold 'till the contestent passes.
idle(); // Make sure stuff runs.
}
}
}
}
I think you'll eventually need state change detection here. As written, as long as the if() is true the statement block will be executed. This would record times for the entire length time the sensor is blocked.
Hello
I´ve found a similar, but "class-less", solution in my sketch box. With respect to your project sheet I did some mods in this sketch for you. Try and check. Your shall have to modify the pin numbers before.
#define ProjectName "Start a timer when button is pressed"
const byte StartPin {A0};
const byte PhotoSensorPin {A1};
const byte redLedPin {2};
const byte greenLedPin {3};
const unsigned long BlinkRate {333};
const int heartBeat { LED_BUILTIN};
enum {Start, PhotoSensor};
enum {NoClick, Click};
struct INPUT_ {
int pin;
bool state;
bool click;
} button [] {
{StartPin, 0, 0},
{PhotoSensorPin, 0, 0},
};
enum {Red, Green};
struct OUTPUT_ {
byte pin;
} led[] {redLedPin, greenLedPin};
enum {Led, Arrival};
struct TIMER {
unsigned long stamp;
unsigned long duration;
unsigned long start;
} timer[] {
{0, BlinkRate, 0},
{0, 0, 0},
};
unsigned long getTime(TIMER &timer) {
return (millis() - timer.start);
}
void buttonRead() {
static unsigned long buttonReadMillis = 0;
const unsigned long buttonReadDebounceTime = 50;
if (millis() - buttonReadMillis >= buttonReadDebounceTime) {
buttonReadMillis = millis();
for (auto &Button : button) {
bool state = !digitalRead(Button.pin);
if (Button.state != state) {
Button.state = state;
if (state) Button.click = state;
}
}
}
}
bool buttonClick(INPUT_ &button) {
bool back = 0;
if (!button.click) return (back);
back = button.click;
button.click = 0;
return (back);
}
void blinkLed (bool color) {
if (millis() - timer[Led].stamp >= timer[Led].duration) {
timer[Led].stamp = millis();
digitalWrite(led[color].pin, digitalRead(led[color].pin) ? 0 : 1);
}
}
void personTime(unsigned long arrivalTime) {
static int place = 1;
if (!arrivalTime) {
Serial.println(F("\n\n< the race is started - waiting for objects >"));
place = 1;
// return;
}
Serial.print(" - PLACE ");
Serial.print(place);
Serial.print(" TIME: ");
Serial.print((float)arrivalTime / 1000, 3);
Serial.println(" SECONDS");
place++;
}
void input() {
buttonRead();
if (buttonClick(button[Start])) timer[Arrival].start = 0, digitalWrite(led[Green].pin, LOW);
if (!timer[Arrival].start) {
if (buttonClick(button[PhotoSensor])) {
digitalWrite(led[Red].pin, LOW);
timer[Arrival].start = millis();
personTime(getTime(timer[Arrival]));
}
}
}
void output() {
blinkLed(timer[Arrival].start);
if (buttonClick(button[PhotoSensor])) personTime(getTime(timer[Arrival]));
}
void setup() { // put your setup code here, to run once:
Serial.begin(9600);
pinMode (heartBeat, OUTPUT);
for (auto make : button) pinMode (make.pin, INPUT_PULLUP);
for (auto make : led) pinMode (make.pin, OUTPUT);
Serial.print(F(".\nlet´s go! "));
}
void loop() { // put your main code here, to run repeatedly:
digitalWrite(heartBeat, millis() / 500 % 2);
input();
output();
}