// For: https://forum.arduino.cc/t/timer-has-random-error/1037211/
//
// Wokwi project with sketch from user: https://wokwi.com/projects/344260957282763346
//
// This Wokwi project: https://wokwi.com/projects/344339167081136722
//
// 2 Oct 2022, Version 1, by Koepel, Public Domain.
// Now purely based on millis().
// Removed the RTC.
// NOT READY, JUST A PROOF OF CONCEPT
//
// To do:
// 1. (optional)
// The accuracy can be improved by using a interrupt
// for the button. In case I2C or something slow is used.
// Not needed at this moment.
//
// 2.
// Entering the number of times is not needed.
// The user can just stop the input by pressing 'D' twice.
//
// 3.
// Make a Finite State Machine
//
// 4.
// Use sprintf() in showTimes().
//
// 5.
// Add custom characters. For example a running person.
//
//
#include <LiquidCrystal.h>
#include <Keypad.h>
const int redLedPin = A1;
const int greenLedPin = A0;
const int buttonPin = 3; // pin 3 is also a interrupt input
const int rs = 13, en = 12, d4 = 11, d5 = 10, d6 = 9, d7 = 8;
LiquidCrystal lcd( rs, en, d4, d5, d6, d7);
const byte ROWS = 4; // four rows
const byte COLS = 4; // four columns
char hexaKeys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = { A2, A3, 0, 1}; // connect to the row pinouts of the keypad
byte colPins[COLS] = { 4, 7, 6, 5}; // connect to the column pinouts of the keypad
Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
// The 'startMillis' is the millis value of time zero.
// The tables contain the times in milliseconds since start as a 'long'.
// The index of an array starts at zero, but that is shown as "time 1."
unsigned long startMillis;
#define MAX_TIMES 20 // maximum number of times
long measuredTimes[MAX_TIMES]; // the table with measured times
int index; // current index in the table when running
// PRE-FILLED WITH VALUES FOR TESTING
long setTimes[MAX_TIMES] = // the table with the set times
{
10000L, 20000L, 30000L,
};
int nTimes = 3; // The number of times determined runtime
unsigned long previousMillisDisplay;
const unsigned long intervalDisplay = 200UL;
void setup()
{
pinMode( redLedPin, OUTPUT); // set to output and LOW
pinMode( greenLedPin, OUTPUT); // set to output and LOW
pinMode( buttonPin, INPUT_PULLUP);
lcd.begin( 20, 4);
lcd.clear();
lcd.setCursor( 6, 1);
lcd.print( F("Welcome"));
delay( 5000);
Enter_Times();
lcd.clear();
lcd.setCursor( 0, 0);
lcd.print( F("The first press of"));
lcd.setCursor(0, 1);
lcd.print( F("the button starts"));
lcd.setCursor( 0, 2);
lcd.print( F("the race ..."));
while( !isButtonPressed( buttonPin)); // wait for the button to be pressed
startMillis = millis(); // timestamp for starting point
lcd.clear();
lcd.setCursor( 5, 2);
lcd.print( F("Running !"));
lcd.setCursor( 0, 3);
lcd.print( F("press at 10,20,30 s"));
}
void loop()
{
// The millis values should always be 'unsigned long' to always work,
// even during a rollover of millis().
// Once the elapsed millis value is calculated, it can be converted to 'long'.
// It is more convenient for the sketch to use 'long'.
unsigned long currentMillis = millis();
unsigned long elapsedMillis = currentMillis - startMillis; // rollover-proof
long timeSinceStart = (long) elapsedMillis; // a 'long' to be used in the sketch
if( isButtonPressed( buttonPin))
{
// Fill the array with the current time.
measuredTimes[index] = timeSinceStart;
// Calculate the difference and show it on the display.
// Line 1: The set time
// Line 2: The measured time
// Line 3: The difference
showTime( 1, index, setTimes[index], false);
showTime( 2, index, measuredTimes[index], false);
showTime( 3, index, measuredTimes[index] - setTimes[index], true);
// Go to the next position in the array.
index++;
if( index >= MAX_TIMES)
{
index = MAX_TIMES - 1; // hang around at the last position
}
}
// A 7-segment display is fast, but the liquid crystals of a LCD display is slow.
// Using a millis-timer to update the current time.
// This code reduces the accuracy for the time to capture the button.
if( currentMillis - previousMillisDisplay >= intervalDisplay)
{
previousMillisDisplay = currentMillis;
showTime( 0, -1, (long) elapsedMillis, false); // -1 to avoid showing index
}
}
void Enter_Times()
{
lcd.setCursor( 0, 0);
lcd.print( F("Enter the times"));
lcd.setCursor( 0, 1);
lcd.print( F("* for s and ms"));
lcd.setCursor( 0, 2);
lcd.print( F("C for backspace"));
lcd.setCursor( 0, 3);
lcd.print( F("D for next and ready"));
delay( 5000);
lcd.clear();
lcd.setCursor( 0, 0);
lcd.print( F("Sorry"));
lcd.setCursor( 0, 1);
lcd.print( F("not implemented"));
delay( 5000);
}
// showTime
// --------
//
// Show the times on a line of the display, with optional sign
//
// Parameters:
// row : 0 = top row, 3 = bottom row
// indx : index, starting from zero. Zero is displayed as time "1".
// A value of -1 for not showing the index and not the dot.
// t : the time in milliseconds as a 'long'
// sign : true to show '+' or '-' sign. false for no sign.
//
// Format : nn. +mm:ss.xxx
// Examples: 1. 00:10.000
// 12. -05:42.998
//
// A sprintf() should make this function better
//
void showTime( int row, int indx, long t, bool sign)
{
// Split the variable into minutes, seconds, milliseconds.
// The index starts at 0, that is time "1", and so on.
lcd.setCursor( 0, row);
int n = 0; // keep track of the number of characters written
if( indx == -1) // not showing the index ?
{
n += lcd.print( F(" "));
}
else
{
int number = indx + 1; // show index zero as time "1" and so on.
if( number < 10)
n += lcd.print( F(" "));
n += lcd.print( number);
n += lcd.print( F(". "));
}
if( sign)
{
if( t < 0)
{
n += lcd.print( F("-"));
}
else
{
n += lcd.print( F("+"));
}
}
else
{
n += lcd.print( F(" "));
}
if( t < 0)
t = -t; // make 't' always positive
int minutes = t / ( 60L * 1000L );
int seconds = ( t / 1000L ) % 60L;
int milliseconds = t % 1000L;
// safety check for maximum
if( minutes > 99)
{
n += lcd.print( F("##:##.###"));
}
else
{
if( minutes < 10)
n += lcd.print( F("0"));
n += lcd.print( minutes);
n += lcd.print( F(":"));
if( seconds < 10)
n += lcd.print( F("0"));
n += lcd.print( seconds);
n += lcd.print( F("."));
if( milliseconds < 10)
n += lcd.print( F("0"));
if( milliseconds < 100)
n += lcd.print( F("0"));
n += lcd.print( milliseconds);
}
// clear the rest of the line.
for( int i=0; i<(20-n); i++)
{
lcd.print( F(" "));
}
}
// isButtonPressed (half-a-debounce)
// ---------------------------------
//
// For maximum accuracy, the debouncing is minimized when the button is pressed.
// Two samples are taken, to filter out a glitch.
// If both samples are LOW, then the button is assumed to be pressed.
// The button is accepted as released with it is steady HIGH for at least 50 ms.
bool debounceMode = false; // for debouncing the release of the button
unsigned long debounceMillis;
bool isButtonPressed( int pin)
{
// Take two samples, to filter out a glitch.
int sample1 = digitalRead( pin);
int sample2 = digitalRead( pin);
if( !debounceMode)
{
// check if the button is pressed
if( sample1 == LOW and sample2 == LOW)
{
// As soon as the button is accepted as pressed,
// the debounce mode for releasing is started.
debounceMode = true;
debounceMillis = millis();
return( true); // immediate return with true (button pressed)
}
}
else
{
// in debounce mode
if( sample1 == LOW or sample2 == LOW)
{
// Still something low, not a valid high yet.
// Restart the timer
debounceMillis = millis();
}
else
{
if( millis() - debounceMillis >= 50)
{
// Steady HIGH for at least 50ms, that is accepted.
debounceMode = false;
}
}
}
return( false);
}
uno:A5.2
uno:A4.2
uno:AREF
uno:GND.1
uno:13
uno:12
uno:11
uno:10
uno:9
uno:8
uno:7
uno:6
uno:5
uno:4
uno:3
uno:2
uno:1
uno:0
uno:IOREF
uno:RESET
uno:3.3V
uno:5V
uno:GND.2
uno:GND.3
uno:VIN
uno:A0
uno:A1
uno:A2
uno:A3
uno:A4
uno:A5
lcd1:VSS
lcd1:VDD
lcd1:V0
lcd1:RS
lcd1:RW
lcd1:E
lcd1:D0
lcd1:D1
lcd1:D2
lcd1:D3
lcd1:D4
lcd1:D5
lcd1:D6
lcd1:D7
lcd1:A
lcd1:K
r1:1
r1:2
r2:1
r2:2
btn1:1.l
btn1:2.l
btn1:1.r
btn1:2.r
rgb1:R
rgb1:COM
rgb1:G
rgb1:B
r3:1
r3:2
r4:1
r4:2
r5:1
r5:2
keypad1:R1
keypad1:R2
keypad1:R3
keypad1:R4
keypad1:C1
keypad1:C2
keypad1:C3
keypad1:C4