Push Button Timer Help

No. I want to start a stopwatch when the button is pressed and reset it when the button is pressed a second time.

Is it to display all along, just updating every second?

Yes.

Then you will have to establish another variable or two and keep track of that vs. millis()

mark event millis(), print a '0',
has millis() advanced 1000 yet?

I'm having trouble finding a place to start the timer when the button is pushed. this is what I've got so far:

#include <Adafruit_LiquidCrystal.h>
unsigned long startTime = 0;
unsigned long currentTime = 0;
int indicator = 3;
const long count = 1000;
unsigned long previousTime = 0;
unsigned long markTime;
unsigned long elapsedTime;
unsigned long lockoutmark;  // pb-related
const unsigned long debounceTime = 250; // debounceTime, used w/ lockout
const byte T1 = 8;  // onboard
const unsigned long testTime = 1500;  // time on
boolean lockout;  // if true then PB doesn't get checked
boolean tripped;  // pb hit, prep Update for new pointers etc.
const byte pb1pin = A0;
byte pb1state;
boolean engaged;

Adafruit_LiquidCrystal lcd(0);

void setup ()
{
  Serial.begin(9600);
  pinMode(pb1pin,INPUT_PULLUP);
  pinMode(T1, OUTPUT);
  
  lockout = false;
  tripped = true;
  engaged = false;
  markTime = millis();
  
  lcd.begin(16, 2);
  lcd.clear();
}

void loop ()
{
  elapsedTime = millis();  
  if(!lockout)  // ok to read
  {
    pb1state = digitalRead(pb1pin);
    if (!pb1state)  // pb made active
    {
      lockoutmark = millis();  // record millis when pb went LOW
      lockout = true;
      tripped = true;
      if (engaged)
      {
        digitalWrite(T1, LOW);
        engaged = false;
      }
      else
      {
        digitalWrite(T1, HIGH);
        markTime = millis();
        engaged = true;
      }
    }
  }
  
  elapsedTime = millis();
  if ((lockout) && ((elapsedTime - lockoutmark) >= debounceTime))
  {
    lockout = false; // lockout done, threshold met
    tripped = false;
  }

  if (engaged)
  {
    elapsedTime = millis();
    indicator++;
    if((elapsedTime - markTime) >= testTime) // determine whether an Update() is due
    {
      digitalWrite(T1,LOW);
      engaged = false;
    }
  
  }

  if ((indicator & 2) == 0) {
    currentTime = millis();
   	 	lcd.setCursor(0,0);
   	 	lcd.print((currentTime - )/1000);
    	lcd.print(':');
    	lcd.print((currentTime- ) / 100);
  }
  
}

My stopwatch is here at the bottom.

Here is a reduced and modified version with a program-structure listing

/* Programm-structure Overview
 *  loop 
 *   - calls GetToggleSwitchState()
 *             - calls TimePeriodIsOver(buttonDebounceTimer, buttonDebounceTime) ) {
 *             
 *   - calls resetDigitsToZero();          
 *             
 *   - calls CountUp();          
 *           - calls TimePeriodIsOver(MyStopWatchTimer,100)
 *           
 *   - calls printStopWatchTime();
 *           - calls TimePeriodIsOver(MyPrintTimer, 50)
 *           - calls clearSerialMonitor();
 */

It has a special serial monitor-printing where the output seems to be steady at the bottom of the serial monitor-window
In reality the code prints so many empty lines than the prevoius lines are scrolled out of sight very fast
This gives an impression of a steady display
The print-intervall is 50 milliseconds to show even 10th-seconds
it can be changed in this line of code
If the serial monitor is flieckering reduce the applications that run on your computer

void printStopWatchTime() {
  if ( TimePeriodIsOver(MyPrintTimer, 50) ) { // <<== change here to 1000

so here is the complete sketch

/* Programm-structure Overview
 *  loop 
 *   - calls GetToggleSwitchState()
 *             - calls TimePeriodIsOver(buttonDebounceTimer, buttonDebounceTime) ) {
 *             
 *   - calls resetDigitsToZero();          
 *             
 *   - calls CountUp();          
 *           - calls TimePeriodIsOver(MyStopWatchTimer,100)
 *           
 *   - calls printStopWatchTime();
 *           - calls TimePeriodIsOver(MyPrintTimer, 50)
 *           - calls clearSerialMonitor();
 */
 
#define ProjectName "Stopwatch demonstration"

// define IO-states for inputs with pull-up-resistors
// pull-up-resistors invert the logig
#define unPressed HIGH
#define pressed   LOW

const byte ToggleButtonPin = 7;

bool activationMode = false;
unsigned long myCounter = 0;
unsigned long MyStopWatchTimer = 0;
unsigned long MyPrintTimer = 0;

boolean stopWatchRunning = false;
byte StopWatch10thSecCounter = 0;
byte SW_Sec  = 0;
byte SW_Min  = 0;
byte SW_Hour = 0;

void setup() {
  Serial.begin(115200); // adjust baudrate in the serial monitor to match the number
  Serial.println( F("Setup-Start") );
  Serial.println( F("Press button connected to IO-pin 7") );
  Serial.println( F("to Start / Stop stopwatch") );

  pinMode (LED_BUILTIN, OUTPUT);  // used for indicating logging active or not
  digitalWrite(LED_BUILTIN, LOW);
  // wire button between IO-pin and GND
  // Pull-up-resistor inverts the logic
  // unpressed: IO-pin detects HIGH
  // pressed:   IO-Pin detects LOW
  pinMode(ToggleButtonPin, INPUT_PULLUP);
  MyStopWatchTimer = millis();
}


void loop () {
  activationMode = GetToggleSwitchState(); // must be executed all the time
  if (activationMode) { // if button pressed to start stopwatch
    //printStopWatchTime();
    if (!stopWatchRunning) { // if stopwatch not yet running = it is really starting
      resetDigitsToZero();
      stopWatchRunning = true; // set flag to true
    }
    CountUp();
    printStopWatchTime();
  }
  else {
    if (stopWatchRunning) {
      stopWatchRunning = false;
    }
  }
}

void resetDigitsToZero() {
  StopWatch10thSecCounter = 0;
  SW_Sec  = 0;
  SW_Min  = 0;
  SW_Hour = 0;
}


void clearSerialMonitor() {
  for (byte i = 0; i < 30; i++) {
    Serial.print("\n");
  }
}

void printStopWatchTime() {
  if ( TimePeriodIsOver(MyPrintTimer, 50) ) { // change here to 1000

    clearSerialMonitor();
    Serial.println("Stopwatch is running");
    Serial.println("Counting up");
    Serial.print(SW_Hour);
    Serial.print(":");

    Serial.print(SW_Min);
    Serial.print(":");
    Serial.print(SW_Sec);
    Serial.print(",");
    Serial.println(StopWatch10thSecCounter);
  }
}


void CountUp() {

  if ( TimePeriodIsOver(MyStopWatchTimer, 100) ) { // check if 0,1 seconds of time have passed by
    StopWatch10thSecCounter++;

    if (StopWatch10thSecCounter == 10) { // if 10 x 0,1 seconds
      SW_Sec++; // increment seconds
      StopWatch10thSecCounter = 0;
    }

    if (SW_Sec == 60) { // check if seconds reached 60
      SW_Min++;         // if yes increment minutes
      SW_Sec = 0;       // set seconds back to 0
    }

    if (SW_Min == 60) { // check if minutes reached 60
      SW_Hour++;        // if yes increment hours
      SW_Min = 0;       // set minutes to 0
    }
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN) ); // read state of LED-output and set to invertetd state
  }
}

bool GetToggleSwitchState() {
  // "static" makes variables persistant over function calls
  static bool toggleState     = false;
  static bool lastToggleState = false;

  static byte buttonStateOld = unPressed;
  static unsigned long buttonScanStarted  =  0;
  unsigned long buttonDebounceTime = 20;
  static unsigned long buttonDebounceTimer;

  byte buttonStateNew;

  if ( TimePeriodIsOver(buttonDebounceTimer, buttonDebounceTime) ) {
    // if more time than buttonDebounceTime has passed by
    // this means let pass by some time until
    // bouncing of the button is over
    buttonStateNew = digitalRead(ToggleButtonPin);

    if (buttonStateNew != buttonStateOld) {
      // if button-state has changed
      buttonStateOld = buttonStateNew;
      if (buttonStateNew == unPressed) {
        // if button is released
        toggleState = !toggleState; // toggle state-variable
      } // the attention-mark is the NOT operator
    }   // which simply inverts the boolean state
  }     // !true  = false   NOT true  is false
  //       !false = true    NOT false is true
  return toggleState;
}


// ignore at first
// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    //startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    startOfPeriod += TimePeriod;
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

best regards Stefan

Okay, I've got what I want mostly worked out, but I don't know how to get the timer to display tenths and hundredths of seconds for more granularity. Also, I'm not sure why.. but sometimes when I hit the button nothing happens.

#include <Adafruit_LiquidCrystal.h>
bool indicator = 1;
unsigned long startTime = 0;
unsigned long currentTime = 0;
const long count = 1000;
unsigned long previousTime = 0;
unsigned long markTime;
unsigned long elapsedTime;
unsigned long lockoutmark;  // pb-related
const unsigned long debounceTime = 250; // debounceTime, used w/ lockout
const byte T1 = 8;  // onboard
const unsigned long testTime = 3000;  // time on
boolean lockout;  // if true then PB doesn't get checked
boolean tripped;  // pb hit, prep Update for new pointers etc.
const byte pb1pin = A0;
byte pb1state;
boolean engaged;
const byte powerPin = 9;

Adafruit_LiquidCrystal lcd(0);

void setup ()
{
  pinMode(powerPin, OUTPUT);
  Serial.begin(9600);
  pinMode(pb1pin,INPUT_PULLUP);
  pinMode(T1, OUTPUT);
  lockout = false;
  tripped = true;
  engaged = false;
  markTime = millis();
  lcd.begin(16, 2);
  lcd.clear();
  digitalWrite(powerPin, HIGH);
  delay(500);
  digitalWrite(powerPin, LOW);
}

void loop ()
{
  elapsedTime = millis();  
  if(!lockout)  // ok to read
  {
    pb1state = digitalRead(pb1pin);
    if (!pb1state)  // pb made active
    {
      indicator = !indicator;
      lockoutmark = millis();  // record millis when pb went LOW
      lockout = true;
      tripped = true;
      if (engaged)
      {
        digitalWrite(T1, LOW);
        engaged = false;
      }
      else
      {
        digitalWrite(T1, HIGH);
        markTime = millis();
        engaged = true;
      }
    }
  }
  
  elapsedTime = millis();
  if ((lockout) && ((elapsedTime - lockoutmark) >= debounceTime))
  {
    lockout = false; // lockout done, threshold met
    tripped = false;
  }

  if (engaged)
  {
    startTime = millis();
    elapsedTime = millis();
    if (((elapsedTime - markTime) < testTime) && indicator == 0)
    {
      lcd.setCursor(0,0);
      lcd.clear();
      lcd.print("Preinfusion");
    }
    if((elapsedTime - markTime) >= testTime) // determine whether an Update() is due
    {
      digitalWrite(T1,LOW);
      engaged = false;
    }
  
  }
  Serial.print(indicator);
  if (indicator == 0 && engaged == false) {
    currentTime = millis();
    
    	lcd.print("          ");
   	 	lcd.setCursor(0,0);
    	lcd.print("Time: ");
   	 	lcd.print(((currentTime - startTime ) + 3000) /1000);

  }
  else {
    lcd.setCursor(0,0);
    lcd.print("                 ");
  }
  
}

How will you ever achieve a 10 milliseconds resolution when your debounce time is 25 times longer?

const unsigned long debounceTime = 250; // debounceTime, used w/ lockout

1/100th seconds = 10 ms resolution is not achievable if you print out at 9600 baud and additionally send data to an I2C-LCD

This line of code rounds to seconds

      lcd.print(((currentTime - startTime ) + 3000) /1000);

you will have to modify this.

printing a single character at 9600 baud needs 1 ms so after printing 10 characters you are above 10 ms

The debounce time filters out the mechanical rattling in the pushbutton.
If it's not responding to a click, I speculate that it may be during the debounce time. You can dial back on that, but at some point you will encounter the rattling-effect inside the switch (mentioned before) - or it's caught up in something otherwise time-consuming.

You can only get as much 'granularity' as you're willing to check for - and at some point have no time for anything else.

Or look into (dare I mention it?) Interrupts

I'm not sure why you say that. It runs fine at 9600.

if the word "that" in this case means not achieving a finer granularity.
If you would like to "see" 1/100the seconds count up on the display

1,01
1,02
1,03
1,04
....
the baudrate is too slow for updating the display so fast.

It will be something like this example
1,01
1,07
1,15
1,23

1/100th seconds can't be seen with the human eye.
1/10th is possible

Ah. Okay, I get it. Yes, im seeing a big blur. My screen can go up to 57600. Not sure if thats fast enough?

Run it up.

what is "your screen" ? I thought you want to display the upcounting time on a I2C-display?

57600 baud means 57600 bits per second
a character is 10 bits (8 databits a startbit a stopbit)

So each character needs 1/57600 * 10 = 174 µS

1/100th second = 10.000 µS / 174 = 57 characters

I haven't checked how long datastransmision to a I2C-display needs.
Again depends on how many bytes and how many commands

This is the screen I am using:

I am building everything in an emulator and the 12C was the only thing the emulator had.

What emulator ist it?

Does this emulator have a serial monitor?
How fast can theis serial monitor print?
I mean if you print 50 lines are the 50 lines run through in less than 0,1 second or is it pretty slow?

It's the tinkercad emulator. I don't know all of the details about it.

There is another simulator that can be achieved without an account

I'm having Issues with the PID now. No matter what I do, the temperature keeps going up. Any advice?

#include "Arduino.h"

#if defined(ARDUINO_ARCH_SAMD) || defined(__SAM3X8E__)
  // use pin 18 with Due, pin 1 with Zero or M0 Pro
  #define lcd Serial1
#else
  #include <SoftwareSerial.h>
  // Create a software serial port!
  SoftwareSerial lcd = SoftwareSerial(0,2);
#endif

// PID Libraries
#include <PID_v1.h>

// Libraries for the DS18B20 sensor
#include <OneWire.h>
#include <DallasTemperature.h>

// ************************************************
// Pins We'll Use
// ************************************************

// Pin for Temperature control pot
#define TMP_CNTRL A1
#define TMP_PWR A2
#define TMP_GND A3
// Pin for Brew Relay
#define BREW_RELAY 8
// Pin for Brew Button
#define BREW_BUTTON A0
// Pin for Power Relay
#define POWER_RELAY 9
// DS18B20 on PIN 6 on the Arduino
#define ONE_WIRE_BUS 6
#define ONE_WIRE_PWR 3
#define ONE_WIRE_GND 4
//Solid state relay on PIN 5 on the Arduino
#define COIL_RELAY 5

// ************************************************
// PID Definitions
// ************************************************

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
double Kp=2, Ki=0.5, Kd=2;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

int WindowSize = 1000;
unsigned long windowStartTime;


// ************************************************
// Definitions for Brew Button & Timer
// ************************************************
bool indicator = 1;
unsigned long startTime = 0;
unsigned long currentTime = 0;
const long count = 1000;
unsigned long previousTime = 0;
unsigned long markTime;
unsigned long elapsedTime;
unsigned long lockoutmark;  // pb-related
const unsigned long debounceTime = 500; // debounceTime, used w/ lockout
const unsigned long endBrew = 500;
const unsigned long preinfusionTime = 3000;  // time on
boolean lockout;  // if true then PB doesn't get checked
boolean tripped;  // pb hit, prep Update for new pointers etc.
byte pb1state;
boolean engaged;


void setup ()
{
  // ************************************************
  // Setup LCD Screen
  // ************************************************

    lcd.begin(9600);

    // set the size of the display if it isn't 16x2 (you only have to do this once)
    lcd.write(0xFE);
    lcd.write(0xD1);
    lcd.write(16);  // 16 columns
    lcd.write(2);   // 2 rows
    delay(10);

    //Set RGB Color to Blue
    lcd.write(0xFE);
    lcd.write(0xD0);
    lcd.write(0x01);
    lcd.write(0x01);
    lcd.write(0xFF);

    // set the contrast
    lcd.write(0xFE);
    lcd.write(0x50);
    lcd.write(200);
    delay(10);

    // set the brightness
    lcd.write(0xFE);
    lcd.write(0x99);
    lcd.write(255);
    delay(10);

    lcd.print(0xFE);
    lcd.print(0x52);
    //change Splash Screen
    lcd.write(0xFE);
    lcd.write(0x40);
    lcd.write("Coffeeduino_v1.0                    ");
    delay(10);



  // ************************************************
  // Setup Brew Button & Timer
  // ************************************************
  pinMode(TMP_PWR, OUTPUT);
  digitalWrite(TMP_PWR, HIGH);
  pinMode(TMP_GND, OUTPUT);
  digitalWrite(TMP_GND, LOW);
  pinMode(POWER_RELAY, OUTPUT);
  pinMode(BREW_BUTTON,INPUT_PULLUP);
  pinMode(BREW_RELAY, OUTPUT);
  lockout = false;
  tripped = true;
  engaged = false;
  markTime = millis();
  // ************************************************
  // Setup Temperature Sensor
  // ************************************************
  pinMode(ONE_WIRE_GND, OUTPUT);
  digitalWrite(ONE_WIRE_GND, LOW);
  pinMode(ONE_WIRE_PWR, OUTPUT);
  digitalWrite(ONE_WIRE_PWR, HIGH);

  // ************************************************
  // Setup PID
  // ************************************************
  pinMode(COIL_RELAY, OUTPUT);
  digitalWrite(COIL_RELAY, LOW);
  windowStartTime = millis();

  //initialize the variables we're linked to
  Setpoint = 40;

  //tell the PID to range between 0 and the full window size
  myPID.SetOutputLimits(0, WindowSize);

  //turn the PID on
  myPID.SetMode(AUTOMATIC);


  // ************************************************
  // Power on Coffee machine
  // ************************************************

  digitalWrite(POWER_RELAY, HIGH);
  delay(500);
  digitalWrite(POWER_RELAY, LOW);

}

void loop ()
{
  //Allow TMP_CNTRL to adjust Setpoint
  int value = analogRead(TMP_CNTRL);
  int increment = value / 13;
  Setpoint = 40 + increment;
  //Brew Controls
  int set = Setpoint;
  int temp = floor(sensors.getTempCByIndex(0));
  elapsedTime = millis();
  Brewstart(elapsedTime, set, temp);
  //PID Controls
  sensors.requestTemperatures();
  Input = sensors.getTempCByIndex(0);
  myPID.Compute();



  /************************************************
   * turn the output pin on/off based on pid output
   ************************************************/

/*  if (millis() - windowStartTime > WindowSize)
  {
    //time to shift the Relay Window
    windowStartTime += WindowSize;
  }*/
  if (Output < millis() - windowStartTime)
    digitalWrite(COIL_RELAY, HIGH);
  else
    digitalWrite(COIL_RELAY, LOW);

}

void Brewstart( unsigned long x, int y, int z) {
    if(!lockout)  // ok to read
  {
    pb1state = digitalRead(BREW_BUTTON);
    if (!pb1state)  // pb made active
    {
      indicator = !indicator;
      lockoutmark = millis();  // record millis when pb went LOW
      lockout = true;
      tripped = true;
      if (engaged)
      {
        digitalWrite(BREW_RELAY, LOW);
        engaged = false;
      }
      else
      {
        digitalWrite(BREW_RELAY, HIGH);
        markTime = millis();
        engaged = true;
      }
    }
  }

  elapsedTime = millis();
  if ((lockout) && ((elapsedTime - lockoutmark) >= debounceTime))
  {
    lockout = false; // lockout done, threshold met
    tripped = false;
  }

  if (engaged)
  {
    startTime = millis();
    elapsedTime = millis();
    if (((elapsedTime - markTime) < preinfusionTime) && indicator == 0)
    {

      // go 'home'
      lcd.write(0xFE);
      lcd.write(0x48);
      lcd.print("Preinfusion  ");
    }
    if((elapsedTime - markTime) >= preinfusionTime && indicator == 0) // determine whether an Update() is due
    {
      digitalWrite(BREW_RELAY,LOW);
      engaged = false;
    }

    if((elapsedTime - markTime) >= endBrew && indicator == 1) // determine whether an Update() is due
    {
      digitalWrite(BREW_RELAY,LOW);
      engaged = false;
    }

  }
  if (indicator == 0 && engaged == false) {
    currentTime = millis();

      // go 'home'
      lcd.write(0xFE);
      lcd.write(0x48);
      // clear screen
      lcd.write(0xFE);
      lcd.write(0x58);

      // Setup screen
      lcd.print("Time: ");
      // Set cursor position
      lcd.write(0xFE);
      lcd.write(0x47);
      // Column 1, Row 2
      lcd.write(0x01);
      lcd.write(0x02);
      lcd.print("Temp S:");
      lcd.print(y);
      lcd.print("A:");
      lcd.print(z);
      //Set cursor position
      lcd.write(0xFE);
      lcd.write(0x47);
      // Column 7, Row 1
      lcd.write(0x07);
      lcd.write(0x01);
   	 	lcd.print((currentTime - startTime  + 3000) /1000);
    	lcd.print(':');
    	lcd.print((currentTime - startTime + 3000) % 1000);

  }
  if (indicator == 1 && engaged == false) {

    // go 'home'
    lcd.write(0xFE);
    lcd.write(0x48);
    // clear screen
    lcd.write(0xFE);
    lcd.write(0x58);

    // Setup screen
    lcd.print("Time: ");
    // Set cursor position
    lcd.write(0xFE);
    lcd.write(0x47);
    // Column 1, Row 2
    lcd.write(0x01);
    lcd.write(0x02);
    lcd.print("Temp S:");
    lcd.print(y);
    lcd.print("A:");
    lcd.print(z);
  }
}

If output-variable is smaller than the difference
between milliseconds since power-on and milliseconds short after power-on

then switch on relay?
with example numbers

lets say your code needs 2000 milliseconds to reach the line

void setup ()
{
  // ************************************************
  // Setup LCD Screen
  // ************************************************

  lcd.begin(9600);

  // set the size of the display if it isn't 16x2 (you only have to do this once)
  lcd.write(0xFE);
  lcd.write(0xD1);
....
  pinMode(COIL_RELAY, OUTPUT);  
  digitalWrite(COIL_RELAY, LOW);
  windowStartTime = millis();    // <<<<<=== THIS LINE millis() = 2000

3 seconds later millis() has value 5000
so your if-condition is

if (Output < millis() - windowStartTime)

with the example numbers

if (output < (5000 - 2000) {

after one minute since power up

if (output < (60000 - 2000) {

This makes no sense at all.

You involved timing in the if-condition so I guess you want to do something with timing.

It seems the functionality you want is not just

If temperature is below setpoint-temperature heating on

If temperature is at or above setpoint-temperature heating OFF

You want timing be involved.

So please describe the wanted functionality with normal words
not with code.