Uno resets when scrolling through menus on LCD.

Hi there.

I have a problem that i can’t seem to wrap my head around.

My breadboard setup is as follows:

  • Arduino uno

  • LCD with I2C backpack

  • Encoder with builtin pushbutton

The program is basically supposed to control 3 relays based on sensor input.

There are 7 different “menus” that can be scrolled through by pushing down the pushbutton on the encoder.

The menus are:

0: Sensor input display
1: Setting for sensor range
2: Activation threshold for Stop relay
3: Activation threshold for Start relay
4: Activation threshold for 2nd start relay
5: Sensor type selection
6: Ouput mode (Relays or 0-10V)
7: Setting for Area [m2]

The reset occurs when going from the 6th to the 7th menu. The arduino resets and goes about its normal business.

I have tried serial output to pinpoint the error, but as the arduino resets, I have not been able to capture anything suspicious.

It does not happen everytime, however sometimes it will happen 4-5 times in a row.

The actual setup is on a PCB, but as this error occurs both when the chip is mounted in the uno and the PCB, my current assumption is that the PCB vs breadboard setup is unrelated.

I really hope that there’s some of all the bright heads in here that are able to point me in some kind of direction.

I hope that I haven’t forgotten anything, but let me know if so.

The code is too long for posting in here, but i have attached the code.

Mathias

SensorModuleV2.0.cpp (18.2 KB)

I have not looked at your code but it has some of the characteristics of a value being written outside the bounds of an array.

You will get more help if you post your code here. If your code is too long then make a simplified version that demonstrates the problem. It might well be the case that in writing a simplified version for us to look at you identify the problem.

As the problem relates to the menu, I suggest you build your simplified version around the code needed to display and select the menu.

This is clutching at straws as your code is too complicated for me to make sense of, but right at the end you have

if (setVal == 5){...

Which I think is where you limit it's value from going too high (is it?), that being the case what happens if setVal is higher than 5? And, as you have menu 0 to 7 (8 in total) how does setVal access menu 6 and 7?

[EDIT MORE!!!]
You have cases up to 8 for the menu, surely they should be 0 to 7?

I have been working under the assumption that it is some kind of variable going outside its bounds, but have not been able to pinpoint exactly that.

If that’s the case, it must be the array levelSetting

The value of “setVal” is capped in “void settings()” (bottom of code)

As suggested, I have deleted a good part of the code to be able to post it. (Thanks PerryBebbington)

volatile byte setVal = 0; // Variable used for determining what to display on LCD
volatile int levelSetting[8];
// depth = 1 //Distance from sensor to bottom of well or maximum meassurement in cm
// stopLevel = 2
// startLevel = 3
// start2Level = 4
// sensor selection = 5
// Output mode = 6
// Well area in m2 = 7




long duration;
long distance;
long meassurement;
long sampMeassurement;




void setup() {

  Wire.begin();
//  Serial.begin(9600);


  byte error, address;
  byte nDevices;
 
 
  nDevices = 0;
  for(address = 1; address < 127; address++ )
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
 
    if (error == 0)
    {
      lcdaddress = address;
      
      nDevices++;
    } 
  }


  pLCD = new LiquidCrystal_I2C(lcdaddress, 16, 2);
  pLCD -> init();
  pLCD -> clear();
  pLCD -> backlight();

  

  //EEPROM.write(5,1);
  // Initial reading of level settings from EEPROM
  for (byte i=0; i<=7; i++) {
    if (i == 5 || i == 6) {
      levelSetting[i] = EEPROM.read(i);
    }
    else {
      levelSetting[i] = EEPROM.read(i)*4;
    }
  }

  levelSetting[6] = constrain(levelSetting[6], 0, 1);
  levelSetting[5] = constrain(levelSetting[5], 0, numSensors);


  

  

  attachInterrupt(digitalPinToInterrupt(encoderSwitchPin), settings, FALLING);
  attachInterrupt(digitalPinToInterrupt(encoderPinA), encoder, CHANGE);


  wdt_enable(WDTO_4S); //Enable watchdog timer with 4 seconds
}

void loop() {


 

//What to display on LCD
switch (setVal) {
 case 5: //Setting sensor selection
  pLCD -> setCursor(0, 0);
  strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[setVal])));
  pLCD -> print(buffer);
  strcpy_P(buffer, (char *)pgm_read_word(&(sensorName[levelSetting[setVal]])));
  pLCD -> setCursor(0, 1);
  pLCD -> print(buffer);
  break;
 case 6: //Setting output mode
  pLCD -> setCursor(0, 0);
  strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[setVal])));
  pLCD -> print(buffer);
  strcpy_P(buffer, (char *)pgm_read_word(&(outputMode[levelSetting[setVal]])));
  pLCD -> setCursor(0, 1);
  pLCD -> print(buffer);
  break;
 case 7: //Setting area
  pLCD -> setCursor(0, 0);
  strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[setVal])));
  pLCD -> print(buffer);
  pLCD -> setCursor(0, 1);
  if (levelSetting[setVal] > 99) {
    pLCD -> print((levelSetting[setVal]-(levelSetting[setVal]-1)));
    pLCD -> print(".");
    if (levelSetting[setVal]-100 < 10) {pLCD -> print(0);}
    pLCD -> print(levelSetting[setVal]-100);
  }
  else {
    pLCD -> print(0);
    pLCD -> print(".");
    pLCD -> print(levelSetting[setVal]);
  }
  strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[9])));
  pLCD -> print(buffer);
  break;
 case 8:
  setVal = 0;
  break;
 default: //Setting for levels
  pLCD -> setCursor(0, 0);
  strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[setVal])));
  pLCD -> print(buffer);
  if (setVal == 0){
    pLCD -> setCursor(9, 0);
    if (pumpRun == 0){strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[13])));}
    if (pumpRun == 1){strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[14])));}
    pLCD -> print(buffer);
  }
  pLCD -> setCursor(0, 1);
  pLCD -> print(levelSetting[setVal]);
  if (setVal == 0){strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[10])));}
  else {strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[8])));}
  pLCD -> print(buffer);
  if (setVal == 0){
    if (pumpRun == 0) {
      if (inFlow < 10 && inFlow >= 0) {
        pLCD -> setCursor(10, 1);
        strcpy_P(buffer, (char *)pgm_read_word(&(spaceText[0])));
        pLCD -> print(buffer);
        pLCD -> print(inFlow);
      }
      if (inFlow >= 10 && inFlow < 100) {
        pLCD -> setCursor(9, 1);
        strcpy_P(buffer, (char *)pgm_read_word(&(spaceText[0])));
        pLCD -> print(buffer);
        pLCD -> print(inFlow);
      }
      if (inFlow >= 100 && inFlow < 1000) {
        pLCD -> setCursor(8, 1);
        strcpy_P(buffer, (char *)pgm_read_word(&(spaceText[0])));
        pLCD -> print(buffer);
        pLCD -> print(inFlow);
      }
    }  
    else {
      if (outFlow > -10 && outFlow < 0) {
        pLCD -> setCursor(9, 1);
        strcpy_P(buffer, (char *)pgm_read_word(&(spaceText[0])));
        pLCD -> print(buffer);
        pLCD -> print(outFlow);
      }
      if (outFlow <= -10 && outFlow > -100) {
        pLCD -> setCursor(8, 1);
        strcpy_P(buffer, (char *)pgm_read_word(&(spaceText[0])));
        pLCD -> print(buffer);
        pLCD -> print(outFlow);
      }
      if (outFlow <= -100 && outFlow > -1000) {
        pLCD -> setCursor(7, 1);
        strcpy_P(buffer, (char *)pgm_read_word(&(spaceText[0])));
        pLCD -> print(buffer);
        pLCD -> print(outFlow);
      }
      if (outFlow <= -1000) {
        pLCD -> setCursor(6, 1);
        strcpy_P(buffer, (char *)pgm_read_word(&(spaceText[0])));
        pLCD -> print(buffer);
        pLCD -> print(outFlow);
      }
    }
    pLCD -> setCursor(13, 1);
    strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[11])));
    pLCD -> print(buffer);
  }
  break;
}

  //Ultrasonic = 0
  //4-20ma sensor = 1
  //0-10v sensor = 2
  //0-5v sensor = 3


  if (setVal == 5 || setVal == 6) {
    EEPROM.update(setVal, levelSetting[setVal]);
  }
  else if (setVal != 0) {
    EEPROM.update(setVal, levelSetting[setVal]/4);
  }

  wdt_reset();
}


void settings() { 
  if (digitalRead(encoderSwitchPin) == LOW){
    if (setVal >= 8) {
      setVal = 0;
    }
    else {
      setVal=setVal+1;
    }
    }
}
void encoder() {
     if (setVal !=0){
       if (digitalRead(encoderPinA) == HIGH)                        // found a low-to-high on channel A
       {
             if (digitalRead(encoderPinB) == LOW)                  // check channel B to see which way encoder is turning
             {
                   levelSetting[setVal] = (levelSetting[setVal] + 1);      // CW
             }
             else
             {
                   levelSetting[setVal] = (levelSetting[setVal] - 1);      // CCW
             }
       }
       else                                          // found a high-to-low on channel A
       {
             if (digitalRead(encoderPinB) == LOW)
             {
                   levelSetting[setVal] = (levelSetting[setVal] - 1);      // CCW
             }
             else
             {
                   levelSetting[setVal] = (levelSetting[setVal] + 1);      // CW
             }
       }
     }
  if (setVal == 5){
    levelSetting[setVal] = constrain(levelSetting[setVal], 0, numSensors);}
  else if (setVal ==6) {levelSetting[setVal] = constrain(levelSetting[setVal], 0, 1);}
  else {levelSetting[setVal] = constrain(levelSetting[setVal], 0, 600);}
}

PerryBebbington:
[EDIT MORE!!!]
You have cases up to 8 for the menu, surely they should be 0 to 7?

The 8th case was me testing around. It was just just added an hour ago. No change in how the arduino acted

Mathias

The next thing you need to do it put Serial.print() at various locations in the code and see how far it gets. Doing that should allow you to identify the point at which is re-starts.

PerryBebbington:
The next thing you need to do it put Serial.print() at various locations in the code and see how far it gets. Doing that should allow you to identify the point at which is re-starts.

I have done as you suggested.

I am able to narrow the problem down to the Switch case. Whenever setVal is 7 the arduino resets before entering the respective case.

However, I am at a loss as to what the actual problem is.

The current setup is that values from 0-4 is handled by “default”, and values from 5-7 has a case.

Any clues to what might be going wrong here? Am i handling the Switch case incorrectly?

Regards

switch (setVal) {
 case 5: //Setting sensor selection
  pLCD -> setCursor(0, 0);
  strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[setVal])));
  pLCD -> print(buffer);
  strcpy_P(buffer, (char *)pgm_read_word(&(sensorName[levelSetting[setVal]])));
  pLCD -> setCursor(0, 1);
  pLCD -> print(buffer);
  Serial.print(" 7");
  break;
 case 6: //Setting output mode
  pLCD -> setCursor(0, 0);
  strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[setVal])));
  pLCD -> print(buffer);
  strcpy_P(buffer, (char *)pgm_read_word(&(outputMode[levelSetting[setVal]])));
  pLCD -> setCursor(0, 1);
  pLCD -> print(buffer);
  Serial.print(" 8");
  break;
 case 7://Setting area
  Serial.print(" a");
  pLCD -> setCursor(0, 0);
  strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[setVal])));
  Serial.print(" b");
  pLCD -> print(buffer);
  pLCD -> setCursor(0, 1);
  if (levelSetting[setVal] > 99) {
    Serial.print(" c");
    pLCD -> print((levelSetting[setVal]-(levelSetting[setVal]-1)));
    pLCD -> print(".");
    if (levelSetting[setVal]-100 < 10) {pLCD -> print(0);}
    pLCD -> print(levelSetting[setVal]-100);
  }
  else {
    Serial.print(" d");
    pLCD -> print(0);
    pLCD -> print(".");
    pLCD -> print(levelSetting[setVal]);
  }
  Serial.print(" e");
  strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[9])));
  pLCD -> print(buffer);
  Serial.print(" 9");
  break;

 default: //Setting for levels
  Serial.print(" x");
  pLCD -> setCursor(0, 0);
  strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[setVal])));
  pLCD -> print(buffer);
  if (setVal == 0){
    pLCD -> setCursor(9, 0);
    if (pumpRun == 0){strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[13])));}
    if (pumpRun == 1){strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[14])));}
    pLCD -> print(buffer);
  }
  pLCD -> setCursor(0, 1);
  pLCD -> print(levelSetting[setVal]);
  if (setVal == 0){strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[10])));}
  else {strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[8])));}
  pLCD -> print(buffer);
  if (setVal == 0){
    if (pumpRun == 0) {
      if (inFlow < 10 && inFlow >= 0) {
        pLCD -> setCursor(10, 1);
        pLCD -> print(" ");
        pLCD -> print(inFlow);
      }
      if (inFlow >= 10 && inFlow < 100) {
        pLCD -> setCursor(9, 1);
        pLCD -> print(" ");
        pLCD -> print(inFlow);
      }
      if (inFlow >= 100 && inFlow < 1000) {
        pLCD -> setCursor(8, 1);
        pLCD -> print(" ");
        pLCD -> print(inFlow);
      }
    }  
    else {
      if (outFlow > -10 && outFlow < 0) {
        pLCD -> setCursor(9, 1);
        pLCD -> print(" ");
        pLCD -> print(outFlow);
      }
      if (outFlow <= -10 && outFlow > -100) {
        pLCD -> setCursor(8, 1);
        pLCD -> print(" ");
        pLCD -> print(outFlow);
      }
      if (outFlow <= -100 && outFlow > -1000) {
        pLCD -> setCursor(7, 1);
        pLCD -> print(" ");
        pLCD -> print(outFlow);
      }
      if (outFlow <= -1000) {
        pLCD -> setCursor(6, 1);
        pLCD -> print(" ");
        pLCD -> print(outFlow);
      }
    }
    pLCD -> setCursor(13, 1);
    strcpy_P(buffer, (char *)pgm_read_word(&(LCDtext[11])));
    pLCD -> print(buffer);
    }
    Serial.print(" 10");
  break;
}

where did it bomb? a, b, c, d...?

Thats just the thing. It never gets that far.

It does the Serial.print() that's just outside the Switch, and then resets

Makes no sense. It should at least print 'a'. There is no code in that case before the debug print. How do you know it's happening with 7? Are you printing setVal? I think it's time to re-post the entire sketch with the new debug statements included.

The entire sketch is above 9000 characters. It is however attached here.

I am also printing “setVal” just outside the Switch case.

Whenever the arduino reset, it never gets to print setVal.

As setVal is changed by an interrupt, I wonder whether this might do some fuckeries if the switch case is already half way through?

SensorModuleV2.0.cpp (17.8 KB)

I think I might have just stumbled on a solution.

In the respective cases, i.e. case 5, 6 ,7 & default, most of the array variables are referenced with "setVal". However, as setVal is changed from an interrupt, it can suddenly reference an array outside its bounds.

I will have to do some more testing, but will revert with what I figure out.

Mathias

as setVal is changed from an interrupt, it can suddenly reference an array outside its bounds.

Wherever and whenever you change an array index variable you should check that it remains in the allowable range before using it to read to or write to the.

To avoid having to remember the size of arrays to use when checking it is helpful to declare a const variable to hold the number of elements in the array and for that number to be calculated by the program itself using

const byte NO_OF_ARRAY_ELEMENTS = sizeof(arrayName) / sizeof arrayName[0]);

In the respective cases, i.e. case 5, 6 ,7 & default, most of the array variables are referenced with "setVal". However, as setVal is changed from an interrupt, it can suddenly reference an array outside its bounds.

That begs the question: why is it being changed by an interrupt? Seems wrong to me.

PerryBebbington:
That begs the question: why is it being changed by an interrupt? Seems wrong to me.

Very fine question.

LevelSetting is used to store the inputs from the user, and it is accessed with setVal.

Some of the inputs range from 0 - 600.
Sensor selection ranges from 0 - 3
Output mode ranges 0-1.

The last two are, amongst other, used to access two char arrays. These contain the text displayed in case 5 & 6.

I'm still testing to ensure that it works now, but my hypothesis is as follows:

If the code is in switch case 6 (output mode) and setVal is changed to 7, levelSetting[setVal] now contains a number that ranges from 0-600. As the char arrays size is 2, that's no bueno.
Inside the switch cases I am now referencing the arrays with numbers, i.e. 5, 6, 7.

I am using interrupts to ensure responsiveness. That is, I deemed the code to be a bit lenghty, and because when using an ultrasonic sensor, I have experienced the Arduino to become down right slow if the sensor is blocked.

I don't know if it was the right approach, but it was the best I had, so to speak. To quote a certain Canadian from YouTube: you've gotta piss with cock you've got.

Anyway thanks a lot for the inputs so far. I really appreciate it.

LevelSetting is used to store the inputs from the user

I am using interrupts to ensure responsiveness.

I feared you were going to say that. You don't use interrupts for user input, causes all kinds of problems. Either you have not stated it or I have mis-read it, but I don't know how the user input it gathered; what is the user input device?

User input by any means is way slower than any well written program can cope with. If you are not returning to loop() every millisecond or less, or maybe, not longer than 10ms, then your code need re-writing. Your code is too long and I am too bad at reading other people's code for me to offer specific advice, but, if you are not already aware of them I suggest you read the following 2 tutorials and re-design your code to take it into account.

Using millis for timing
Demonstration for several things at the same time
Especially the second one.

There are other relevant tutorials and maybe someone else will follow this with a link to one I have missed.