Crónometro más contador con sensores inductivos y LCD

La resta de unidades esta aca

stopTime = millis()-startTime;

Restas el tiempo actual en millis() menos el que tomaste en el arranque.

Que librería LiquidCrystal_I2C.h usas? dime de quien es y si tienes un link mejor.

Acabo de probar el código en el IDE porque yo uso PlatformIO y este es el resultado

El Sketch usa 8940 bytes (3%) del espacio de almacenamiento de programa. El máximo es 253952 bytes.
Las variables Globales usan 522 bytes (6%) de la memoria dinámica, dejando 7670 bytes para las variables locales. El máximo es 8192 bytes.

Vuelvo a poner el código

#include <Wire.h>
#include <LiquidCrystal_I2C.h>      // https://github.com/fmalpartida/New-LiquidCrystal
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

// Variables para los cálculos internos del cronómetro
int horas         = 0;
int minutos       = 0;
int segundos      = 0;
int decimas       = 0;
int contador      = 0;

unsigned long startTime, stopTime;
bool start, startAnt  = false;
bool stop, stopAnt    = false;
bool reset, resetAnt  = false;

int boton_resta   = 7;   // restar unidades contador
int boton_reset   = 8;   // resetear contador y cronometro
int sensor_inicio = 9; // pulsador_inicio en PIN digital 10
int sensor_fin    = 10;   // pulsador_pausa en PIN digital 9
bool flag = false;
bool freset = false;

// estas variables son para simular
byte estado = 0;
unsigned long demoro;

//char *convertirSegundosAHMmSs(long seconds) {
void convertirSegundosAHMmSs(unsigned long seconds) {
    static unsigned long secondsAnt = 0;
    char tmp[20];

    
    int s = seconds % 60;
    int m = (seconds / 60) % 60;
    int h = (seconds / (60 * 60)) % 24;
    sprintf(tmp, "%02d:%02d:%02d", h,m,s);
    if (seconds != secondsAnt) {
        Serial.println("Recibidos: " + String(seconds));
        Serial.println(tmp);
    }
    secondsAnt = seconds;  
    //return tmp;
}

void setup() {
  Serial.begin(115200);                   // Comienzo de la comunicación serie
  Serial.println("Iniciando programa.");
  pinMode(sensor_inicio, INPUT_PULLUP); // Pin digital 10 como entrada
  pinMode(sensor_fin, INPUT_PULLUP);    // Pin digital 9 como entrada
  pinMode(boton_reset, INPUT);          // Pin digital 8 como entrada
  pinMode(boton_resta, INPUT);          // Pin digital 7 como entrada

  // Inicializamos el LCD
  lcd.begin(16, 2);
  lcd.backlight();
}
void loop() {
  // Si presionamos el pulsador de inicio se pone todo a cero, se reinicia em
  // cronómetro y se suma una unidad al contador
  if (start && !startAnt) {   // siempre usa este criterio. El de mirar el flanco no un estado.
      startTime = millis();
      flag = true;
      freset = false;
  }
  startAnt = start;
  
  if (stop && !stopAnt) {   // siempre usa este criterio. El de mirar el flanco no un estado.
      stopTime = millis()-startTime;
      flag = false;
  }
  stopAnt = stop;

  if (reset && !resetAnt) {   // siempre usa este criterio. El de mirar el flanco no un estado.
      freset = true;
  }
  resetAnt = reset;

  if (freset) 
      //Serial.println(convertirSegundosAHMmSs(0));
      convertirSegundosAHMmSs(0);
  else {
      if (flag) {
          //Serial.println(convertirSegundosAHMmSs(startTime/1000));
          convertirSegundosAHMmSs(startTime/1000);
      }
      else {
          //Serial.println(convertirSegundosAHMmSs(stopTime/1000));
          convertirSegundosAHMmSs(stopTime/1000);
      }
  } 
}

NOTA:
Revisa porque has tomado un código que luego cambié.