Accurate stopwatch. Fixing micros() rollover

I have a serious. SERIOUS problem. I have this code:

/*
CHANGELOG

1.3.18
Fixed button debounce.

1.3.19
Added hours.

1.3.20
Because of hard time doing proper hour-minute-second-us measurement, hours were removed. Also, the display is blinking, when it's paused.

1.3.20.1
Quick fix, display stayed off if you reset the stopwatch while it's blinking, now it does not. 
*/

#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // Пины RS, E, D4, D5, D6, D7
const int startStopButtonPin = 8; // Пин кнопки старта/стопа
const int resetButtonPin = 7; // Пин кнопки сброса

enum State { STOPPED, RUNNING, PAUSED };
State state = STOPPED;

unsigned long long startTime = 0;
unsigned long long elapsedTime = 0;
unsigned long long totalElapsedTime = 0;
unsigned long long previousElapsedTime = 0;
unsigned long long lastBlinkTime = 0;
bool displayOn = true;
bool buttonPressed = false;

// Function declaration
void updateDisplay();

void setup() {
  lcd.begin(16, 2);
  Serial.begin(9600);
  pinMode(startStopButtonPin, INPUT_PULLUP);
  pinMode(resetButtonPin, INPUT_PULLUP);

  // Выводим верхнюю строку один раз при запуске
  lcd.setCursor(0, 0);
  lcd.blink();
  animateText("v1.4 Stable", 50);
  lcd.setCursor(0, 1);
  animateText("[          ]0%", 15);
  lcd.setCursor(0,1);
  animateText("[#         ]13%", 15);
  lcd.setCursor(0, 1);
  animateText("[###       ]36%", 15);
  lcd.setCursor(0, 1);
  animateText("[######    ]62%", 15);
  lcd.setCursor(0, 1);
  animateText("[######### ]99%", 15);
  lcd.setCursor(0, 1);
  animateText("[##########]100%", 15);
  lcd.setCursor(0,1);
  lcd.setCursor(0,0);
  animateText("START/STOP-GREEN", 25);
  lcd.setCursor(0, 1);
  animateText("RESET-RED            ", 25);  
  lcd.setCursor(15,1);
  delay(5000);
  lcd.setCursor(0,0);
  animateText("                ", 25);
  lcd.setCursor(0, 1);
  animateText("                ", 25);
  lcd.clear();
  lcd.setCursor(0,0);
  animateText("m:ss.us", 25);
  delay(10);
}

void animateText(const char* text, int delayTime) {
  int len = strlen(text);
  for (int i = 0; i < len; ++i) {
    lcd.print(text[i]);
    delay(delayTime);
  }
  delay(500); // Задержка после завершения анимации
}

void loop() {
  if (digitalRead(startStopButtonPin) == LOW) {
    delay(10); // Антидребезг
    buttonPressed = true;
  } else {
    if (buttonPressed) {
      handleButtonPress();
      buttonPressed = false;
    }
  }

  if (digitalRead(resetButtonPin) == LOW) {
    delay(10); // Антидребезг
    resetStopwatch();
  }

  if (state == RUNNING) {
    elapsedTime = micros() - startTime;

    // Проверка изменения значения перед обновлением экрана
    if (elapsedTime != previousElapsedTime) {
      updateDisplay();
      previousElapsedTime = elapsedTime;
    }
  }

  // Мигание экрана при остановленном секундомере
  if (state == PAUSED && millis() - lastBlinkTime >= 500) {
    lastBlinkTime = millis();
    displayOn = !displayOn;
    if (displayOn) {
      lcd.display(); // Включаем экран
    } else {
      lcd.noDisplay(); // Выключаем экран
    }
  }
}

void handleButtonPress() {
  switch (state) {
    case STOPPED:
      startStopwatch();
      break;
    case RUNNING:
      pauseStopwatch();
      break;
    case PAUSED:
      resumeStopwatch();
      break;
  }
}

void startStopwatch() {
  state = RUNNING;
  startTime = micros();
  lcd.display(); // Включаем экран при запуске
}

void pauseStopwatch() {
  state = PAUSED;
  totalElapsedTime += elapsedTime; // Накопление времени перед паузой
  lcd.display(); // Включаем экран при паузе
}

void resumeStopwatch() {
  state = RUNNING;
  startTime = micros();
  lcd.display(); // Включаем экран при продолжении
}

void resetStopwatch() {
  state = STOPPED;
  startTime = 0;
  elapsedTime = 0;
  totalElapsedTime = 0;
  previousElapsedTime = 0;
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.blink();
  lcd.display();
  lcd.print("m:ss.us");
}

void updateDisplay() {
  // Не перерисовываем верхнюю строку, если секундомер работает
  if (state == PAUSED) {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.blink();
    lcd.print("m:ss.us");
  }

  unsigned long long minutes = (totalElapsedTime + elapsedTime) / 60000000ULL;
  unsigned long long seconds = ((totalElapsedTime + elapsedTime) / 1000000ULL) % 60;
  unsigned long long microseconds = ((totalElapsedTime + elapsedTime) % 1000000ULL) / 1;

  // Вывод времени с ведущими нулями
// Вывод времени с ведущими нулями
  lcd.setCursor(0, 1);
  lcd.print(static_cast<unsigned long>(minutes)); // Явное приведение к unsigned long
  lcd.print(":");
  if (seconds < 10) lcd.print("0");
  lcd.print(static_cast<unsigned long>(seconds)); // Явное приведение к unsigned long
  lcd.print(".");
  if (microseconds < 10) lcd.print("0");
  lcd.print(static_cast<unsigned long>(microseconds)); // Явное приведение к unsigned long
}

And i have a prob with the micros() counter overflow. I need to fix this thing. I would like to change this globally in the Arduino's code. I know that micros is a

unsigned long micros(void)

thing. I would like to make it 64-bit (unsigned long long), so it doesn't reset every ~70 minutes. I am making a decently accurate stopwatch, which shouldn't go to all 0 after just above 1 hour of work. If you can give me a fix in this code - go ahead, but changes to Arduino's core lib (or whatever it's called) so i won't have to worry about doing this again will be more appreciated. Thank you. :heart:

Have you tried replacing unsigned long long with uint64_t?

(system library stdint.h should have typedef unsigned long long int uint64_t)

(or maybe it is the Arduino you are using)

Thank you! I will try your solutions :heart: I have done some changes to core's library files (i made micros() and millis() ULL) and I am testing the thing right now. If it doesn't overflow - I am celebrating! I will send you the files of the lib if it works, but be careful with it anyway :upside_down_face: :heart:

Same thing.

  if (microseconds < 10) lcd.print("0");
  lcd.print(static_cast<unsigned long>(microseconds)); // Явное приведение к unsigned long
}

The microseconds value can be from 1 to 6 digits. This code will not print it correctly. Up to 5 leading zeros will be needed.

I suggest

  int minutes = (totalElapsedTime + elapsedTime) / 60000000ULL;
  int seconds = ((totalElapsedTime + elapsedTime) / 1000000ULL) % 60;
  unsigned long microseconds = ((totalElapsedTime + elapsedTime) % 1000000ULL);
  char buffer[17];

  sprintf(buffer, "%02d:%02d.%06lu", minutes, seconds, microseconds);

  // Вывод времени с ведущими нулями
  lcd.setCursor(0, 1);
  lcd.print(buffer);
}

Haha ,I didn't now abou this core typdef.

unsigned  looooooooong

sound better :smiley:

@PaulRB - When does system architecture use more/fewer bits to typedef, like "int" versus "uint8_t" (one is 8 bits, the other is system dependent)?

I think it works like this:

If you use short int, I think that will always be 16 bits, on any type of Arduino, whether its an 8-bit or 32-bit chip.

If you use long int I think that will always be 32 bits, on any type of Arduino, whether its an 8-bit or 32-bit chip.

If you use long long int I think that will always be 64 bits, on any type of Arduino, whether its an 8-bit or 32-bit chip.

If you use int, without specifying short or long then it will be 16 bits on 8-bit Arduino and 32 bits on 32-bit Arduino.

If you use int8_t, int16_t, int32_t, int64_t they will always be the length you expect, on any type of Arduino.

Can anyone confirm?

1 Like