When reseting arduino and/or millis is a good thing

My first big project with arduino was designing my own slot car timing system. I began as most do, scavenging youTube vids and other people's sketches. I came across a great sketch from 2012, in German, which was a timer capable of many lanes .
This sketch uses interrupt service routines, and they are very slick. They can't be fooled, and as soon as the system is up, millis gets going. By nature, this is a simple sketch. you drive laps, it times them down to the thousandth of a sec. very cool. There was no racing, just accurate timing. As I modified things to suit my needs (LEDs, bigger display, Race or Practice mode) two items remained an issue: 1. If you change cars, you want a fresh timer, a reset. 2. the first lap begins counting when the arduino fires up.
Learning the software reset is a good thing if you are doing what I am doing. I made a condition which requires simultaneous button presses.
```
void ( resetFunc) (void) = 0; //  program reset function (set before main loop)

// Hold both buttons down to reset program.(at the very end of main loop)
 BUTTONSTATE1 = digitalRead(BUTTON1);
 if (BUTTONSTATE1 == HIGH)
   BUTTONSTATE2 = digitalRead(BUTTON2);
 if (BUTTONSTATE2 == HIGH)
   resetFunc();*
* *it is worth noting, this taught me a lot because it was simple to create, yet I was surprised in that you can start with button2, the arduino is so fast, you can't tell the difference.* *The other problem was solved by detatching the interrupts as soon as the race start sequence begins. then as the green light comes on, millis resets, attach interrupt. This was not only helpful, but it automatically ensures the lap sensor debounce for the first lap. (4 seconds before a lap will register - big track)* *This is the last part of the start lighting sequence, followed by the millis reset (this runs only once)* *

  • for (int i = 0; i < SLOTS; i++)
         {
           digitalWrite(LEDGROUP[i][0], HIGH);

}
     delay(2000);

for (int i = 0; i < SLOTS; i++)
     {
       digitalWrite(LEDGROUP[i][0], LOW); // all lights out
       digitalWrite(LEDGROUP[i][1], LOW);
       digitalWrite(LEDGROUP[i][2], LOW);
     }
     delay(1800); // amount of lights-out time before green light
     noInterrupts();
     timer0_millis = 0;
     interrupts();
     raceStart = millis(); // global race start time used for each lanes race length
     for (int i = 0; i < SLOTS; i++)
     {
       digitalWrite(LEDGROUP[i][0], HIGH); // green light - race start and reset miliis
       attachInterrupt(pinInterrupts[i], isrFunctions[i], HIGH); sensors activate
     }*
```
I am posting as an FYI because these two "don'ts" are perfect for my project, as they solve a lot of issues & cause zero problems. I am amazed at how many people don't think these are useful tools. For my system, which is reset every 2 minutes or so, it is perfect. [/code]
[/code]

Attache the entire code.

oh boy... you asked.... it is a long one. pt1/2

#include <Wire.h> // Library for I2C communication
#include <LiquidCrystal_I2C.h> // Library for LCD
LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 20, 4);


int LEDPIN0[] = {9, 7, 8};
int LEDPIN1[] = {12, 10, 11};
const int pinCount = 3;
const int SLOTS = 2;
int LEDGROUP[SLOTS][pinCount] = {{9, 7, 8}, {12, 10, 11}};
#define MINROUNDTIME 4000// Minimum lap time in milliseconds, lap debounce
byte laserPins[] = {2, 3};// Pins for connecting the laser sensors (D2, D3)
byte pinInterrupts[] = {0, 1};// Interrupt-Numbers for these pins

const int BUTTON1 = 5;  //toggle switches
const int BUTTON2 = 6;
int BUTTONSTATE1 = 0; // state of buttons for reset routine
int BUTTONSTATE2 = 0;

int Race = 0;  // state of button 5 for race/practice mode selction
int Practice = 0;  // state of button 6 for race/practice mode selction
int RaceLaps = 1; // number of laps raced is always =>1 


extern volatile unsigned long timer0_millis;
volatile long roundStart[] = {0, 0}; // Start of the last round in milliseconds, volatile because of ISR!
unsigned long raceStart = 0;  //  exact time in millis light turns green
unsigned long raceTime[] = {0, 0}; //   end of race in millis
int raceEnd[] = {0, 0}; // counter to define raceTime only once.
long lapTime[] = {0, 0};   //Lap time of the last lap in milliseconds
long lapRecord[] = {20000, 20000};  // Lap time of the fastest lap in milliseconds
int lapNumber[] = {0, 0};  //  Lap Counter


void (* resetFunc) (void) = 0; //  program reset function

void timing1()     // Interrupt (ISR routines)
{
  if (millis() - roundStart[0] > MINROUNDTIME)
    roundStart[0] = millis();
}
void timing2()
{
  if (millis() - roundStart[1] > MINROUNDTIME)
    roundStart[1] = millis();
}
void (*isrFunctions[])() = { timing1, timing2 };


void setup() {
  lcd.init();
  lcd.backlight();

  for (int i = 0; i < SLOTS; i++)   // Initialize laser sensors and ISR routines
  {
    pinMode(laserPins[i], INPUT_PULLUP);
    attachInterrupt(pinInterrupts[i], isrFunctions[i], HIGH);
  }
  for (int i = 0; i < pinCount; i++)
  {
    pinMode(LEDPIN0[i], OUTPUT);
    pinMode(LEDPIN1[i], OUTPUT);
  }
  pinMode(BUTTON1, INPUT);
  pinMode(BUTTON2, INPUT);

  lcd.setCursor(6, 0);
  lcd.print("Suitcase");
  lcd.setCursor(1, 1);
  lcd.print("Grand Prix Circuit");
  lcd.setCursor(1, 3);
  lcd.print("Practice    Race");

  Practice = digitalRead(BUTTON1);  //  Choose Race Mode
  Race = digitalRead(BUTTON2);
  while (Practice == 0 && Race == 0)
  {
    Practice = digitalRead(BUTTON1);
    Race = digitalRead(BUTTON2);
  }
  delay(100);
  lcd.clear();
  char buffer [21];

  Race = 0;                         // Choose number of laps if in race mode
  while (Practice == 0 && Race == 0)   //  Same initial button condition - practice never uses this
  {
    lcd.setCursor(6, 0);
    lcd.print("Suitcase");
    lcd.setCursor(1, 1);
    lcd.print("Grand Prix Circuit");
    lcd.setCursor(3, 2);
    lcd.print("How Many Laps?");
    lcd.setCursor(0, 3);
    sprintf(buffer, "  Laps=%02d    Start", RaceLaps);
    lcd.print(buffer);

    int BUTTONSTATE1 = digitalRead(BUTTON1);
    if (BUTTONSTATE1 == HIGH)
    {
      RaceLaps++;
    }
    Race = digitalRead(BUTTON2);
  }
  lcd.clear();
}


void ProcessLapTimes()    //Process lap times
{
  static long RoundProcessed[] = {0, 0};  // Start of the penultimate round in milliseconds
  long curMillis;
  curMillis = millis();  // Note the current status of the millis () function

  for (int i = 0; i < SLOTS; i++)  // Check all slots for changes in the round start time
  {
    if  (RoundProcessed[i] != roundStart[i])  // New lap since the last loop run
    {
      lapNumber[i]++; // Lap counter plus 1
      lapTime[i] = roundStart[i] - RoundProcessed[i];  // Determine lap time
      if (lapTime[i] < lapRecord[i])  // Determine if it was the fastest lap
        lapRecord[i] = lapTime[i];
      if (lapRecord[i] == lapTime[i]) // if new lapRecord[i] flash red LED
        digitalWrite(LEDGROUP[i][1], HIGH);
      if (lapTime[i] < lapRecord[!i])
        digitalWrite(LEDGROUP[i][2], HIGH);
      RoundProcessed[i] = roundStart[i];   // mark this lap time as processed
    }
  }
}
void ProcessRaceTimes()    //   Racing Lap processing
{
  static long RoundProcessed[] = {0, 0};  // Start of the penultimate round in milliseconds
  long curMillis;
  curMillis = millis();  // Note the current status of the millis () function


  for (int i = 0; i < SLOTS; i++)  // Check all slots for changes in the round start time
  {
    if  (RoundProcessed[i] != roundStart[i])  // New lap since the last loop run
    {
      lapNumber[i]++; // Lap counter plus 1
      lapTime[i] = roundStart[i] - RoundProcessed[i];  // Determine lap time
      if (lapTime[i] < lapRecord[i])  // Determine if it was the fastest lap
        lapRecord[i] = lapTime[i];
      if (lapRecord[i] == lapTime[i] && lapTime[i] < lapRecord[!i]) // if fastest race lap flash red LED
        digitalWrite(LEDGROUP[i][2], HIGH);
      RoundProcessed[i] = roundStart[i];   // mark this lap time as processed
    }
  }
}

void ShowRaceTimes() // Race Display
{
  static long lastUpdate;
  long thisLap, thisRecord;
  char fullline[20];
  char lcdline[10];
  int ltime[4];


  if (millis() / 500 == lastUpdate) return; // Update display only every 500 ms
  lastUpdate = millis() / 500;
  snprintf(fullline, sizeof(fullline), "Grand Prix %2d Laps", RaceLaps);
  lcd.setCursor(1, 0);
  lcd.print(fullline);
  for (int i = 0; i < SLOTS; i++)
  {
    // Lap time and fastest lap time in hundredths of a lap
    thisLap = (lapTime[i] + 5) / 10;  // lap time
    thisRecord = (lapRecord[i] + 5) / 10;  // lap time record
    ltime[0] = thisLap / 100; // whole seconds
    ltime[1] = thisLap % 100; // hundredths of second
    ltime[2] = thisRecord / 100; // whole seconds
    ltime[3] = thisRecord % 100; // hundredths of second

    snprintf(lcdline, sizeof(lcdline), "lap%2d/%2d", lapNumber[i], RaceLaps);
    if (i == 0)
    {
      lcd.setCursor(1, 1);
    }
    else
    {
      lcd.setCursor(11, 1);
    }
    lcd.print(lcdline);
    snprintf(lcdline, sizeof(lcdline), "%3d.%02d  ", ltime[0], ltime[1]);
    if (i == 0)
    {
      lcd.setCursor(0, 2);
    }
    else
    {
      lcd.setCursor(10, 2);
    }
    lcd.print(lcdline);
    snprintf(lcdline, sizeof(lcdline), "rec %2d.%02d", ltime[2], ltime[3]);
    if (i == 0)
    {
      lcd.setCursor(0, 3);
    }
    else
    {
      lcd.setCursor(10, 3);
    }
    lcd.print(lcdline);
  }
}

void ShowRaceResults()
{
  static long lastUpdate;
  long avgLap, thisRecord, totalTime;
  char lcdline[10];
  int ltime[6];

  if (millis() / 500 == lastUpdate) return; // Update display only every 500 ms
  lastUpdate = millis() / 500;
  lcd.setCursor(1, 0);
  lcd.print("Grand Prix Results");

  for (int i = 0; i < SLOTS; i++)
  {
    // total time, avg time and fastest lap in hundredths of a lap
    avgLap = ((raceTime[i] / RaceLaps) + 5) / 10; // avg lap time
    thisRecord = (lapRecord[i] + 5) / 10;  // lap time record
    totalTime = (raceTime[i] + 5) / 10;
    ltime[0] = avgLap / 100; // whole seconds
    ltime[1] = avgLap % 100; // hundredths of second
    ltime[2] = thisRecord / 100; // whole seconds
    ltime[3] = thisRecord % 100; // hundredths of second
    ltime[4] = totalTime / 100;
    ltime[5] = totalTime % 100;
    snprintf(lcdline, sizeof(lcdline), "tot %2d.%02d", ltime[4], ltime[5]);
    if (i == 0)
    {
      lcd.setCursor(0, 1);
    }
    else
    {
      lcd.setCursor(10, 1);
    }
    lcd.print(lcdline);
    snprintf(lcdline, sizeof(lcdline), "avg %2d.%02d", ltime[0], ltime[1]);
    if (i == 0)
    {
      lcd.setCursor(0, 2);
    }
    else
    {
      lcd.setCursor(10, 2);
    }
    lcd.print(lcdline);
    snprintf(lcdline, sizeof(lcdline), "rec %2d.%02d", ltime[2], ltime[3]);
    if (i == 0)
    {
      lcd.setCursor(0, 3);
    }
    else
    {
      lcd.setCursor(10, 3);
    }
    lcd.print(lcdline);
  }
}

There should never be a need to reset an Arduino or reset millis() if the program is properly designed. You don't reset the clock in your kitchen just because you don't like the time it is displaying.

You may need buttons to re-direct the program to different activities or to change program variables, but that should not require a reset of the Arduino.

There is always a small risk that the Arduino program will stop working and the proper way to deal with that is to use the Watch Dog Timer (WDT). But that is intended to deal with an unforeseen problem, not an option in an otherwise working program.

...R

PS ... if a program is too long post the .ino file as an attachment so we don't have to join parts together with the risk of introducing errors.

pt 2/2

void ShowLapTimes() //  Practice lap Display
{
  static long lastUpdate;
  long thisLap, thisRecord;
  char lcdline[10];
  int ltime[4];

  if (millis() / 500 == lastUpdate) return; // Update display only every 500 ms
  lastUpdate = millis() / 500;
  for (int i = 0; i < SLOTS; i++)
  {
    // Lap time and fastest lap time in hundredths of a lap
    thisLap = (lapTime[i] + 5) / 10;  // lap time
    thisRecord = (lapRecord[i] + 5) / 10;  // lap time record
    ltime[0] = thisLap / 100; // whole seconds
    ltime[1] = thisLap % 100; // hundredths of second
    ltime[2] = thisRecord / 100; // whole seconds
    ltime[3] = thisRecord % 100; // hundredths of second
    snprintf(lcdline, sizeof(lcdline), "lane % 1d", i + 1);
    if (i == 0)
    {
      lcd.setCursor(1, 0);
    }
    else
    {
      lcd.setCursor(11, 0);
    }
    lcd.print(lcdline);
    snprintf(lcdline, sizeof(lcdline), "lap % 3d", lapNumber[i]);
    if (i == 0)
    {
      lcd.setCursor(1, 1);
    }
    else
    {
      lcd.setCursor(11, 1);
    }
    lcd.print(lcdline);
    snprintf(lcdline, sizeof(lcdline), " %3d.%02d ", ltime[0], ltime[1]);
    if (i == 0)
    {
      lcd.setCursor(0, 2);
    }
    else
    {
      lcd.setCursor(10, 2);
    }
    lcd.print(lcdline);
    snprintf(lcdline, sizeof(lcdline), "rec%2d.%02d", ltime[2], ltime[3]);
    if (i == 0)
    {
      lcd.setCursor(0, 3);
    }
    else
    {
      lcd.setCursor(10, 3);
    }
    lcd.print(lcdline);
  }
}

void loop()
{
  if (Practice == 1)
  {
    ProcessLapTimes();
    ShowLapTimes(); // show lap times

    //  flash the green led when you cross the line
    if (millis() - roundStart[0] < 200)
      digitalWrite(LEDGROUP[0][0], HIGH);
    else
      digitalWrite(LEDGROUP[0][0], LOW);

    if (millis() - roundStart[1] < 200)
      digitalWrite(LEDGROUP[1][0], HIGH);
    else
      digitalWrite(LEDGROUP[1][0], LOW);

    delay(100);
    for (int i = 0; i < SLOTS; i++)
    {
      digitalWrite(LEDGROUP[i][1], LOW);
      digitalWrite(LEDGROUP[i][2], LOW);
    }
  }

  else
  {

    if (raceTime[0] == 0 || raceTime[1] == 0)
    {
      ProcessRaceTimes(); // process race timing
      ShowRaceTimes(); // show race display
    }
    if (Race == 1)
    {
      for (int i = 0; i < SLOTS; i++)
      {
        digitalWrite(LEDGROUP[i][0], LOW); // always turn off lights after reset.
        digitalWrite(LEDGROUP[i][1], LOW);
        digitalWrite(LEDGROUP[i][2], LOW);
        detachInterrupt(pinInterrupts[i]);
      }
      delay(3000);
      for (int i = 0; i < SLOTS; i++)
      {
        digitalWrite(LEDGROUP[i][1], HIGH);
      }

      delay(2000);
      for (int i = 0; i < SLOTS; i++)
      {
        digitalWrite(LEDGROUP[i][2], HIGH);

      }
      delay(2000);

      for (int i = 0; i < SLOTS; i++)
      {
        digitalWrite(LEDGROUP[i][0], HIGH);

      }
      delay(2000);

      for (int i = 0; i < SLOTS; i++)
      {
        digitalWrite(LEDGROUP[i][0], LOW);
        digitalWrite(LEDGROUP[i][1], LOW);
        digitalWrite(LEDGROUP[i][2], LOW);
      }
      delay(1800);
      noInterrupts();
      timer0_millis = 0;
      interrupts();
      raceStart = millis();
      for (int i = 0; i < SLOTS; i++)
      {
        digitalWrite(LEDGROUP[i][0], HIGH);
        attachInterrupt(pinInterrupts[i], isrFunctions[i], HIGH);
      }
    }



    //  flash the red led when you cross the line
    if (millis() - roundStart[0] < 200)
      digitalWrite(LEDGROUP[0][1], HIGH);
    else
      digitalWrite(LEDGROUP[0][1], LOW);

    if (millis() - roundStart[1] < 200)
      digitalWrite(LEDGROUP[1][1], HIGH);
    else
      digitalWrite(LEDGROUP[1][1], LOW);

    delay(100);
    for (int i = 0; i < SLOTS; i++)
    {
      if (lapNumber[i] < RaceLaps)  // lED 2 flashes off unless race is over
        digitalWrite(LEDGROUP[i][2], LOW);
    }
    for (int i = 0; i < SLOTS; i++)
    {
      if (lapNumber[i] == RaceLaps) // when you cross the finish
      {
        detachInterrupt(pinInterrupts[i]);
        digitalWrite(LEDGROUP[i][0], LOW);
        digitalWrite(LEDGROUP[i][2], HIGH);
        if (raceEnd[i] == 0)
        {
          raceTime[i] = millis();
          raceEnd[i] = 1;
        }
      }
      while (raceTime[0] > 0 && raceTime[1] > 0)
      {
        ShowRaceResults();
        digitalWrite(LEDGROUP[0][1], LOW);
        digitalWrite(LEDGROUP[1][1], LOW);
        BUTTONSTATE1 = digitalRead(BUTTON1); // Hold both buttons down to reset.
        if (BUTTONSTATE1 == HIGH)
          BUTTONSTATE2 = digitalRead(BUTTON2);
        if (BUTTONSTATE2 == HIGH)
          resetFunc();
      }
    }
    Race = 2;
  }
  // Hold both buttons down to reset program.
  BUTTONSTATE1 = digitalRead(BUTTON1);
  if (BUTTONSTATE1 == HIGH)
    BUTTONSTATE2 = digitalRead(BUTTON2);
  if (BUTTONSTATE2 == HIGH)
    resetFunc();
}

[/code]

Sorry, I am limited by the website in my response time. Thanks for engaging. here is ino file

I guess my point is that this is a toy racetrack timer. it isn't on for days or anything like that (although, I have tested it to ridiculous lap lengths). I often change cars and want a clean slate, or a new race, a minute after the last one.

I can see how these two extreme actions could be bad in a complex system with multiple components running over a long period of time, while controlling something valuable or important.

However, for a simple slotcar timer (with a lot of options programmed in) they are simple and effective solutions. (I did try to do all this without both steps btw)

lasertiming9_1_race.ino (12.9 KB)

I suppose I could tell it to return to the opening screen without resetting, then have the program reset the laptimes, etc.

I guess to satisfy the true programmers here (of which I am NOT one...holy cow) I need to utilize the setting of (raceTime = millis():wink: into the 1st race lap calculations...

as I continue in my defense I realize how to program it in (I think).... If I eliminate the millis reset, I already have raceTime set, I could set a condition on lap 0 to set stuff to raceTime.

that said...it does work well already. But now I will go and prove OP wrong on your behalf...lol

I like a new challenge.

My Reply #3 was not intended as a directive to revise your program. If you are happy with it then that's fine. But for the next time you might consider a different approach.

And keep in mind that my comment was also intended for other readers of your Thread, and not just for you.

...R

bsuitcase:
I suppose I could tell it to return to the opening screen without resetting, then have the program reset the laptimes, etc.

That sounds exactly what a completed program would do. :roll_eyes:

It should be obvious that when your program actually starts timing, the initial value of millis() is not zero in any case, so you just record it and use that as a reference value. So whenever you need to re-start, you just do the same thing.