Lap Timer TFT Screen help

Hello!
This is my first Arduino project, and my first post on this forum. I'm having a lot of fun learning about Arduino and happy with the progress I've made on my own, but I'm at a place where I think I need help. I would welcome feedback on how to make my question more clear to this group, if needed!

Project Overview:
I'm attempting to create a Laser Lap Timer to use outdoors for a dirtbike event my friends have set for later in the summer. The timer should record:

  • Current Lap Time
  • Current Lap number
  • Last Lap
  • Best Lap

The Hardware being used:

The Library that I'm using for the TFT display is:

Because I'm still learning about how to make things with Arduino, the project lives as a mashup between two existing projects. I took the example project from the TFT_HX8357 library (TFT_Print_Test) and inserted what I believed to be the important portions of code from this Laser Lap Timer project that I found on Gadgetronicx >> [Lap timer for slot cars using Laser and Arduino] Note: because I'm a new user, I'm only allowed to post two links, so I'm unable to link the project here. However it is the first thing that comes up when doing a Google Search for the words in BOLD.

The current state of the project seems to be working, which is very exciting for me! But I'm having two issues:

  • The entire screen refreshes at the rate assigned in the delay portion at the end of the code. It's very abrasive to look at. I'm currently unable to figure out how to only update the portion of the screen that displays lap times, when necessary.
  • I'm struggling to get the times to display in Minute:Second:Millisecond. It appears to just be displaying in Millisecond only.

The code that I've hobbled together is posted below. I'm sure that there may be a more efficient way to achieve what I'm looking for and would welcome feedback on that as well. Currently, as stated before, it's kind of a mish-mash between two existing projects that I've found.


#include <TFT_HX8357.h> // Hardware-specific library

TFT_HX8357 tft = TFT_HX8357();       // Invoke custom library

#define TFT_GREY 0x5AEB // New colour

// laps info
unsigned long currentRunStartMillis;
unsigned long lastRunInMillis;
unsigned long bestRunInMillis;
int currentLap;
unsigned long savedMillis;

// global for display
int sec_val, milli_val;

// laser gate
const int gateSensorPin = 2;    // the number of the gate sensor pin
int gateSensorState;             // the current reading from the sensor
int lastgateSensorState = LOW;   // the previous reading from sensor
unsigned long lastDebounceTime = 0;  // the last time the sensor pin was toggled
int debounceDelay = 50;    // the debounce time; increase if the output flickers



void setup(void) {
  tft.init();
  tft.setRotation(3);
    // pin mode
  pinMode(gateSensorPin, INPUT);
  delay(1); // to late the sensor and laser work, so we wont get the lap triggered.
    // reset params
  currentRunStartMillis = 0;
  lastRunInMillis = 0;
  bestRunInMillis = 0;
  currentLap = 0; 
}

void loop() {
  // read the state of the laser sensor:
  int reading = digitalRead(gateSensorPin);
  // If the switch changed, due to noise or pressing:
  if (reading != lastgateSensorState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  } //end if
 
  // if passes the debounce time
  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading != gateSensorState) {
      gateSensorState = reading;
 
      // If we went low, this mean the beam was broken
      if (gateSensorState == LOW) {
        // save the millis so all the math on it will be done with the same value.
        savedMillis = millis();
        // if its not the first lap
        if (currentLap > 0) {
          // save the last run
          lastRunInMillis = savedMillis - currentRunStartMillis;
          // if last run is faster then best run
          if (lastRunInMillis < bestRunInMillis || bestRunInMillis == 0) {
            //save as best
            bestRunInMillis = lastRunInMillis;
          } //end if
        } //end if
       
        //reset the current
        currentRunStartMillis = savedMillis;
       
        // move lap counter
        currentLap++;
      } //end if
    } //end if
  } //end if
 
 
  // save the reading. Next time through the loop, it'll be the lastgateSensorState:
  lastgateSensorState = reading;
  // Fill screen with random colour so we can see the effect of printing with and without 
  // a background colour defined
  tft.fillScreen(TFT_RED);
  
  // Set "cursor" at top left corner of display (0,0) and select font 2
  // (cursor will move to next line automatically during printing with 'tft.println'
  //  or stay on the line is there is room for the text with tft.print)
  tft.setCursor(20, 0, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
  tft.setTextColor(TFT_BLUE);  tft.setTextSize(3);
  // We can now plot text on screen using the "print" class
  tft.println("CURRENT LAP");
  
  
  tft.setCursor(50, 40, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
  tft.setTextColor(TFT_BLUE);  tft.setTextSize(5);
  // We can now plot text on screen using the "print" class
  tft.println(long(currentRunStartMillis));
  
  
  tft.setCursor(20, 110, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
  tft.setTextColor(TFT_BLUE);  tft.setTextSize(3);
  // We can now plot text on screen using the "print" class
  tft.println("LAST");
 

  tft.setCursor(50, 150, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
  tft.setTextColor(TFT_BLUE);  tft.setTextSize(5);
  // We can now plot text on screen using the "print" class
  tft.println(long(lastRunInMillis));

    tft.setCursor(20, 240, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
  tft.setTextColor(TFT_GREEN);  tft.setTextSize(4);
  // We can now plot text on screen using the "print" class
  tft.println("BEST");

    tft.setCursor(160, 230, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
  tft.setTextColor(TFT_BLUE);  tft.setTextSize(5);
  // We can now plot text on screen using the "print" class
  tft.println(long(bestRunInMillis)),sec_val, milli_val;

  delay(0);
}

// calculate millis into 2 values, seconeds and millis for display
void calcResultFromMillis(unsigned long value, int *sec_val, int *milli_val) {
  *sec_val = int(value / 1000);
  *milli_val = value - *sec_val * 1000;
}


Instead of clearing entire display try to draw a background color rectangle covering deleting text.

I don't see your efforts in the code.

Thank you!

Instead of clearing entire display try to draw a background color rectangle covering deleting text.

I was able to figure this out after trying several things. I'm starting to understand more about how the loop works. I can see now how in my original code having this in the main loop caused that issue.:

 tft.fillScreen(TFT_RED);

My Current Code now shows fillRect prior to any portion of the screen that needs to update.

I am still working to learn more about how to use millis to display Minute:sec:Milli.
The closest I've gotten is in the "Current Lap" portion of the screen, and it currently looks like this (seen also is the fillRect solution as I am using it):

tft.setCursor(20, 0, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
tft.setTextColor(TFT_BLUE,TFT_BLACK);  tft.setTextSize(3);
  // We can now plot text on screen using the "print" class
tft.println("CURRENT LAP");
  
tft.fillRect(10,50,440,60,TFT_BLACK);  
tft.setCursor(50, 40, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
tft.setTextColor(TFT_BLUE,TFT_BLACK);  tft.setTextSize(5);
  // We can now plot text on screen using the "print" class
tft.println(min_val);
tft.setCursor(132, 40, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
tft.setTextColor(TFT_YELLOW,TFT_BLACK);  tft.setTextSize(5);
  // We can now plot text on screen using the "print" class
tft.println(':');
tft.setCursor(150, 40, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
tft.setTextColor(TFT_BLUE,TFT_BLACK);  tft.setTextSize(5);
tft.println(sec_val);
tft.setCursor(232, 40, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
tft.setTextColor(TFT_YELLOW,TFT_BLACK);  tft.setTextSize(5);
  // We can now plot text on screen using the "print" class
tft.println(':');
tft.setCursor(250, 40, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
tft.setTextColor(TFT_BLUE,TFT_BLACK);  tft.setTextSize(5);
tft.println(milli_val);

It works fine enough for what I need, but I've realized that I have two issues:

  • I need to find a way to reset sec_val back to zero once it rolls over past 60. It continues to count individual seconds. As a nicety, it would be great to have the min_val and sec_val display as "00" format as well, currently it displays single digit until it rolls over to double digit.

My current (unsuccessful) effort looks like this:

  lastgateSensorState = reading;
  
  // save current milis
  savedMillis = millis();
 if (sec_val > 60) {
   sec_val = 0;
 }
  • I'm not sure where to begin so I can recall the saved information for Last Lap and Best Lap in the same format.

I will continue to play with it as I have time. Any insights on examples to reference would be appreciated for the two problem points listed above.

I will post the current version of the code in completion below this post, if it's helpful.

Thanks,
Chris

Here is the complete code as it sits currently



#include <TFT_HX8357.h> // Hardware-specific library

TFT_HX8357 tft = TFT_HX8357();       // Invoke custom library

// laps info
unsigned long currentRunStartMillis;
unsigned long lastRunInMillis;
unsigned long bestRunInMillis;
int currentLap;
unsigned long savedMillis;

// global for display
int min_val, sec_val, milli_val;

// laser gate
const int gateSensorPin = 2;    // the number of the gate sensor pin
int gateSensorState;             // the current reading from the sensor
int lastgateSensorState = LOW;   // the previous reading from sensor
unsigned long lastDebounceTime = 0;  // the last time the sensor pin was toggled
unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

void setup(void) {
tft.init();
tft.setRotation(3);
    // pin mode
pinMode(gateSensorPin, INPUT);
delay(1); // to late the sensor and laser work, so we wont get the lap triggered.
 // reset params
currentRunStartMillis = 0;
lastRunInMillis = 0;
bestRunInMillis = 0;
currentLap = 0; 
tft.fillScreen(TFT_BLACK);
}

void loop() {
  // read the state of the laser sensor:
  int reading = digitalRead(gateSensorPin);
  // If the switch changed, due to noise or pressing:
  if (reading != lastgateSensorState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  } //end if
 
  // if passes the debounce time
  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading != gateSensorState) {
      gateSensorState = reading;
 
      // If we went low, this mean the beam was broken
      if (gateSensorState == LOW) {
        // save the millis so all the math on it will be done with the same value.
        savedMillis = millis();
        // if its not the first lap
        if (currentLap > 0) {
          // save the last run
          lastRunInMillis = savedMillis - currentRunStartMillis;
          // if last run is faster then best run
          if (lastRunInMillis < bestRunInMillis || bestRunInMillis == 0) {
            //save as best
            bestRunInMillis = lastRunInMillis;
          } //end if
        } //end if
       
        //reset the current
        currentRunStartMillis = savedMillis;
       
        // move lap counter
        currentLap++;
      } //end if
    } //end if
  } //end if
 
 
  
  lastgateSensorState = reading;
  
  // save current milis
  savedMillis = millis();
 if (sec_val > 60) {
   sec_val = 0;
 }
  
  // if we start the first lap
  if (currentLap > 0) {
    calcResultFromMillis(savedMillis - currentRunStartMillis, &min_val, &sec_val, &milli_val);
  } else {
    calcResultFromMillis(0,  &min_val, &sec_val, &milli_val);
  } //end if
 
tft.setCursor(20, 0, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
tft.setTextColor(TFT_BLUE,TFT_BLACK);  tft.setTextSize(3);
  // We can now plot text on screen using the "print" class
tft.println("CURRENT LAP");
  
tft.fillRect(10,50,440,60,TFT_BLACK);  
tft.setCursor(50, 40, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
tft.setTextColor(TFT_BLUE,TFT_BLACK);  tft.setTextSize(5);
  // We can now plot text on screen using the "print" class
tft.println(min_val);
tft.setCursor(132, 40, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
tft.setTextColor(TFT_YELLOW,TFT_BLACK);  tft.setTextSize(5);
  // We can now plot text on screen using the "print" class
tft.println(':');
tft.setCursor(150, 40, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
tft.setTextColor(TFT_BLUE,TFT_BLACK);  tft.setTextSize(5);
tft.println(sec_val);
tft.setCursor(232, 40, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
tft.setTextColor(TFT_YELLOW,TFT_BLACK);  tft.setTextSize(5);
  // We can now plot text on screen using the "print" class
tft.println(':');
tft.setCursor(250, 40, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
tft.setTextColor(TFT_BLUE,TFT_BLACK);  tft.setTextSize(5);
tft.println(milli_val);

  
tft.setCursor(20, 110, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
tft.setTextColor(TFT_BLUE,TFT_BLACK);  tft.setTextSize(3);
  // We can now plot text on screen using the "print" class
tft.println("LAST");
 
tft.fillRect(50,160,400,60,TFT_BLACK);  
tft.setCursor(50, 150, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
tft.setTextColor(TFT_BLUE,TFT_BLACK);  tft.setTextSize(5);
  // We can now plot text on screen using the "print" class
tft.println(long(lastRunInMillis));

 tft.setCursor(20, 240, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
  tft.setTextColor(TFT_GREEN,TFT_BLACK);  tft.setTextSize(4);
  // We can now plot text on screen using the "print" class
  tft.println("BEST");

  tft.fillRect(160,240,300,60,TFT_BLACK);  
  tft.setCursor(160, 230, 2);
  // Set the font colour to be white with a black background, set text size multiplier to 1
  tft.setTextColor(TFT_BLUE,TFT_BLACK);  tft.setTextSize(5);
  // We can now plot text on screen using the "print" class
  tft.println(int (bestRunInMillis));

  delay(1);
}

// calculate millis into 2 values, seconeds and millis for display
void calcResultFromMillis(unsigned long value, int *min_val, int *sec_val, int *milli_val) {
  *min_val = int(value / 60000);
  *sec_val = int(value / 1000);
  *milli_val = value - *sec_val * 1000;
}


As well as a photo of the project so far

if (sec_val < 10) {
   tft.print('0');
}
tft.println(sec_val);

just take remainder part after divide sec_val to 60 (see last line) :

void calcResultFromMillis(unsigned long value, int *min_val, int *sec_val, int *milli_val) {
  *min_val = int(value / 60000);
  *sec_val = int(value / 1000);
  *milli_val = value - *sec_val * 1000;
  *sec_val = *sec_val % 60;
}

Thank you very much for your help @b707 ! I am fully up and running!

I do have some further general questions about the code and moments where it could be structured more efficiently, however I will post those questions in a different thread as they are not really related to the Display any longer.

Very much appreciated!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.