Arduino resets on button push, after a long period of time.

Hi. I am unsure if this is the correct sub forum to be posting this, as it could be an issue with my programming, or an issue with the arduino. But I have chosen to post here.

I am making a speedometer/odometer, and have buttons that switch between modes. However, I have come around to testing it, and when left for a long time (probably around an hour) the arduino resets itself when I press the button.

The button is connected via an interrupt pin, and there is an LCD screen attached using LiquidCrystal. I am not sure if this is something that is to be expected, or whether I am doing something wrong, or whether the board is doing something wrong.

I will paste in all my code below, in case this is my fault...

float Version = 0.9;

//Settings
int pulsesPerRev = 4;
int pulsesPerRot = 6;
/*Wheel circumference in metres*/
double wheelCircumference = 1.5;

//Libraries
#include <EEPROM.h>
#include <LiquidCrystal.h>

//LCD Pins
#define rs 32
#define enable 33
#define d4 34
#define d5 35
#define d6 36
#define d7 37
//LCD Initialisation
LiquidCrystal lcd(rs,enable,d4,d5,d6,d7);

//Pins
//Digital
#define RPMPin 41
//Analog
#define lambdaPin 0
#define Temp1Pin 1;
#define Temp2Pin 2;
//Interrupts
/*The line from the speedometer should be attached to digital 2.
  The line from button 1 should be attached to digital 3.
  The line from button 2 should be attached to digital 21. */

//Variables to be loaded from EEPROM
volatile int setting = 0;
volatile int imperialMetric = 0;
/* The total distance travelled in m. */
double TotalDistance = 0;
double TripDistance = 0;
unsigned long TripTime = 0;
unsigned long LoadedTripTime = 0;

//External Settings
#define numberOfSettings 6
#define rpmSample 1000
#define avgLength 8

//External Variables
double speed;
int newDisplay = 456;
volatile int numPulses = 0;
volatile unsigned long pulseDebounce;
volatile unsigned long buttonDebounce;
unsigned long lastSpeedTime;
unsigned long TimeOnReset;
int rpm = 0;
int counter = 0;
int speedArray[avgLength];
unsigned long timer = 0;
int oldButtonPos;
int refresh = 0;
volatile int pressCount = 0;

//Functions
void setup(){
  Serial.begin(9600);
  //Start LCD
  lcd.begin(16,2);
  //Setup interrupts.
  //Display startup on LCD
  lcd.print("Speeduino");
  lcd.setCursor(0,1);
  lcd.print("Version ");
  lcd.print(Version);
  delay(2000);
  lcd.clear();
  pinMode(RPMPin,INPUT);
}
void loop(){
  attachInterrupt(0,pulse,RISING);
  attachInterrupt(1,button1Press,FALLING);
  attachInterrupt(2,button2Press,FALLING);
  if (setting == 0) displayODO();
  if (setting == 1) displayRPM();
  if (setting == 2) displayTripODO();
  if (setting == 3) displayTripTIME();
  if (setting == 4) displayAvgSPEED();
  if (setting == 5) displayTripReset();
  if (millis() > timer + 2000){
    updateSpeedDist();
    updateTripTime();
    timer = millis();
  }
}
void pulse(){
  if (millis()> pulseDebounce + 30) {
    numPulses++;
    pulseDebounce = millis();
  }
  Serial.println("We felt a pulse captain!");
}
void button1Press(){
  if ((millis()>buttonDebounce+250) || pressCount > 5) {
    setting = setting + 1;
    if (setting >= numberOfSettings) setting = 0;
    buttonDebounce = millis();
    pressCount = 0;
  }
  pressCount++;
  Serial.print("Button 1 pressed, Setting = ");
  Serial.println(setting);
}
void button2Press(){
  if (millis()>buttonDebounce+250) {
    imperialMetric++;
    imperialMetric = imperialMetric % 2;
    buttonDebounce = millis();
    newDisplay = 897;
    Serial.println("Button 2 Pressed");
  }
}
int updateSpeedDist(){
  unsigned long newSpeedTime = millis();
  double speedMps = (((double)numPulses/(double)pulsesPerRot)*wheelCircumference)/(((double)newSpeedTime - (double)lastSpeedTime)/1000);
  speedArray[counter] = speedMps * 2.23693629;
  counter++;
  counter % avgLength;
  
  for (int i =0; i<avgLength; i++) {
    speed += speedArray[i];
  }
  speed = speed/avgLength;
  double newDist = (((double) numPulses/(double)pulsesPerRot)*wheelCircumference);
  TotalDistance += newDist;
  TripDistance += newDist;
  numPulses = 0;
  return 1;
}
int updateTripTime(){
  TripTime =LoadedTripTime + millis() - TimeOnReset;
  return 1;
}
int resetTrip(){
  TripDistance = 0;
  TripTime = 0;
  LoadedTripTime = 0;
  TimeOnReset = millis();
  return 1;
}
double getTripAverage(){
  double avgMPS = TripDistance/((double)TripTime/1000);
  return (avgMPS * 2.23693629);
}
int getRPM(){
  unsigned long time = millis();
  int voltage = 1;
  int previousRead = 0;
  unsigned long newtime = time;
  int revCount = 0;
  
  while (newtime < time + rpmSample){
    voltage = digitalRead(RPMPin);
    newtime = millis();
    if ((voltage==HIGH)&&(previousRead==0)) {
      revCount++;
      previousRead = 1;
    }
    else if (voltage == LOW){
      previousRead = 0;
    }
  }
  Serial.print(millis());
  Serial.print("RPM: ");
  Serial.println(revCount);
  return (int)((((double)revCount/(double)((double)rpmSample/1000))*60)/(double)pulsesPerRev);
}
int displayODO(){
  if ((newDisplay != 0) || refresh > 1000){
    lcd.clear();
    newDisplay = 0;
    refresh = 0;
  }
  refresh++;
  lcd.setCursor(0,0);
  if (imperialMetric == 0){
    lcd.print("Speed: ");
    lcd.print(speed);
    lcd.print("mph");
    lcd.setCursor(0,1);
    lcd.print("Dist: ");
    lcd.print((TotalDistance*0.000621371192));
    lcd.print("m");
  }
  if (imperialMetric == 1){
    lcd.print("Speed: ");
    lcd.print(speed*1.609344);
    lcd.print("kph");
    lcd.setCursor(0,1);
    lcd.print("Dist: ");
    lcd.print(TotalDistance*0.001);
    lcd.print("km");
  }
  return 1;
}
int displayRPM(){
  if ((newDisplay != 1) || refresh > 10){
    lcd.clear();
    newDisplay = 1;
    refresh = 0;
  }
  refresh++;
  lcd.setCursor(0,0);
  if (imperialMetric == 0){
    lcd.print("Speed: ");
    lcd.print(speed);
    lcd.print("mph");
    lcd.setCursor(0,1);
    lcd.print("RPM: ");
    lcd.print(rpm);
  }
  if (imperialMetric == 1){
    lcd.print("Speed: ");
    lcd.print(speed*1.609344);
    lcd.print("kph");
    lcd.setCursor(0,1);
    lcd.print("RPM: ");
    lcd.print(rpm);
  }
  rpm = getRPM();
  return 1;
}
int displayTripODO(){
  if ((newDisplay != 2) || refresh > 1000) {
    lcd.clear();
    newDisplay = 2;
    refresh=0;
  }
  refresh++;
  lcd.setCursor(0,0);
  if (imperialMetric == 0){
    lcd.print("Speed: ");
    lcd.print(speed);
    lcd.print("mph");
    lcd.setCursor(0,1);
    lcd.print("T Dist: ");
    lcd.print(TripDistance*0.000621371192);
    lcd.print("M");
  }
  if (imperialMetric == 1){
    lcd.print("Speed: ");
    lcd.print(speed*1.609344);
    lcd.print("kph");
    lcd.setCursor(0,1);
    lcd.print("T Dist: ");
    lcd.print(TripDistance*0.001);
    lcd.print("km");
  }
  return 1;
}
int displayTripTIME(){
  if ((newDisplay != 3) || refresh > 1000){
    newDisplay = 3;
    lcd.clear();
    refresh = 0;
  }
  refresh++;
  lcd.setCursor(0,0);
  if (imperialMetric == 0){
    lcd.print("Speed: ");
    lcd.print(speed);
    lcd.print("mph");
  }
  if (imperialMetric == 1){
    lcd.print("Speed: ");
    lcd.print(speed*1.609344);
    lcd.print("kph");
  }
  lcd.setCursor(0,1);
  lcd.print("T Time: ");
  lcd.print((((TripTime/1000)/60)/60));
  lcd.print(":");
  int minutes = ((TripTime/1000)/60)%60 ;
  if (minutes < 10) lcd.print("0");
  lcd.print(minutes);
  lcd.print(":");
  int seconds = (TripTime/1000) % 60;
  if (seconds < 10) lcd.print("0");
  lcd.print(seconds);
  return 1;
}
int displayAvgSPEED(){
  if ((newDisplay != 4) || refresh > 1000) {
    newDisplay = 4;
    lcd.clear();
    refresh = 0;
  }
  refresh++;
  double avg = getTripAverage();
  lcd.setCursor(0,0);
  if (imperialMetric == 0){
    lcd.print("Speed: ");
    lcd.print(speed);
    lcd.print("mph");
    lcd.setCursor(0,1);
    lcd.print("Av Spd: ");
    lcd.print(avg);
    lcd.print("mph");
  }
  if (imperialMetric == 1){
    lcd.print("Speed: ");
    lcd.print(speed*1.609344);
    lcd.print("kph");
    lcd.setCursor(0,1);
    lcd.print("Av Spd: ");
    lcd.print(avg*1.609344);
    lcd.print("kph");
  }
  return 1;
}
int displayTripReset(){
  if (newDisplay != 5) {
    newDisplay = 5;
    lcd.clear();
    oldButtonPos = imperialMetric;
  }
  lcd.setCursor(0,0);
  lcd.print("To reset trip,");
  lcd.setCursor(0,1);
  lcd.print("press button 2");
  if (imperialMetric != oldButtonPos) {
    resetTrip();
    imperialMetric = oldButtonPos;
    lcd.clear();
    lcd.print("Trip Reset");
    delay(1000);
    setting = 0;
  }
}

How is the button wired? (Pull-up or Pull-down)

Why are you running attachInterrupt in loop()?

The attach interrupt was not originally in the loop, I moved it around as I was having a problem with the buttons being unresponsive and I thought it might help. (I was in the try anything phase of troubleshooting. It didn't help.) I will move them back.

The button is pull-down I believe (Voltage low when depressed?).

With more testing, I have noticed that it seems to happen around the 11 minute mark.

void button1Press(){
  if ((millis()>buttonDebounce+250) || pressCount > 5) {
    setting = setting + 1;
    if (setting >= numberOfSettings) setting = 0;
    buttonDebounce = millis();
    pressCount = 0;
  }
  pressCount++;
  Serial.print("Button 1 pressed, Setting = ");
  Serial.println(setting);
}

Interrupt handlers are supposed to be fast. Serial output is ssslllooowww. It has no place in an interrupt handler.

Couldn't you come up with more meaningful names for the functions? The pulse() function is fine, but, unless you have labels on the panel that the switches are mounted in that say "1" and "2", the other names are not.

Why are updateSpeedDist() and updateTripTime() int functions? You discard the (constant) return value.

It is obvious that you know how to use millis(). So, get rid of the delay() in loop. Then, the interrupt handlers to react to the switch state changes won't be necessary. You will be able to poll the switches, instead.

The serial output was there to help me test which part of the program was going wrong. It won't be there in the final version, it was just allowing me to see where it had gone wrong.

As for the int return, this is just the way I was taught to write C programs. The lecturer had been programming for a while, and it was something about void returns being a bit wonky when C was first created. When you get marked down for returning voids, it becomes habit to not return voids. I don't think it does any harm.

I can't see a delay in loop, but I will put the buttons in loop instead like you say. The only problem with that I guess would be the time it takes to return the RPM, but I could obviously work out a different way of doing that. The pulse does need to be on an interrupt pin though, and it will be annoying if that stops working too.

This is currently running on what I think is an Arduino Clone. Going to try testing it out on my UNO just to rule out it being the board though.

Are you on a mega? If you aren't, the arduino only has 2 external interrupts and therefore attachInterrupt(2, whatever, whatever) won't do anything.

Resetting upon an interrupt sounds like there isn't a function there to catch it, but WInterrupts.c has that covered for you.

Why don't you try making it a simpler problem and unplug the LCD screen? Also, remove the third attachInterrupt.

It says Arduino Mega 1280 on the board, although I am pretty sure it is a clone. The interrupts all work (at least for a while). I will write a simpler program though to rule out the board (Hadn't thought of that one). Trying that now...

counter % avgLength;

What's this? It won't do much.


Purely for style reasons, try using "switch" here:

  if (setting == 0) displayODO();

if (setting == 1) displayRPM();
  if (setting == 2) displayTripODO();
  if (setting == 3) displayTripTIME();
  if (setting == 4) displayAvgSPEED();
  if (setting == 5) displayTripReset();

Plus what PaulS said. Debugging displays inside an ISR are likely to change behaviour.

Well, turns out it was the serial output that was stopping it from working. Thanks for all the help :slight_smile: